super-graph/serv/resolve.go

163 lines
3.1 KiB
Go
Raw Normal View History

2019-05-13 01:27:26 +02:00
package serv
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
2019-05-13 01:27:26 +02:00
"strings"
"github.com/cespare/xxhash/v2"
2019-06-04 16:54:51 +02:00
"github.com/dosco/super-graph/jsn"
2019-05-13 01:27:26 +02:00
"github.com/dosco/super-graph/psql"
)
var (
rmap map[uint64]*resolvFn
)
type resolvFn struct {
IDField []byte
Path [][]byte
2019-09-20 06:19:11 +02:00
Fn func(h http.Header, id []byte) ([]byte, error)
2019-05-13 01:27:26 +02:00
}
func initResolvers() {
var err error
2019-05-13 01:27:26 +02:00
rmap = make(map[uint64]*resolvFn)
for _, t := range conf.Tables {
err = initRemotes(t)
if err != nil {
break
}
2019-05-13 01:27:26 +02:00
}
if err != nil {
errlog.Fatal().Err(err).Msg("failed to initialize resolvers")
}
2019-05-13 01:27:26 +02:00
}
func initRemotes(t configTable) error {
2019-05-13 01:27:26 +02:00
h := xxhash.New()
for _, r := range t.Remotes {
// defines the table column to be used as an id in the
// remote request
idcol := r.ID
// if no table column specified in the config then
// use the primary key of the table as the id
if len(idcol) == 0 {
pcol, err := pcompile.IDColumn(t.Name)
if err != nil {
return err
}
idcol = pcol.Key
2019-05-13 01:27:26 +02:00
}
idk := fmt.Sprintf("__%s_%s", t.Name, idcol)
// register a relationship between the remote data
// and the database table
val := &psql.DBRel{Type: psql.RelRemote}
val.Left.Col = idcol
val.Right.Col = idk
err := pcompile.AddRelationship(strings.ToLower(r.Name), t.Name, val)
if err != nil {
return err
}
2019-05-13 01:27:26 +02:00
// the function thats called to resolve this remote
// data request
fn := buildFn(r)
path := [][]byte{}
for _, p := range strings.Split(r.Path, ".") {
path = append(path, []byte(p))
}
rf := &resolvFn{
IDField: []byte(idk),
Path: path,
Fn: fn,
}
// index resolver obj by parent and child names
rmap[mkkey(h, r.Name, t.Name)] = rf
// index resolver obj by IDField
rmap[xxhash.Sum64(rf.IDField)] = rf
}
return nil
2019-05-13 01:27:26 +02:00
}
2019-09-20 06:19:11 +02:00
func buildFn(r configRemote) func(http.Header, []byte) ([]byte, error) {
2019-05-13 01:27:26 +02:00
reqURL := strings.Replace(r.URL, "$id", "%s", 1)
client := &http.Client{}
2019-09-20 06:19:11 +02:00
fn := func(hdr http.Header, id []byte) ([]byte, error) {
uri := fmt.Sprintf(reqURL, id)
req, err := http.NewRequest("GET", uri, nil)
2019-05-13 01:27:26 +02:00
if err != nil {
return nil, err
}
2019-12-03 05:08:35 +01:00
if host, ok := hdr["Host"]; ok {
req.Host = host[0]
}
2019-06-08 06:44:56 +02:00
for _, v := range r.SetHeaders {
req.Header.Set(v.Name, v.Value)
}
2019-05-13 01:27:26 +02:00
for _, v := range r.PassHeaders {
2019-09-20 06:19:11 +02:00
req.Header.Set(v, hdr.Get(v))
2019-05-13 01:27:26 +02:00
}
2019-06-08 06:44:56 +02:00
2019-12-03 05:08:35 +01:00
logger.Debug().Str("uri", uri).Msg("Remote Join")
2019-05-13 01:27:26 +02:00
res, err := client.Do(req)
if err != nil {
2019-11-25 08:22:33 +01:00
errlog.Error().Err(err).Msgf("Failed to connect to: %s", uri)
2019-05-13 01:27:26 +02:00
return nil, err
}
defer res.Body.Close()
if r.Debug {
reqDump, err := httputil.DumpRequestOut(req, true)
if err != nil {
return nil, err
}
resDump, err := httputil.DumpResponse(res, true)
if err != nil {
return nil, err
}
2019-12-03 05:08:35 +01:00
logger.Debug().Msgf("Remote Request Debug:\n%s\n%s",
reqDump, resDump)
}
if res.StatusCode != 200 {
return nil,
fmt.Errorf("server responded with a %d", res.StatusCode)
}
2019-05-13 01:27:26 +02:00
b, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
2019-06-04 16:54:51 +02:00
if err := jsn.ValidateBytes(b); err != nil {
return nil, err
}
2019-05-13 01:27:26 +02:00
return b, nil
}
return fn
}