encode.go

  1package httpbinding
  2
  3import (
  4	"fmt"
  5	"net/http"
  6	"net/url"
  7	"strconv"
  8	"strings"
  9)
 10
 11const (
 12	contentLengthHeader = "Content-Length"
 13	floatNaN            = "NaN"
 14	floatInfinity       = "Infinity"
 15	floatNegInfinity    = "-Infinity"
 16)
 17
 18// An Encoder provides encoding of REST URI path, query, and header components
 19// of an HTTP request. Can also encode a stream as the payload.
 20//
 21// Does not support SetFields.
 22type Encoder struct {
 23	path, rawPath, pathBuffer []byte
 24
 25	query  url.Values
 26	header http.Header
 27}
 28
 29// NewEncoder creates a new encoder from the passed in request. It assumes that
 30// raw path contains no valuable information at this point, so it passes in path
 31// as path and raw path for subsequent trans
 32func NewEncoder(path, query string, headers http.Header) (*Encoder, error) {
 33	return NewEncoderWithRawPath(path, path, query, headers)
 34}
 35
 36// NewHTTPBindingEncoder creates a new encoder from the passed in request. All query and
 37// header values will be added on top of the request's existing values. Overwriting
 38// duplicate values.
 39func NewEncoderWithRawPath(path, rawPath, query string, headers http.Header) (*Encoder, error) {
 40	parseQuery, err := url.ParseQuery(query)
 41	if err != nil {
 42		return nil, fmt.Errorf("failed to parse query string: %w", err)
 43	}
 44
 45	e := &Encoder{
 46		path:    []byte(path),
 47		rawPath: []byte(rawPath),
 48		query:   parseQuery,
 49		header:  headers.Clone(),
 50	}
 51
 52	return e, nil
 53}
 54
 55// Encode returns a REST protocol encoder for encoding HTTP bindings.
 56//
 57// Due net/http requiring `Content-Length` to be specified on the http.Request#ContentLength directly. Encode
 58// will look for whether the header is present, and if so will remove it and set the respective value on http.Request.
 59//
 60// Returns any error occurring during encoding.
 61func (e *Encoder) Encode(req *http.Request) (*http.Request, error) {
 62	req.URL.Path, req.URL.RawPath = string(e.path), string(e.rawPath)
 63	req.URL.RawQuery = e.query.Encode()
 64
 65	// net/http ignores Content-Length header and requires it to be set on http.Request
 66	if v := e.header.Get(contentLengthHeader); len(v) > 0 {
 67		iv, err := strconv.ParseInt(v, 10, 64)
 68		if err != nil {
 69			return nil, err
 70		}
 71		req.ContentLength = iv
 72		e.header.Del(contentLengthHeader)
 73	}
 74
 75	req.Header = e.header
 76
 77	return req, nil
 78}
 79
 80// AddHeader returns a HeaderValue for appending to the given header name
 81func (e *Encoder) AddHeader(key string) HeaderValue {
 82	return newHeaderValue(e.header, key, true)
 83}
 84
 85// SetHeader returns a HeaderValue for setting the given header name
 86func (e *Encoder) SetHeader(key string) HeaderValue {
 87	return newHeaderValue(e.header, key, false)
 88}
 89
 90// Headers returns a Header used for encoding headers with the given prefix
 91func (e *Encoder) Headers(prefix string) Headers {
 92	return Headers{
 93		header: e.header,
 94		prefix: strings.TrimSpace(prefix),
 95	}
 96}
 97
 98// HasHeader returns if a header with the key specified exists with one or
 99// more value.
100func (e Encoder) HasHeader(key string) bool {
101	return len(e.header[key]) != 0
102}
103
104// SetURI returns a URIValue used for setting the given path key
105func (e *Encoder) SetURI(key string) URIValue {
106	return newURIValue(&e.path, &e.rawPath, &e.pathBuffer, key)
107}
108
109// SetQuery returns a QueryValue used for setting the given query key
110func (e *Encoder) SetQuery(key string) QueryValue {
111	return NewQueryValue(e.query, key, false)
112}
113
114// AddQuery returns a QueryValue used for appending the given query key
115func (e *Encoder) AddQuery(key string) QueryValue {
116	return NewQueryValue(e.query, key, true)
117}
118
119// HasQuery returns if a query with the key specified exists with one or
120// more values.
121func (e *Encoder) HasQuery(key string) bool {
122	return len(e.query.Get(key)) != 0
123}