-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.go
More file actions
377 lines (335 loc) · 14 KB
/
context.go
File metadata and controls
377 lines (335 loc) · 14 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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
package httpx
import (
"context"
"io"
"mime/multipart"
"net/http"
)
// RequestInfo exposes a stable, read-only view of an incoming HTTP request.
//
// All methods on RequestInfo are side-effect-free:
// calling them MUST NOT consume the request body, trigger form parsing,
// or mutate any internal request state.
//
// This interface is intended to be safely used by middleware and handlers
// that only need to request metadata such as routing, headers, queries, and cookies.
//
// Implementations should provide best-effort, framework-independent behavior
// across supported HTTP frameworks.
type RequestInfo interface {
Method() string
Path() string // Always returns a request path
FullPath() string // Returns a route pattern when available, empty otherwise
ClientIP() string // Best-effort client IP detection
Param(key string) string
Params() map[string]string // nil if no params
Query(key string) string
Queries() map[string][]string // nil if no queries
RawQuery() string
Header(key string) string
Headers() map[string][]string // nil if no headers
Cookie(name string) (string, error) // Returns error if cookie not found
Cookies() map[string]string // nil if no cookies
}
// BodyAccess provides access to the raw request body.
//
// Methods on BodyAccess MAY consume the request body.
// Implementations SHOULD ensure the body can be read multiple times
// when possible, so that BodyAccess and Binder can coexist safely.
//
// Callers must assume that reading from the body has consumption semantics
// and should avoid invoking these methods unless necessary.
type BodyAccess interface {
// BodyRaw returns the full request body as a byte slice.
//
// Calling this method may consume the underlying request body.
// Implementations should make best-effort to allow subsequent reads.
BodyRaw() ([]byte, error)
// BodyReader returns a reader for the request body.
//
// The returned reader may be consumed by the caller.
// Implementations should document whether the reader is reusable.
BodyReader() io.ReadCloser
}
// FormAccess provides access to form and multipart form data.
//
// Methods on FormAccess MAY trigger form or multipart parsing.
// Parsing form data can have observable side effects, including:
// - consuming the request body
// - allocating memory
// - creating temporary files on disk
//
// Callers should treat these methods as potentially expensive and
// avoid calling them unless form data is required.
//
// Implementations should ensure that form parsing is performed at most once
// per request and that parsed results are reused across calls.
type FormAccess interface {
// FormValue returns the first value associated with the given key.
//
// Calling this method may trigger form parsing.
// If the key does not exist, an empty string is returned.
FormValue(key string) string
// MultipartForm returns the parsed multipart form.
//
// Calling this method may trigger multipart parsing and cause
// temporary files to be created on disk.
//
// The returned *multipart.Form is owned by the request context and
// must not be modified by the caller.
MultipartForm() (*multipart.Form, error)
// FormFile returns the first file for the provided form field name.
//
// Calling this method may trigger multipart parsing.
// If no file is associated with the given name, an error is returned.
FormFile(name string) (*multipart.FileHeader, error)
}
// Request aggregates request inspection and request data access capabilities.
//
// Request is a composite interface that combines side-effect-free request
// metadata access with request body and form access.
//
// Callers should be aware that while RequestInfo methods are guaranteed
// to be side-effect free, methods provided by BodyAccess and FormAccess
// MAY consume the request body or trigger request parsing.
type Request interface {
// RequestInfo provides read-only access to request metadata.
RequestInfo
// BodyAccess provides access to the raw request body and may
// have consumption semantics.
BodyAccess
// FormAccess provides access to form and multipart form data and
// may trigger parsing with observable side effects.
FormAccess
}
// Binder standardizes payload decoding across HTTP frameworks.
//
// Binder methods decode data from different parts of the request
// into the provided destination structure. Decoding behavior is
// based on struct tags and follows framework-independent conventions.
//
// Binder methods MAY consume the request body or trigger parsing
// of request data. Implementations SHOULD ensure that decoding can
// coexist safely with BodyAccess and FormAccess when possible.
type Binder interface {
// BindJSON decodes the JSON request body into dst.
//
// Decoding is performed based on `json` struct tags.
//
// Calling this method may consume the request body.
// Implementations should make best-effort to allow the body
// to be read again after binding.
BindJSON(dst any) error
// BindQuery decodes URL query parameters into dst.
//
// Decoding is performed based on `query` struct tags.
BindQuery(dst any) error
// BindForm decodes form and multipart form fields into dst.
//
// Decoding is performed based on `form` struct tags.
// Calling this method may trigger form or multipart parsing.
BindForm(dst any) error
// BindURI decodes route parameters into dst.
//
// Decoding is performed based on `uri` struct tags.
BindURI(dst any) error
// BindHeader decodes HTTP headers into dst.
//
// Decoding is performed based on `header` struct tags.
// Header field names should be treated in a case-insensitive manner.
BindHeader(dst any) error
}
// Responder writes HTTP responses in a framework-independent manner.
//
// Methods on Responder mutate the outgoing response and return errors
// to indicate success or failure of response operations.
// Once a response body is written, the response is considered committed,
// and further attempts to modify status code or headers may return errors,
// depending on the underlying framework.
//
// Implementations should ensure consistent behavior across frameworks
// where possible.
type Responder interface {
// Status sets the HTTP status code for the response.
//
// Calling this method does not write the response body.
Status(code int)
// SetHeader sets a response header.
//
// This method does not write the response body.
SetHeader(key, value string)
// SetCookie adds a Set-Cookie header to the response.
//
// This method does not write the response body.
SetCookie(cookie *http.Cookie)
// JSON writes the given value as a JSON response with the provided status code.
//
// The Content-Type header should be set to "application/json".
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., JSON marshaling error,
// response already committed).
JSON(code int, v any) error
// Text writes the given string as a plain text response with the provided status code.
//
// The Content-Type header should be set to "text/plain; charset=utf-8".
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., response already committed).
Text(code int, s string) error
// NoContent writes a response with no body and the provided status code.
//
// This method commits the response without writing a body.
// Returns nil on success, error on failure (e.g., response already committed).
NoContent(code int) error
// Bytes writes raw bytes to the response with the provided status code
// and Content-Type.
//
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., response already committed).
Bytes(code int, b []byte, contentType string) error
// DataFromReader streams data from the provided reader to the response.
//
// The size parameter specifies the total number of bytes to be written.
// A size of -1 indicates that the size is unknown.
//
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., IO error, response already committed).
DataFromReader(code int, contentType string, r io.Reader, size int) error
// File writes the contents of the specified file to the response.
//
// Implementations may use optimized file transfer mechanisms
// provided by the underlying framework.
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., file not found, response already committed).
File(path string) error
// Redirect sends a redirect response to the client with the provided
// status code and target location.
//
// Calling this method commits the response.
// Returns nil on success, error on failure (e.g., invalid status code, response already committed).
Redirect(code int, location string) error
}
// ResponseInfo exposes a read-only response state.
//
// This optional capability can be used by middleware and handlers that need
// to inspect the response after downstream handlers have run.
//
// Implementations should provide best-effort behavior across frameworks.
type ResponseInfo interface {
// StatusCode returns the current response status code.
StatusCode() int
}
// NativeContextProvider exposes the underlying framework context.
//
// This optional capability is an escape hatch for framework-specific features
// that are intentionally not included in the cross-framework Context surface.
type NativeContextProvider interface {
NativeContext() any
}
// StateStore carries request-scoped values shared across the handler chain.
//
// StateStore provides a simple key-value storage that is scoped to the
// lifetime of a single request. Values stored in StateStore are intended
// to be shared between middleware and handlers handling the same request,
// within the same handler chain execution.
//
// IMPORTANT: Values stored via Set are NOT propagated through the standard
// context.Context returned by Context.Context(). They are visible only to
// middleware and handlers that share the same httpx.Context instance for
// the current request. To propagate values through context.Context (e.g.
// into downstream business logic, goroutines, or RPC calls), use
// SetContext with context.WithValue instead.
//
// Stored values MUST NOT be accessed concurrently without external
// synchronization unless the implementation explicitly guarantees
// concurrency safety.
type StateStore interface {
// Set associates the given value with the provided key for the
// lifetime of the current request.
//
// The value is accessible only within the current handler chain
// (i.e., by subsequent middleware and the final handler). It is
// NOT propagated through context.Context.
//
// Setting a value with an existing key replaces the previous value.
Set(key string, val any)
// Get retrieves the value associated with the given key.
//
// The returned boolean indicates whether the key was present.
// Only values set via Set on the same httpx.Context instance
// are visible; values stored in context.Context are not accessible here.
Get(key string) (any, bool)
}
// Context is the cross-framework surface passed into handlers and middleware.
//
// Context aggregates request inspection, request data binding, response
// writing, request-scoped state, and handler chain control into a single
// interface.
//
// Context is valid only for the lifetime of a single request and MUST NOT
// be retained or accessed after the request has completed.
//
// To pass request-scoped metadata (such as trace IDs or deadlines) into
// business logic, middleware, or background goroutines, always use the
// standard context.Context obtained via the Context() method. Do NOT pass
// the httpx.Context itself across goroutine boundaries — it may be backed
// by a pooled object whose lifetime ends when the HTTP response is sent.
//
// Implementations should provide consistent behavior across supported
// HTTP frameworks while respecting their underlying execution models.
type Context interface {
// Request provides access to incoming request data.
Request
// Responder provides methods to write the outgoing response.
Responder
// Binder provides standardized request data decoding.
Binder
// StateStore provides request-scoped key-value storage.
StateStore
// Context returns the standard Go context.Context for the current request.
//
// The returned context is derived from the underlying framework context
// and respects request cancellation and deadlines. It is safe to pass
// this value to downstream business logic, database calls, or RPC clients.
//
// Values stored via StateStore.Set are NOT visible through the returned
// context.Context. Use SetContext with context.WithValue to propagate
// values through the standard context chain.
Context() context.Context
// SetContext replaces the standard Go context.Context for the current request.
//
// This is typically used by middleware to inject request-scoped metadata:
//
// ctx.SetContext(context.WithValue(ctx.Context(), traceIDKey, id))
//
// The provided context should be derived from ctx.Context() to preserve
// cancellation and deadline propagation.
SetContext(ctx context.Context)
// Next executes downstream handlers in the chain.
//
// It returns nil if no error occurred downstream.
// If one or more errors occurred downstream, it returns a non-nil error.
// Implementations may aggregate multiple errors (for example, via errors.Join)
// and are not required to preserve framework-specific ordering semantics.
//
// If an error is returned, the middleware chain should be interrupted
// and the error should be handled appropriately.
Next() error
}
// AsResponseInfo returns response inspection capability when supported.
func AsResponseInfo(ctx Context) (ResponseInfo, bool) {
ri, ok := ctx.(ResponseInfo)
return ri, ok
}
// AsNativeContext returns the underlying native context when supported.
func AsNativeContext[T any](ctx Context) (T, bool) {
var zero T
nativeProvider, ok := ctx.(NativeContextProvider)
if !ok {
return zero, false
}
native, ok := nativeProvider.NativeContext().(T)
if !ok {
return zero, false
}
return native, true
}