Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
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
56 changes: 56 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,57 @@ 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) {
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
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 {
ctx.Request.SetBodyStream(r.Body, int(r.ContentLength))
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
}

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

Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
}
Comment thread
aaydin-tr marked this conversation as resolved.
Outdated
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
}
232 changes: 232 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,231 @@ 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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

// 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)),
ContentLength: int64(len(bodyContent)),
}

ctx := &fasthttp.RequestCtx{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

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{}),
ContentLength: 10,
}

ctx := &fasthttp.RequestCtx{}
ConvertNetHttpRequestToFastHttpRequest(httpReq, ctx)

_, err := io.ReadAll(ctx.RequestBodyStream())
if err == nil {
t.Fatal("expected error when reading body stream, got nil")
}
})
}