-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnegotiate.go
119 lines (98 loc) · 2.96 KB
/
negotiate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package ep
import (
"bytes"
"net/http"
"github.com/advanderveer/ep/epcoding"
"github.com/advanderveer/ep/internal/accept"
)
// detectContentType attempt to guess the content type by looking at the
// provided bytes.
func detectContentType(b []byte) (ct string) {
ct = http.DetectContentType(b)
if ct != "text/plain; charset=utf-8" {
return ct
}
b = bytes.TrimSpace(b)
if len(b) > 0 {
// in our usecase of detecting request bodies we can be a bit more
// liberal and assume that the client tries to send either JSON or XML
switch {
case b[0] == '{':
fallthrough
case b[0] == '"':
fallthrough
case b[0] == '[':
return "application/json; charset=utf-8"
}
}
return
}
// negotiateDecoder will inspect the request and available body decoders
// to figure out which should be used. It returns nil when the request's body
// should not be considered by the server and an error if this is unexpected.
func negotiateDecoder(r *http.Request, decs []epcoding.Decoding) (epcoding.Decoder, error) {
const op Op = "negotiateDecoder"
// If the request has a content-type it has explicitely indicated to have
// content and of a certain type. Else we sniff to make sure it's empty
ct := r.Header.Get("Content-Type")
if ct == "" {
prc := Buffer(r.Body)
peek, _ := prc.Peek(512)
if len(peek) < 1 {
return nil, nil // it's for sure empty. That's fine
}
r.Body = prc
// attempt to detect it
ct = detectContentType(peek)
}
// At this point we know the client really has sent something to us so if
// we can't handle it the negotiation fails
if len(decs) < 1 {
return nil, Err(op,
"client sent request body but no decoders configured",
UnsupportedError,
)
}
// Parse the content header, we only care about the value
value, _ := accept.ParseValueAndParams(ct)
// Turn the decodings into asks for the negotiation algorithm
asks := make([]string, 0, len(decs))
for _, dec := range decs {
asks = append(asks, dec.Accepts())
}
// finally, negotiate what is necessary for the content type
_, aski := accept.Negotiate(asks, []string{value})
if aski < 0 {
return nil, Err(op,
"non-empty request body no configured decoder accepts it",
UnsupportedError,
)
}
return decs[aski].Decoder(r), nil
}
func negotiateEncoder(
r *http.Request,
w http.ResponseWriter,
encs []epcoding.Encoding,
) (epcoding.Encoder, string, error) {
const op Op = "negotiateEncoder"
if len(encs) < 1 {
return nil, "", Err(op, "no encoders configured", ServerError)
}
asks := r.Header.Values("Accept")
if len(asks) < 1 || r.Header.Get("Accept") == "" {
return encs[0].Encoder(w), encs[0].Produces(), nil
}
offers := make([]string, 0, len(encs))
for _, enc := range encs {
offers = append(offers, enc.Produces())
}
offeri, _ := accept.Negotiate(asks, offers)
if offeri < 0 {
return nil, "", Err(op,
"no configured encoder produces what the client accepts",
UnacceptableError,
)
}
return encs[offeri].Encoder(w), encs[offeri].Produces(), nil
}