package serv import ( "fmt" "io/ioutil" "net/http" "net/http/httputil" "strings" "github.com/cespare/xxhash/v2" "github.com/dosco/super-graph/jsn" "github.com/dosco/super-graph/psql" ) var ( rmap map[uint64]*resolvFn ) type resolvFn struct { IDField []byte Path [][]byte Fn func(h http.Header, id []byte) ([]byte, error) } func initResolvers() error { rmap = make(map[uint64]*resolvFn) for _, t := range conf.Tables { err := initRemotes(t) if err != nil { return err } } return nil } func initRemotes(t configTable) error { 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 } 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 } // 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 } func buildFn(r configRemote) func(http.Header, []byte) ([]byte, error) { reqURL := strings.Replace(r.URL, "$id", "%s", 1) client := &http.Client{} fn := func(hdr http.Header, id []byte) ([]byte, error) { uri := fmt.Sprintf(reqURL, id) req, err := http.NewRequest("GET", uri, nil) if err != nil { return nil, err } if host, ok := hdr["Host"]; ok { req.Host = host[0] } for _, v := range r.SetHeaders { req.Header.Set(v.Name, v.Value) } for _, v := range r.PassHeaders { req.Header.Set(v, hdr.Get(v)) } logger.Debug().Str("uri", uri).Msg("Remote Join") res, err := client.Do(req) if err != nil { errlog.Error().Err(err).Msgf("Failed to connect to: %s", uri) 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 } 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) } b, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } if err := jsn.ValidateBytes(b); err != nil { return nil, err } return b, nil } return fn }