forked from connectrpc/connect-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.go
More file actions
255 lines (216 loc) · 8.25 KB
/
context.go
File metadata and controls
255 lines (216 loc) · 8.25 KB
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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// Copyright 2021-2025 The Connect Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package connect
import (
"context"
"net/http"
)
// CallInfo represents information relevant to an RPC call.
type CallInfo interface {
// Spec returns a description of this call.
Spec() Spec
// Peer describes the other party for this call.
Peer() Peer
// RequestHeader returns the HTTP headers for this request. Headers beginning with
// "Connect-" and "Grpc-" are reserved for use by the Connect and gRPC
// protocols: applications may read them but shouldn't write them.
RequestHeader() http.Header
// ResponseHeader returns the HTTP headers for this response. Headers beginning with
// "Connect-" and "Grpc-" are reserved for use by the Connect and gRPC
// protocols: applications may read them but shouldn't write them.
// On the client side, this method returns nil before
// the call is actually made. After the call is made, for streaming operations,
// this method will block for the server to actually return response headers.
ResponseHeader() http.Header
// ResponseTrailer returns the trailers for this response. Depending on the underlying
// RPC protocol, trailers may be sent as HTTP trailers or a protocol-specific
// block of in-body metadata.
//
// Trailers beginning with "Connect-" and "Grpc-" are reserved for use by the
// Connect and gRPC protocols: applications may read them but shouldn't write
// them.
//
// On the client side, this method returns nil before
// the call is actually made. After the call is made, for streaming operations,
// this method will block for the server to actually return response trailers.
ResponseTrailer() http.Header
// HTTPMethod returns the HTTP method for this request. This is nearly always
// POST, but side-effect-free unary RPCs could be made via a GET.
//
// On a newly created request, via NewRequest, this will return the empty
// string until the actual request is actually sent and the HTTP method
// determined. This means that client interceptor functions will see the
// empty string until *after* they delegate to the handler they wrapped. It
// is even possible for this to return the empty string after such delegation,
// if the request was never actually sent to the server (and thus no
// determination ever made about the HTTP method).
HTTPMethod() string
internalOnly()
}
// Create a new client (i.e. outgoing) context for use from a client. When the
// returned context is passed to RPCs, the returned call info can be used to set
// request metadata before the RPC is invoked and to inspect response
// metadata after the RPC completes.
//
// The returned context may be re-used across RPCs as long as they are
// not concurrent. Results of all CallInfo methods other than
// RequestHeader() are undefined if the context is used with concurrent RPCs.
func NewClientContext(ctx context.Context) (context.Context, CallInfo) {
info := &clientCallInfo{}
return context.WithValue(ctx, clientCallInfoContextKey{}, info), info
}
// CallInfoForHandlerContext returns the CallInfo for the given handler (i.e. incoming) context, if there is one.
func CallInfoForHandlerContext(ctx context.Context) (CallInfo, bool) {
value, ok := ctx.Value(handlerCallInfoContextKey{}).(CallInfo)
return value, ok
}
// handlerCallInfo is a CallInfo implementation used for unary handlers.
type handlerCallInfo struct {
spec Spec
peer Peer
method string
requestHeader http.Header
responseHeader http.Header
responseTrailer http.Header
}
func (c *handlerCallInfo) Spec() Spec {
return c.spec
}
func (c *handlerCallInfo) Peer() Peer {
return c.peer
}
func (c *handlerCallInfo) RequestHeader() http.Header {
if c.requestHeader == nil {
c.requestHeader = make(http.Header)
}
return c.requestHeader
}
func (c *handlerCallInfo) ResponseHeader() http.Header {
if c.responseHeader == nil {
c.responseHeader = make(http.Header)
}
return c.responseHeader
}
func (c *handlerCallInfo) ResponseTrailer() http.Header {
if c.responseTrailer == nil {
c.responseTrailer = make(http.Header)
}
return c.responseTrailer
}
func (c *handlerCallInfo) HTTPMethod() string {
return c.method
}
// internalOnly implements CallInfo.
func (c *handlerCallInfo) internalOnly() {}
// streamingHandlerCallInfo is a CallInfo implementation used for streaming RPC handlers.
type streamingHandlerCallInfo struct {
conn StreamingHandlerConn
}
func (c *streamingHandlerCallInfo) Spec() Spec {
return c.conn.Spec()
}
func (c *streamingHandlerCallInfo) Peer() Peer {
return c.conn.Peer()
}
func (c *streamingHandlerCallInfo) RequestHeader() http.Header {
return c.conn.RequestHeader()
}
func (c *streamingHandlerCallInfo) ResponseHeader() http.Header {
return c.conn.ResponseHeader()
}
func (c *streamingHandlerCallInfo) ResponseTrailer() http.Header {
return c.conn.ResponseTrailer()
}
func (c *streamingHandlerCallInfo) HTTPMethod() string {
// All stream calls are POSTs
return http.MethodPost
}
// internalOnly implements CallInfo.
func (c *streamingHandlerCallInfo) internalOnly() {}
// clientCallInfo is a CallInfo implementation used for clients.
type clientCallInfo struct {
responseSource
spec Spec
peer Peer
method string
requestHeader http.Header
}
func (c *clientCallInfo) Spec() Spec {
return c.spec
}
func (c *clientCallInfo) Peer() Peer {
return c.peer
}
func (c *clientCallInfo) RequestHeader() http.Header {
if c.requestHeader == nil {
c.requestHeader = make(http.Header)
}
return c.requestHeader
}
func (c *clientCallInfo) ResponseHeader() http.Header {
if c.responseSource == nil {
return nil
}
return c.responseSource.ResponseHeader()
}
func (c *clientCallInfo) ResponseTrailer() http.Header {
if c.responseSource == nil {
return nil
}
return c.responseSource.ResponseTrailer()
}
func (c *clientCallInfo) HTTPMethod() string {
return c.method
}
// internalOnly implements CallInfo.
func (c *clientCallInfo) internalOnly() {}
// clientCallInfoContextKey is the key used to store client call info in context.
type clientCallInfoContextKey struct{}
// sentinelContextKey is the key used to store a copy of client call info in context
// when a request is made.
// Each step in an interceptor chain compares the actual call info with the
// sentinel call info. If the two values are different, the request will
// return an error in the interceptor.
// This protects against changing the call info in interceptors, which is prohibited
// as it would allow users to modify call info mid-flight independent of the actual
// request or response.
// Users who wish to modify call info data such as headers and trailers should instead
// use Connect [Request] and [Response] wrapper types.
type sentinelContextKey struct{}
// handlerCallInfoContextKey is the key used to store handler call info in context.
type handlerCallInfoContextKey struct{}
// responseSource indicates a type that manages response headers and trailers.
type responseSource interface {
ResponseHeader() http.Header
ResponseTrailer() http.Header
}
// responseWrapper wraps a Response object so that it can implement the responseSource interface.
type responseWrapper[Res any] struct {
response *Response[Res]
}
func (w *responseWrapper[Res]) ResponseHeader() http.Header {
return w.response.Header()
}
func (w *responseWrapper[Res]) ResponseTrailer() http.Header {
return w.response.Trailer()
}
// clientCallInfoForContext gets the call info from a client/outgoing context.
func clientCallInfoForContext(ctx context.Context) (*clientCallInfo, bool) {
info, ok := ctx.Value(clientCallInfoContextKey{}).(*clientCallInfo)
return info, ok
}
// newHandlerContext creates a new handler/incoming context.
func newHandlerContext(ctx context.Context, info CallInfo) context.Context {
return context.WithValue(ctx, handlerCallInfoContextKey{}, info)
}