github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/lib/http.go (about) 1 package lib 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/http" 10 11 apiutil "github.com/qri-io/qri/api/util" 12 "github.com/qri-io/qri/base/params" 13 qhttp "github.com/qri-io/qri/lib/http" 14 ) 15 16 // NewHTTPRequestHandler creates a JSON-API endpoint for a registered dispatch 17 // method 18 func NewHTTPRequestHandler(inst *Instance, libMethod string) http.HandlerFunc { 19 return func(w http.ResponseWriter, r *http.Request) { 20 if r.Method != http.MethodPost { 21 apiutil.WriteErrResponse(w, http.StatusNotFound, fmt.Errorf("%s only accepts http POST requests", libMethod)) 22 return 23 } 24 25 p := inst.NewInputParam(libMethod) 26 if p == nil { 27 log.Debugw("http request: input params returned nil", "libMethod", libMethod) 28 apiutil.WriteErrResponse(w, http.StatusBadRequest, fmt.Errorf("no params for method %s", libMethod)) 29 return 30 } 31 32 if err := DecodeParams(r, p); err != nil { 33 log.Debugw("decode params:", "err", err) 34 apiutil.WriteErrResponse(w, http.StatusBadRequest, err) 35 return 36 } 37 38 source := SourceFromRequest(r) 39 res, cursor, err := inst.WithSource(source).Dispatch(r.Context(), libMethod, p) 40 if err != nil { 41 log.Debugw("http request: dispatch", "err", err) 42 apiutil.RespondWithError(w, err) 43 return 44 } 45 46 if cursor != nil { 47 nextURL := r.URL.Path 48 nextParams, err := cursor.ToParams() 49 if err != nil { 50 apiutil.RespondWithError(w, err) 51 return 52 } 53 apiutil.WriteResponseWithNextPage(w, res, nextURL, nextParams) 54 return 55 } 56 57 apiutil.WriteResponse(w, res) 58 } 59 } 60 61 // SourceFromRequest retrieves from the http request the source for resolving refs 62 func SourceFromRequest(r *http.Request) string { 63 return r.Header.Get(qhttp.SourceResolver) 64 } 65 66 // DecodeParams decodes a json body into params 67 func DecodeParams(r *http.Request, p interface{}) error { 68 defer func() { 69 if defSetter, ok := p.(NZDefaultSetter); ok { 70 defSetter.SetNonZeroDefaults() 71 } 72 }() 73 74 body, err := snoop(&r.Body) 75 if err != nil && err != io.EOF { 76 return fmt.Errorf("unable to read request body: %w", err) 77 } 78 79 if err != io.EOF { 80 if err := json.NewDecoder(body).Decode(p); err != nil { 81 return fmt.Errorf("unable to decode params from request body: %w", err) 82 } 83 } 84 85 if lp, ok := p.(params.ListParams); ok { 86 if err := lp.ListParamsFromRequest(r); err != nil { 87 return fmt.Errorf("unable to get list params from request: %w", err) 88 } 89 } 90 91 // allow empty params 92 return nil 93 } 94 95 // snoop reads from an io.ReadCloser and restores it so it can be read again 96 func snoop(body *io.ReadCloser) (io.ReadCloser, error) { 97 if body != nil && *body != nil { 98 result, err := ioutil.ReadAll(*body) 99 (*body).Close() 100 101 if err != nil { 102 return nil, err 103 } 104 if len(result) == 0 { 105 return nil, io.EOF 106 } 107 108 *body = ioutil.NopCloser(bytes.NewReader(result)) 109 return ioutil.NopCloser(bytes.NewReader(result)), nil 110 } 111 return nil, io.EOF 112 }