Skip to content
This repository was archived by the owner on Jun 12, 2026. It is now read-only.

Commit 83ab335

Browse files
lvan100lianghuan
authored andcommitted
refactor(httpsvr): add RequestContext
1 parent 12ad253 commit 83ab335

3 files changed

Lines changed: 133 additions & 97 deletions

File tree

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"mime"
2424
"net/http"
2525
"strconv"
26-
"strings"
2726

2827
"github.com/go-spring/stdlib/errutil"
2928
"github.com/go-spring/stdlib/jsonflow"
@@ -36,93 +35,6 @@ var ErrorHandler = func(r *http.Request, w http.ResponseWriter, err error) {
3635
http.Error(w, err.Error(), http.StatusInternalServerError)
3736
}
3837

39-
type ctxKeyType struct{}
40-
41-
var ctxKey ctxKeyType
42-
43-
// httpReqResp wraps both *http.Request and http.ResponseWriter.
44-
type httpReqResp struct {
45-
r *http.Request
46-
w http.ResponseWriter
47-
}
48-
49-
// getHTTPReqResp retrieves the httpReqResp wrapper from the context.
50-
func getHTTPReqResp(ctx context.Context) httpReqResp {
51-
req, _ := ctx.Value(&ctxKey).(httpReqResp)
52-
return req
53-
}
54-
55-
// setHTTPReqResp stores the http.Request and http.ResponseWriter in the context.
56-
func setHTTPReqResp(ctx context.Context, r *http.Request, w http.ResponseWriter) context.Context {
57-
return context.WithValue(ctx, &ctxKey, httpReqResp{r, w})
58-
}
59-
60-
// GetReq retrieves the *http.Request from the context if available.
61-
func GetReq(ctx context.Context) *http.Request {
62-
return getHTTPReqResp(ctx).r
63-
}
64-
65-
// GetHeader retrieves a specific HTTP request header by key from the context.
66-
func GetHeader(ctx context.Context, key string) string {
67-
if r := getHTTPReqResp(ctx).r; r != nil {
68-
return r.Header.Get(key)
69-
}
70-
return ""
71-
}
72-
73-
// SetCode sets the HTTP response status code in the context.
74-
func SetCode(ctx context.Context, httpCode int) {
75-
if w := getHTTPReqResp(ctx).w; w != nil {
76-
w.WriteHeader(httpCode)
77-
}
78-
}
79-
80-
// SetHeader sets a response header key/value pair in the context.
81-
func SetHeader(ctx context.Context, key, value string) {
82-
if w := getHTTPReqResp(ctx).w; w != nil {
83-
w.Header().Set(key, value)
84-
}
85-
}
86-
87-
// SetCookie adds a Set-Cookie header to the HTTP response in the context.
88-
func SetCookie(ctx context.Context, cookie *http.Cookie) {
89-
if cookie != nil {
90-
if w := getHTTPReqResp(ctx).w; w != nil {
91-
http.SetCookie(w, cookie)
92-
}
93-
}
94-
}
95-
96-
// Router defines a single route with HTTP method, pattern, and handler.
97-
type Router struct {
98-
Method string
99-
Pattern string
100-
Handler http.HandlerFunc
101-
}
102-
103-
// Server is an interface that defines a method to register routes.
104-
type Server interface {
105-
Route(r Router)
106-
}
107-
108-
// SimpleServer defines a basic HTTP server with an internal multiplexer.
109-
type SimpleServer struct {
110-
*http.Server
111-
mux *http.ServeMux
112-
}
113-
114-
// NewSimpleServer creates a new SimpleServer instance with the specified address.
115-
func NewSimpleServer(addr string) *SimpleServer {
116-
mux := http.NewServeMux()
117-
svr := &http.Server{Addr: addr, Handler: mux}
118-
return &SimpleServer{Server: svr, mux: mux}
119-
}
120-
121-
// Route registers a new route in the SimpleServer with the provided router.
122-
func (s *SimpleServer) Route(r Router) {
123-
s.mux.HandleFunc(strings.TrimSpace(r.Method+" "+r.Pattern), r.Handler)
124-
}
125-
12638
// RequestObject defines the interface that all request types must implement.
12739
type RequestObject interface {
12840
// Bind binds query/path parameters to the struct.
@@ -248,8 +160,7 @@ func HandleJSON[Req RequestObject, Resp any](w http.ResponseWriter, r *http.Requ
248160
}
249161

250162
w.Header().Set("Content-Type", "application/json")
251-
ctx := setHTTPReqResp(r.Context(), r, w)
252-
resp := h(ctx, req)
163+
resp := h(r.Context(), req)
253164

254165
if err := jsonflow.MarshalWrite(w, resp); err != nil {
255166
ErrorHandler(r, w, err)
@@ -409,8 +320,7 @@ func HandleStream[Req RequestObject, Resp *Event[T], T any](w http.ResponseWrite
409320
w.Header().Set("Cache-Control", "no-cache")
410321
w.Header().Set("Connection", "keep-alive")
411322

412-
ctx := setHTTPReqResp(r.Context(), r, w)
413-
h(ctx, req, responses)
323+
h(r.Context(), req, responses)
414324
close(responses)
415325
<-done
416326
}

httpsvr/httpsvr_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,28 +133,30 @@ func (s *HelloServerImpl) Stream(ctx context.Context, req *HelloRequest, resp ch
133133
}
134134

135135
// Routers returns a list of HTTP routers for the service.
136-
func Routers(server HelloServer) []httpsvr.Router {
136+
func Routers(server HelloServer, fn httpsvr.NewRequestContext) []httpsvr.Router {
137137
return []httpsvr.Router{
138138
{
139139
Method: "GET",
140140
Pattern: "/v1/hello",
141141
Handler: func(w http.ResponseWriter, r *http.Request) {
142-
httpsvr.HandleJSON(w, r, NewHelloRequest(), server.Hello)
142+
ctx := httpsvr.WithRequestContext(r.Context(), fn(r, w))
143+
httpsvr.HandleJSON(w, r.WithContext(ctx), NewHelloRequest(), server.Hello)
143144
},
144145
},
145146
{
146147
Method: "GET",
147148
Pattern: "/v1/stream",
148149
Handler: func(w http.ResponseWriter, r *http.Request) {
149-
httpsvr.HandleStream(w, r, NewHelloRequest(), server.Stream)
150+
ctx := httpsvr.WithRequestContext(r.Context(), fn(r, w))
151+
httpsvr.HandleStream(w, r.WithContext(ctx), NewHelloRequest(), server.Stream)
150152
},
151153
},
152154
}
153155
}
154156

155157
func TestHello(t *testing.T) {
156158
svr := httpsvr.NewSimpleServer(":9191")
157-
for _, r := range Routers(&HelloServerImpl{}) {
159+
for _, r := range Routers(&HelloServerImpl{}, httpsvr.NewSimpleContext) {
158160
svr.Route(r)
159161
}
160162
go func() {
@@ -177,7 +179,7 @@ func TestHello(t *testing.T) {
177179

178180
func TestStream(t *testing.T) {
179181
svr := httpsvr.NewSimpleServer(":9191")
180-
for _, r := range Routers(&HelloServerImpl{}) {
182+
for _, r := range Routers(&HelloServerImpl{}, httpsvr.NewSimpleContext) {
181183
svr.Route(r)
182184
}
183185
go func() {

httpsvr/server.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2025 The Go-Spring Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package httpsvr
18+
19+
import (
20+
"context"
21+
"net/http"
22+
"strings"
23+
)
24+
25+
// RequestContext abstracts an HTTP request lifecycle context.
26+
// It provides unified access to the request, response writer.
27+
type RequestContext interface {
28+
// Request returns the underlying *http.Request.
29+
Request() *http.Request
30+
31+
// ResponseWriter returns the underlying http.ResponseWriter.
32+
ResponseWriter() http.ResponseWriter
33+
34+
// PathValue returns the value of the named path parameter.
35+
PathValue(name string) string
36+
}
37+
38+
type ctxKeyType struct{}
39+
40+
var ctxKey ctxKeyType
41+
42+
// GetRequestContext retrieves the RequestContext stored in ctx.
43+
// It returns nil if no RequestContext is present.
44+
func GetRequestContext(ctx context.Context) RequestContext {
45+
rc, _ := ctx.Value(&ctxKey).(RequestContext)
46+
return rc
47+
}
48+
49+
// WithRequestContext returns a new context derived from ctx
50+
// that carries the given RequestContext.
51+
func WithRequestContext(ctx context.Context, c RequestContext) context.Context {
52+
return context.WithValue(ctx, &ctxKey, c)
53+
}
54+
55+
// SimpleContext is a minimal RequestContext implementation
56+
// backed directly by http.Request and http.ResponseWriter.
57+
type SimpleContext struct {
58+
r *http.Request
59+
w http.ResponseWriter
60+
}
61+
62+
// NewRequestContext defines a factory function used to create
63+
// a RequestContext from an HTTP request and response writer.
64+
type NewRequestContext func(r *http.Request, w http.ResponseWriter) RequestContext
65+
66+
// NewSimpleContext creates a SimpleContext instance and returns it
67+
// as a RequestContext.
68+
func NewSimpleContext(r *http.Request, w http.ResponseWriter) RequestContext {
69+
return &SimpleContext{r: r, w: w}
70+
}
71+
72+
// Request returns the underlying *http.Request.
73+
func (c *SimpleContext) Request() *http.Request {
74+
return c.r
75+
}
76+
77+
// ResponseWriter returns the underlying http.ResponseWriter.
78+
func (c *SimpleContext) ResponseWriter() http.ResponseWriter {
79+
return c.w
80+
}
81+
82+
// PathValue returns the value of the named path parameter.
83+
func (c *SimpleContext) PathValue(name string) string {
84+
return c.r.PathValue(name)
85+
}
86+
87+
// Router describes a single HTTP route definition,
88+
// including method, URL pattern, and handler.
89+
type Router struct {
90+
Method string
91+
Pattern string
92+
Handler http.HandlerFunc
93+
}
94+
95+
// Server defines the minimal contract for an HTTP server
96+
// capable of registering routes.
97+
type Server interface {
98+
Route(r Router)
99+
}
100+
101+
// SimpleServer is a lightweight HTTP server implementation
102+
// based on http.Server and http.ServeMux.
103+
type SimpleServer struct {
104+
*http.Server
105+
mux *http.ServeMux
106+
}
107+
108+
// NewSimpleServer creates a SimpleServer bound to the given address.
109+
func NewSimpleServer(addr string) *SimpleServer {
110+
mux := http.NewServeMux()
111+
svr := &http.Server{
112+
Addr: addr,
113+
Handler: mux,
114+
}
115+
return &SimpleServer{Server: svr, mux: mux}
116+
}
117+
118+
// Route registers a route with method-based pattern matching.
119+
// The pattern format follows Go 1.22+ ServeMux conventions,
120+
// for example: "GET /users/{id}".
121+
func (s *SimpleServer) Route(r Router) {
122+
pattern := strings.TrimSpace(r.Method + " " + r.Pattern)
123+
s.mux.HandleFunc(pattern, r.Handler)
124+
}

0 commit comments

Comments
 (0)