2019-05-13 01:27:26 +02:00
|
|
|
package serv
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
2019-06-06 14:57:00 +02:00
|
|
|
"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
|
|
|
}
|
|
|
|
|
2019-06-14 06:32:15 +02:00
|
|
|
func initResolvers() error {
|
2019-05-13 01:27:26 +02:00
|
|
|
rmap = make(map[uint64]*resolvFn)
|
|
|
|
|
|
|
|
for _, t := range conf.DB.Tables {
|
2019-06-14 06:32:15 +02:00
|
|
|
err := initRemotes(t)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-05-13 01:27:26 +02:00
|
|
|
}
|
2019-06-14 06:32:15 +02:00
|
|
|
return nil
|
2019-05-13 01:27:26 +02:00
|
|
|
}
|
|
|
|
|
2019-06-14 06:32:15 +02:00
|
|
|
func initRemotes(t configTable) error {
|
2019-05-13 01:27:26 +02:00
|
|
|
h := xxhash.New()
|
2019-06-14 06:32:15 +02:00
|
|
|
var err error
|
2019-05-13 01:27:26 +02:00
|
|
|
|
|
|
|
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 {
|
2019-06-14 06:32:15 +02:00
|
|
|
idcol, err = pcompile.IDColumn(t.Name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
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
|
2019-05-15 04:32:12 +02:00
|
|
|
|
2019-05-13 01:27:26 +02:00
|
|
|
val := &psql.DBRel{
|
|
|
|
Type: psql.RelRemote,
|
|
|
|
Col1: idcol,
|
|
|
|
Col2: idk,
|
|
|
|
}
|
2019-06-14 06:32:15 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2019-06-14 06:32:15 +02:00
|
|
|
|
|
|
|
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) {
|
2019-06-14 06:32:15 +02:00
|
|
|
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-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-09-20 06:19:11 +02:00
|
|
|
if host, ok := hdr["Host"]; ok {
|
2019-06-08 06:44:56 +02:00
|
|
|
req.Host = host[0]
|
2019-06-06 14:57:00 +02:00
|
|
|
}
|
2019-05-13 01:27:26 +02:00
|
|
|
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
2019-06-14 06:32:15 +02:00
|
|
|
logger.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()
|
|
|
|
|
2019-06-06 14:57:00 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Warn().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
|
|
|
|
}
|