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  }