Skip to content

Commit 5efed7c

Browse files
committed
feat: add ConvertNetHttpRequestToFastHttpRequest adaptor function
1 parent c2db561 commit 5efed7c

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed

fasthttpadaptor/request.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package fasthttpadaptor
33
import (
44
"bytes"
55
"io"
6+
"net"
67
"net/http"
78
"net/url"
9+
"strconv"
810
"strings"
911

1012
"github.com/valyala/fasthttp"
@@ -68,3 +70,62 @@ func ConvertRequest(ctx *fasthttp.RequestCtx, r *http.Request, forServer bool) e
6870

6971
return nil
7072
}
73+
74+
// ConvertNetHttpToFastHttp converts an http.Request to a fasthttp.RequestCtx.
75+
// The caller is responsible for the lifecycle of the fasthttp.RequestCtx.
76+
func ConvertNetHttpRequestToFastHttpRequest(r *http.Request, ctx *fasthttp.RequestCtx) error {
77+
ctx.Request.Header.SetMethod(r.Method)
78+
79+
if r.RequestURI != "" {
80+
ctx.Request.SetRequestURI(r.RequestURI)
81+
} else if r.URL != nil {
82+
ctx.Request.SetRequestURI(r.URL.RequestURI())
83+
}
84+
85+
ctx.Request.Header.SetProtocol(r.Proto)
86+
ctx.Request.SetHost(r.Host)
87+
88+
for k, values := range r.Header {
89+
for i, v := range values {
90+
if i == 0 {
91+
ctx.Request.Header.Set(k, v)
92+
} else {
93+
ctx.Request.Header.Add(k, v)
94+
}
95+
}
96+
}
97+
98+
if r.Body != nil {
99+
body, err := io.ReadAll(r.Body)
100+
if err != nil {
101+
return err
102+
}
103+
ctx.Request.SetBody(body)
104+
}
105+
106+
if r.RemoteAddr != "" {
107+
addr := parseRemoteAddr(r.RemoteAddr)
108+
ctx.SetRemoteAddr(addr)
109+
}
110+
111+
return nil
112+
}
113+
114+
func parseRemoteAddr(addr string) net.Addr {
115+
host, port, err := net.SplitHostPort(addr)
116+
if err != nil {
117+
return &net.TCPAddr{IP: net.ParseIP(addr)}
118+
}
119+
return &net.TCPAddr{
120+
IP: net.ParseIP(host),
121+
Port: parsePort(port),
122+
}
123+
}
124+
125+
func parsePort(port string) int {
126+
p, err := strconv.Atoi(port)
127+
if err != nil {
128+
return 0
129+
}
130+
return p
131+
}

fasthttpadaptor/request_test.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package fasthttpadaptor
22

