-
Notifications
You must be signed in to change notification settings - Fork 217
/
context.go
212 lines (181 loc) · 5.83 KB
/
context.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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package http
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
)
// TransportContext allows a Receiver to understand the context of a request.
type TransportContext struct {
URI string
Host string
Method string
Header http.Header
StatusCode int
// IgnoreHeaderPrefixes controls what comes back from AttendToHeaders.
// AttendToHeaders controls what is output for .String()
IgnoreHeaderPrefixes []string
}
// NewTransportContext creates a new TransportContext from a http.Request.
func NewTransportContext(req *http.Request) TransportContext {
var tx *TransportContext
if req != nil {
tx = &TransportContext{
URI: req.RequestURI,
Host: req.Host,
Method: req.Method,
Header: req.Header,
}
} else {
tx = &TransportContext{}
}
tx.AddIgnoreHeaderPrefix("accept-encoding", "user-agent", "connection", "content-type")
return *tx
}
// NewTransportContextFromResponse creates a new TransportContext from a http.Response.
// If `res` is nil, it returns a context with a http.StatusInternalServerError status code.
func NewTransportContextFromResponse(res *http.Response) TransportContext {
var tx *TransportContext
if res != nil {
tx = &TransportContext{
Header: res.Header,
StatusCode: res.StatusCode,
}
} else {
tx = &TransportContext{StatusCode: http.StatusInternalServerError}
}
tx.AddIgnoreHeaderPrefix("accept-encoding", "user-agent", "connection", "content-type")
return *tx
}
// TransportResponseContext allows a Receiver response with http transport specific fields.
type TransportResponseContext struct {
// Header will be merged with the response headers.
Header http.Header
}
// AttendToHeaders returns the list of headers that exist in the TransportContext that are not currently in
// tx.IgnoreHeaderPrefix.
func (tx TransportContext) AttendToHeaders() []string {
a := []string(nil)
if tx.Header != nil && len(tx.Header) > 0 {
for k := range tx.Header {
if tx.shouldIgnoreHeader(k) {
continue
}
a = append(a, k)
}
}
return a
}
func (tx TransportContext) shouldIgnoreHeader(h string) bool {
for _, v := range tx.IgnoreHeaderPrefixes {
if strings.HasPrefix(strings.ToLower(h), strings.ToLower(v)) {
return true
}
}
return false
}
// String generates a pretty-printed version of the resource as a string.
func (tx TransportContext) String() string {
b := strings.Builder{}
b.WriteString("Transport Context,\n")
empty := b.Len()
if tx.URI != "" {
b.WriteString(" URI: " + tx.URI + "\n")
}
if tx.Host != "" {
b.WriteString(" Host: " + tx.Host + "\n")
}
if tx.Method != "" {
b.WriteString(" Method: " + tx.Method + "\n")
}
if tx.StatusCode != 0 {
b.WriteString(" StatusCode: " + strconv.Itoa(tx.StatusCode) + "\n")
}
if tx.Header != nil && len(tx.Header) > 0 {
b.WriteString(" Header:\n")
for _, k := range tx.AttendToHeaders() {
b.WriteString(fmt.Sprintf(" %s: %s\n", k, tx.Header.Get(k)))
}
}
if b.Len() == empty {
b.WriteString(" nil\n")
}
return b.String()
}
// AddIgnoreHeaderPrefix controls what header key is to be attended to and/or printed.
func (tx *TransportContext) AddIgnoreHeaderPrefix(prefix ...string) {
if tx.IgnoreHeaderPrefixes == nil {
tx.IgnoreHeaderPrefixes = []string(nil)
}
tx.IgnoreHeaderPrefixes = append(tx.IgnoreHeaderPrefixes, prefix...)
}
// Opaque key type used to store TransportContext
type transportContextKeyType struct{}
var transportContextKey = transportContextKeyType{}
// WithTransportContext return a context with the given TransportContext into the provided context object.
func WithTransportContext(ctx context.Context, tcxt TransportContext) context.Context {
return context.WithValue(ctx, transportContextKey, tcxt)
}
// TransportContextFrom pulls a TransportContext out of a context. Always
// returns a non-nil object.
func TransportContextFrom(ctx context.Context) TransportContext {
tctx := ctx.Value(transportContextKey)
if tctx != nil {
if tx, ok := tctx.(TransportContext); ok {
return tx
}
if tx, ok := tctx.(*TransportContext); ok {
return *tx
}
}
return TransportContext{}
}
// Opaque key type used to store Headers
type headerKeyType struct{}
var headerKey = headerKeyType{}
// ContextWithHeader returns a context with a header added to the given context.
// Can be called multiple times to set multiple header key/value pairs.
func ContextWithHeader(ctx context.Context, key, value string) context.Context {
header := HeaderFrom(ctx)
header.Add(key, value)
return context.WithValue(ctx, headerKey, header)
}
// HeaderFrom extracts the header object in the given context. Always returns a non-nil Header.
func HeaderFrom(ctx context.Context) http.Header {
ch := http.Header{}
header := ctx.Value(headerKey)
if header != nil {
if h, ok := header.(http.Header); ok {
copyHeaders(h, ch)
}
}
return ch
}
// SetContextHeader sets the context's headers replacing any headers currently in context.
func SetContextHeaders(ctx context.Context, headers http.Header) context.Context {
return context.WithValue(ctx, headerKey, headers)
}
// Opaque key type used to store long poll target.
type longPollTargetKeyType struct{}
var longPollTargetKey = longPollTargetKeyType{}
// WithLongPollTarget returns a new context with the given long poll target.
// `target` should be a full URL and will be injected into the long polling
// http request within StartReceiver.
func ContextWithLongPollTarget(ctx context.Context, target string) context.Context {
return context.WithValue(ctx, longPollTargetKey, target)
}
// LongPollTargetFrom looks in the given context and returns `target` as a
// parsed url if found and valid, otherwise nil.
func LongPollTargetFrom(ctx context.Context) *url.URL {
c := ctx.Value(longPollTargetKey)
if c != nil {
if s, ok := c.(string); ok && s != "" {
if target, err := url.Parse(s); err == nil {
return target
}
}
}
return nil
}