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}