33
import (
4+
"bytes"
5+
"errors"
6+
"io"
47
"net/http"
8+
"net/url"
59
"testing"
610

711
"github.com/valyala/fasthttp"
@@ -27,3 +31,251 @@ func BenchmarkConvertRequest(b *testing.B) {
2731
_ = ConvertRequest(ctx, &httpReq, true)
2832
}
2933
}
34+
35+
func BenchmarkConvertNetHttpRequestToFastHttpRequest(b *testing.B) {
36+
var httpReq http.Request = http.Request{
37+
Method: "GET",
38+
RequestURI: "/test",
39+
Host: "test",
40+
Header: http.Header{
41+
"X": []string{"test"},
42+
"Y": []string{"test"},
43+
},
44+
}
45+
46+
ctx := &fasthttp.RequestCtx{}
47+
48+
b.ResetTimer()
49+
for i := 0; i < b.N; i++ {
50+
_ = ConvertNetHttpRequestToFastHttpRequest(&httpReq, ctx)
51+
}
52+
}
53+
54+
// errReader is a reader that always returns an error.
55+
type errReader struct{}
56+
57+
func (errReader) Read([]byte) (int, error) {
58+
return 0, errors.New("read error")
59+
}
60+
61+
func TestConvertNetHttpRequestToFastHttpRequest(t *testing.T) {
62+
t.Parallel()
63+
64+
t.Run("basic conversion", func(t *testing.T) {
65+
t.Parallel()
66+
httpReq := &http.Request{
67+
Method: "POST",
68+
RequestURI: "/test/path?query=1",
69+
Proto: "HTTP/1.1",
70+
Host: "example.com",
71+
Header: http.Header{},
72+
}
73+
74+
ctx := &fasthttp.RequestCtx{}
75+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
76+
if err != nil {
77+
t.Fatalf("unexpected error: %v", err)
78+
}
79+
80+
if string(ctx.Method()) != "POST" {
81+
t.Errorf("expected method POST, got %s", ctx.Method())
82+
}
83+
if string(ctx.RequestURI()) != "/test/path?query=1" {
84+
t.Errorf("expected URI /test/path?query=1, got %s", ctx.RequestURI())
85+
}
86+
if string(ctx.Request.Header.Protocol()) != "HTTP/1.1" {
87+
t.Errorf("expected protocol HTTP/1.1, got %s", ctx.Request.Header.Protocol())
88+
}
89+
if string(ctx.Host()) != "example.com" {
90+
t.Errorf("expected host example.com, got %s", ctx.Host())
91+
}
92+
})
93+
94+
t.Run("URL fallback when RequestURI is empty", func(t *testing.T) {
95+
t.Parallel()
96+
httpReq := &http.Request{
97+
Method: "GET",
98+
RequestURI: "",
99+
URL: &url.URL{
100+
Path: "/fallback/path",
101+
RawQuery: "foo=bar",
102+
},
103+
Proto: "HTTP/1.1",
104+
Host: "fallback.com",
105+
Header: http.Header{},
106+
}
107+
108+
ctx := &fasthttp.RequestCtx{}
109+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
110+
if err != nil {
111+
t.Fatalf("unexpected error: %v", err)
112+
}
113+
114+
if string(ctx.RequestURI()) != "/fallback/path?foo=bar" {
115+
t.Errorf("expected URI /fallback/path?foo=bar, got %s", ctx.RequestURI())
116+
}
117+
})
118+
119+
t.Run("single header", func(t *testing.T) {
120+
t.Parallel()
121+
httpReq := &http.Request{
122+
Method: "GET",
123+
RequestURI: "/",
124+
Proto: "HTTP/1.1",
125+
Host: "example.com",
126+
Header: http.Header{
127+
"X-Custom-Header": []string{"custom-value"},
128+
},
129+
}
130+
131+
ctx := &fasthttp.RequestCtx{}
132+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
133+
if err != nil {
134+
t.Fatalf("unexpected error: %v", err)
135+
}
136+
137+
if string(ctx.Request.Header.Peek("X-Custom-Header")) != "custom-value" {
138+
t.Errorf("expected header value custom-value, got %s", ctx.Request.Header.Peek("X-Custom-Header"))
139+
}
140+
})
141+
142+
t.Run("multiple header values", func(t *testing.T) {
143+
t.Parallel()
144+
httpReq := &http.Request{
145+
Method: "GET",
146+
RequestURI: "/",
147+
Proto: "HTTP/1.1",
148+
Host: "example.com",
149+
Header: http.Header{
150+
"Accept": []string{"text/html", "application/json", "text/plain"},
151+
},
152+
}
153+
154+
ctx := &fasthttp.RequestCtx{}
155+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
156+
if err != nil {
157+
t.Fatalf("unexpected error: %v", err)
158+
}
159+
160+
// Check all header values are present
161+
var values []string
162+
ctx.Request.Header.VisitAll(func(key, value []byte) {
163+
if string(key) == "Accept" {
164+
values = append(values, string(value))
165+
}
166+
})
167+
168+
if len(values) != 3 {
169+
t.Errorf("expected 3 Accept header values, got %d", len(values))
170+
}
171+
})
172+
173+
t.Run("request body", func(t *testing.T) {
174+
t.Parallel()
175+
bodyContent := []byte("test body content")
176+
httpReq := &http.Request{
177+
Method: "POST",
178+
RequestURI: "/",
179+
Proto: "HTTP/1.1",
180+
Host: "example.com",
181+
Header: http.Header{},
182+
Body: io.NopCloser(bytes.NewReader(bodyContent)),
183+
}
184+
185+
ctx := &fasthttp.RequestCtx{}
186+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
187+
if err != nil {
188+
t.Fatalf("unexpected error: %v", err)
189+
}
190+
191+
if !bytes.Equal(ctx.Request.Body(), bodyContent) {
192+
t.Errorf("expected body %q, got %q", bodyContent, ctx.Request.Body())
193+
}
194+
})
195+
196+
t.Run("nil body", func(t *testing.T) {
197+
t.Parallel()
198+
httpReq := &http.Request{
199+
Method: "GET",
200+
RequestURI: "/",
201+
Proto: "HTTP/1.1",
202+
Host: "example.com",
203+
Header: http.Header{},
204+
Body: nil,
205+
}
206+
207+
ctx := &fasthttp.RequestCtx{}
208+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
209+
if err != nil {
210+
t.Fatalf("unexpected error: %v", err)
211+
}
212+
213+
if len(ctx.Request.Body()) != 0 {
214+
t.Errorf("expected empty body, got %q", ctx.Request.Body())
215+
}
216+
})
217+
218+
t.Run("remote address with port", func(t *testing.T) {
219+
t.Parallel()
220+
httpReq := &http.Request{
221+
Method: "GET",
222+
RequestURI: "/",
223+
Proto: "HTTP/1.1",
224+
Host: "example.com",
225+
Header: http.Header{},
226+
RemoteAddr: "192.168.1.100:8080",
227+
}
228+
229+
ctx := &fasthttp.RequestCtx{}
230+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
231+
if err != nil {
232+
t.Fatalf("unexpected error: %v", err)
233+
}
234+
235+
remoteAddr := ctx.RemoteAddr().String()
236+
if remoteAddr != "192.168.1.100:8080" {
237+
t.Errorf("expected remote addr 192.168.1.100:8080, got %s", remoteAddr)
238+
}
239+
})
240+
241+
t.Run("remote address without port", func(t *testing.T) {
242+
t.Parallel()
243+
httpReq := &http.Request{
244+
Method: "GET",
245+
RequestURI: "/",
246+
Proto: "HTTP/1.1",
247+
Host: "example.com",
248+
Header: http.Header{},
249+
RemoteAddr: "192.168.1.100",
250+
}
251+
252+
ctx := &fasthttp.RequestCtx{}
253+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
254+
if err != nil {
255+
t.Fatalf("unexpected error: %v", err)
256+
}
257+
258+
remoteAddr := ctx.RemoteAddr().String()
259+
if remoteAddr != "192.168.1.100:0" {
260+
t.Errorf("expected remote addr 192.168.1.100:0, got %s", remoteAddr)
261+
}
262+
})
263+
264+
t.Run("body read error", func(t *testing.T) {
265+
t.Parallel()
266+
httpReq := &http.Request{
267+
Method: "POST",
268+
RequestURI: "/",
269+
Proto: "HTTP/1.1",
270+
Host: "example.com",
271+
Header: http.Header{},
272+
Body: io.NopCloser(errReader{}),
273+
}
274+
275+
ctx := &fasthttp.RequestCtx{}
276+
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
277+
if err == nil {
278+
t.Fatal("expected error, got nil")
279+
}
280+
})
281+
}

0 commit comments

Comments
 (0)