-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.go
More file actions
127 lines (112 loc) · 5.21 KB
/
Copy pathcontext.go
File metadata and controls
127 lines (112 loc) · 5.21 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
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package httpserver
import (
"bufio"
"net"
"net/http"
"time"
"github.com/thanhminhmr/go-common/common"
)
// Context is the per-request state passed to [Handler] and [Middleware]. It
// implements [context.Context] by delegating to the underlying HTTP request and
// owns the response state assembled by the handler chain.
//
// Context values are created by [Router.Handle]. The zero value is invalid, and
// a Context must not be copied after first use. Its lifetime ends when the
// response is written: before a streaming or hijack body runs, the Context is
// cleared, and calling anything on it — or on a [Response] handle saved from
// earlier — is a bug with undefined behavior.
type Context struct {
_ common.NoCopy
request *http.Request
writer http.ResponseWriter
// response
status int
body any
marshaller uint
}
// Deadline delegates to the HTTP request context.
func (c *Context) Deadline() (deadline time.Time, ok bool) { return c.request.Context().Deadline() }
// Done delegates to the HTTP request context.
func (c *Context) Done() <-chan struct{} { return c.request.Context().Done() }
// Err delegates to the HTTP request context.
func (c *Context) Err() error { return c.request.Context().Err() }
// Value delegates to the HTTP request context.
func (c *Context) Value(key any) any { return c.request.Context().Value(key) }
// Response returns a handle to the current response and reports whether one
// exists. Its status is zero until [Context.NewResponse] is called; until then
// Response returns the zero Response and false. A pending [Context.Hijack]
// means no response exists either.
func (c *Context) Response() (Response, bool) {
if c.status == 0 {
return Response{}, false
}
return Response{ctx: c}, true
}
// NewResponse starts a new response with status and returns its handle. It
// clears the previous body and all response headers, and cancels a pending
// [Context.Hijack]. The response is not written until the [Router.Handle]
// middleware and handler chain returns.
//
// NewResponse panics unless status is between 200 and 599, or when the Context
// was already cleared for a streaming or hijack body.
func (c *Context) NewResponse(status int) Response {
if status < 200 || status > 599 {
panic("BUG: invalid status")
}
c.status, c.body, c.marshaller = status, nil, marshallerIsDirect
clear(c.writer.Header())
return Response{ctx: c}
}
// Hijack records body as the takeover handler for the underlying network
// connection. The takeover itself is committed after the complete middleware
// and handler chain returns, at the same point a response is written: a
// pending hijack discards any response state assembled so far — after Hijack,
// [Context.Response] reports false and [Context.Hijacked] reports true — and
// at write time the connection is handed to body instead of writing a
// response. Middleware that runs after the handler can inspect the pending
// takeover with [Context.Hijacked] and cancel it by calling
// [Context.NewResponse]; calling Hijack again before the write replaces body.
//
// body receives the hijacked connection and a buffered ReadWriter holding any
// unread request bytes. A protocol-switch response (for example the WebSocket
// 101 upgrade line with its headers) must be written manually inside body.
// body owns the connection for its duration; the connection is closed when
// body returns or panics, and the error returned by body is logged at write
// time. After the takeover the HTTP request context no longer reflects the
// connection, so body must rely on connection reads or deadlines to notice a
// dropped peer. body runs after the Context was cleared, so it must not use
// the Context or any response handle saved from earlier.
//
// Hijack returns an error — without recording anything and without touching
// the response state, so the handler can still fall back to
// [Context.NewResponse] — when the underlying writer does not support
// hijacking ([http.ErrNotSupported]). A hijack failure at write time is
// logged and written as an empty 500 response.
func (c *Context) Hijack(body func(conn net.Conn, readWriter *bufio.ReadWriter) error) error {
writer := c.writer
if sw, ok := writer.(*StreamWriter); ok {
writer = sw.writer
}
if _, ok := writer.(http.Hijacker); !ok {
return http.ErrNotSupported
}
c.status, c.body, c.marshaller = 0, body, marshallerIsDirect
return nil
}
// Hijacked reports whether the connection is being taken over with
// [Context.Hijack]: a pending takeover recorded by a handler, which middleware
// may still cancel with [Context.NewResponse]. Once the takeover is committed
// the Context is cleared, so Hijacked no longer reports it.
func (c *Context) Hijacked() bool { return c.status == 0 && c.body != nil }
// clear ends the Context's lifetime: writeResponse calls it before handing the
// connection to a streaming or hijack body. Any use of a cleared Context is a
// bug with undefined behavior.
func (c *Context) clear() {
c.request, c.writer = nil, nil
c.status, c.body, c.marshaller = 0, nil, marshallerIsDirect
}