1 package httpbinding
2 3 import (
4 "fmt"
5 "net/http"
6 "net/url"
7 "strconv"
8 "strings"
9 )
10 11 const (
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.
22 type 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
32 func 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.
39 func 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.
61 func (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
81 func (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
86 func (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
91 func (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.
100 func (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
105 func (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
110 func (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
115 func (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.
121 func (e *Encoder) HasQuery(key string) bool {
122 return len(e.query.Get(key)) != 0
123 }
124