Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions fasthttpadaptor/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package fasthttpadaptor
import (
"bytes"
"io"
"net"
"net/http"
"net/url"
"strconv"
"strings"

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

return nil
}

// ConvertNetHttpToFastHttp converts an http.Request to a fasthttp.RequestCtx.
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
// The caller is responsible for the lifecycle of the fasthttp.RequestCtx.
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
func ConvertNetHttpRequestToFastHttpRequest(r *http.Request, ctx *fasthttp.RequestCtx) error {
ctx.Request.Header.SetMethod(r.Method)

if r.RequestURI != "" {
ctx.Request.SetRequestURI(r.RequestURI)
} else if r.URL != nil {
ctx.Request.SetRequestURI(r.URL.RequestURI())
Comment thread
aaydin-tr marked this conversation as resolved.
}
Comment thread
aaydin-tr marked this conversation as resolved.

ctx.Request.Header.SetProtocol(r.Proto)
ctx.Request.SetHost(r.Host)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r.Host can be empty in which case it should be r.URL.Host.


for k, values := range r.Header {
for i, v := range values {
if i == 0 {
ctx.Request.Header.Set(k, v)
} else {
ctx.Request.Header.Add(k, v)
}
Comment thread
aaydin-tr marked this conversation as resolved.
}
}
Comment thread
aaydin-tr marked this conversation as resolved.

if r.Body != nil {
body, err := io.ReadAll(r.Body)
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
if err != nil {
return err
}
ctx.Request.SetBody(body)
}

if r.RemoteAddr != "" {
addr := parseRemoteAddr(r.RemoteAddr)
ctx.SetRemoteAddr(addr)
}

Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
return nil
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are missing some properties that aren't being copied right now. For example r.TLS and r.Close, r.TransferEncoding, r.Trailer, and r.URL.Scheme which are all used by net/http.


func parseRemoteAddr(addr string) net.Addr {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return &net.TCPAddr{IP: net.ParseIP(addr)}
}
return &net.TCPAddr{
IP: net.ParseIP(host),
Port: parsePort(port),
}
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
}

func parsePort(port string) int {
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
p, err := strconv.Atoi(port)
if err != nil {
return 0
}
return p
}
252 changes: 252 additions & 0 deletions fasthttpadaptor/request_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package fasthttpadaptor

import (
"bytes"
"errors"
"io"
"net/http"
"net/url"
"testing"

"github.com/valyala/fasthttp"
Expand All @@ -27,3 +31,251 @@ func BenchmarkConvertRequest(b *testing.B) {
_ = ConvertRequest(ctx, &httpReq, true)
}
}

func BenchmarkConvertNetHttpRequestToFastHttpRequest(b *testing.B) {
var httpReq http.Request = http.Request{
Method: "GET",
RequestURI: "/test",
Host: "test",
Header: http.Header{
"X": []string{"test"},
"Y": []string{"test"},
},
}
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated

ctx := &fasthttp.RequestCtx{}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ConvertNetHttpRequestToFastHttpRequest(&httpReq, ctx)
}
Comment thread
aaydin-tr marked this conversation as resolved.
}

// errReader is a reader that always returns an error.
type errReader struct{}

func (errReader) Read([]byte) (int, error) {
return 0, errors.New("read error")
}

func TestConvertNetHttpRequestToFastHttpRequest(t *testing.T) {
t.Parallel()

t.Run("basic conversion", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "POST",
RequestURI: "/test/path?query=1",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if string(ctx.Method()) != "POST" {
t.Errorf("expected method POST, got %s", ctx.Method())
}
if string(ctx.RequestURI()) != "/test/path?query=1" {
t.Errorf("expected URI /test/path?query=1, got %s", ctx.RequestURI())
}
if string(ctx.Request.Header.Protocol()) != "HTTP/1.1" {
t.Errorf("expected protocol HTTP/1.1, got %s", ctx.Request.Header.Protocol())
}
if string(ctx.Host()) != "example.com" {
t.Errorf("expected host example.com, got %s", ctx.Host())
}
})

t.Run("URL fallback when RequestURI is empty", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "",
URL: &url.URL{
Path: "/fallback/path",
RawQuery: "foo=bar",
},
Proto: "HTTP/1.1",
Host: "fallback.com",
Header: http.Header{},
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if string(ctx.RequestURI()) != "/fallback/path?foo=bar" {
t.Errorf("expected URI /fallback/path?foo=bar, got %s", ctx.RequestURI())
}
})
Comment thread
aaydin-tr marked this conversation as resolved.

t.Run("single header", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{
"X-Custom-Header": []string{"custom-value"},
},
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if string(ctx.Request.Header.Peek("X-Custom-Header")) != "custom-value" {
t.Errorf("expected header value custom-value, got %s", ctx.Request.Header.Peek("X-Custom-Header"))
}
})

t.Run("multiple header values", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{
"Accept": []string{"text/html", "application/json", "text/plain"},
},
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Check all header values are present
var values []string
ctx.Request.Header.VisitAll(func(key, value []byte) {
if string(key) == "Accept" {
values = append(values, string(value))
}
})

if len(values) != 3 {
t.Errorf("expected 3 Accept header values, got %d", len(values))
}
})

t.Run("request body", func(t *testing.T) {
t.Parallel()
bodyContent := []byte("test body content")
httpReq := &http.Request{
Method: "POST",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
Body: io.NopCloser(bytes.NewReader(bodyContent)),
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if !bytes.Equal(ctx.Request.Body(), bodyContent) {
t.Errorf("expected body %q, got %q", bodyContent, ctx.Request.Body())
}
})
Comment thread
aaydin-tr marked this conversation as resolved.

t.Run("nil body", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
Body: nil,
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if len(ctx.Request.Body()) != 0 {
t.Errorf("expected empty body, got %q", ctx.Request.Body())
}
})

t.Run("remote address with port", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
RemoteAddr: "192.168.1.100:8080",
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

remoteAddr := ctx.RemoteAddr().String()
if remoteAddr != "192.168.1.100:8080" {
t.Errorf("expected remote addr 192.168.1.100:8080, got %s", remoteAddr)
}
})

t.Run("remote address without port", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "GET",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
RemoteAddr: "192.168.1.100",
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

remoteAddr := ctx.RemoteAddr().String()
if remoteAddr != "192.168.1.100:0" {
t.Errorf("expected remote addr 192.168.1.100:0, got %s", remoteAddr)
}
})
Comment thread
aaydin-tr marked this conversation as resolved.

t.Run("body read error", func(t *testing.T) {
t.Parallel()
httpReq := &http.Request{
Method: "POST",
RequestURI: "/",
Proto: "HTTP/1.1",
Host: "example.com",
Header: http.Header{},
Body: io.NopCloser(errReader{}),
}

ctx := &fasthttp.RequestCtx{}
err := ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)
if err == nil {
t.Fatal("expected error, got nil")
}
})
}