From 05bf9d60455cabc625024b8d5786785be19ce661 Mon Sep 17 00:00:00 2001 From: bagas Date: Sun, 25 Jan 2026 18:45:08 +0700 Subject: [PATCH] refactor(header): NewRequest to accept only []byte --- internal/http/header/header_test.go | 119 +--------------------------- internal/http/header/parser.go | 71 ----------------- internal/http/header/request.go | 32 +++++--- 3 files changed, 24 insertions(+), 198 deletions(-) diff --git a/internal/http/header/header_test.go b/internal/http/header/header_test.go index 5207b1c..b3f9228 100644 --- a/internal/http/header/header_test.go +++ b/internal/http/header/header_test.go @@ -1,15 +1,13 @@ package header import ( - "bufio" "bytes" - "io" "testing" "github.com/stretchr/testify/assert" ) -func TestNewRequestFromBytes(t *testing.T) { +func TestNewRequest(t *testing.T) { tests := []struct { name string data []byte @@ -93,94 +91,6 @@ func TestNewRequestFromBytes(t *testing.T) { } } -func TestNewRequestFromReader(t *testing.T) { - tests := []struct { - name string - data []byte - expectErr bool - errContains string - expectEOF bool - expectMethod string - expectPath string - expectVersion string - expectHeaders map[string]string - }{ - { - name: "success", - data: []byte("POST /api HTTP/1.1\r\nContent-Type: application/json\r\n\r\n"), - expectErr: false, - expectMethod: "POST", - expectPath: "/api", - expectVersion: "HTTP/1.1", - expectHeaders: map[string]string{ - "Content-Type": "application/json", - }, - }, - { - name: "read error on start line", - data: []byte{}, - expectErr: true, - expectEOF: true, - }, - { - name: "invalid start line", - data: []byte("INVALID\n\n"), - expectErr: true, - errContains: "invalid start line", - }, - { - name: "read error on headers", - data: []byte("GET / HTTP/1.1\nHost: example.com"), - expectErr: true, - expectEOF: true, - }, - { - name: "multiple colons in header", - data: []byte("GET / HTTP/1.1\r\nX-Custom: value:with:colons\r\n\r\n"), - expectErr: false, - expectMethod: "GET", - expectPath: "/", - expectVersion: "HTTP/1.1", - expectHeaders: map[string]string{ - "X-Custom": "value:with:colons", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - br := bufio.NewReader(bytes.NewReader(tt.data)) - req, err := NewRequest(br) - if tt.expectErr { - assert.Error(t, err) - if tt.expectEOF { - assert.Equal(t, io.EOF, err) - } - if tt.errContains != "" { - assert.Contains(t, err.Error(), tt.errContains) - } - assert.Nil(t, req) - } else { - assert.NoError(t, err) - assert.NotNil(t, req) - assert.Equal(t, tt.expectMethod, req.Method()) - assert.Equal(t, tt.expectPath, req.Path()) - assert.Equal(t, tt.expectVersion, req.Version()) - for k, v := range tt.expectHeaders { - assert.Equal(t, v, req.Value(k)) - } - } - }) - } -} - -func TestNewRequestUnsupportedType(t *testing.T) { - req, err := NewRequest(123) - assert.Error(t, err) - assert.Contains(t, err.Error(), "unsupported type: int") - assert.Nil(t, req) -} - func TestRequestHeaderMethods(t *testing.T) { data := []byte("GET / HTTP/1.1\r\nHost: original\r\n\r\n") req, _ := NewRequest(data) @@ -315,30 +225,3 @@ func TestSetRemainingHeaders(t *testing.T) { }) } } - -func TestParseHeadersFromReaderEdgeCases(t *testing.T) { - tests := []struct { - name string - data []byte - expectHeaders map[string]string - }{ - { - name: "malformed header line", - data: []byte("GET / HTTP/1.1\r\nMalformedLine\r\nK1: V1\r\n\r\n"), - expectHeaders: map[string]string{ - "K1": "V1", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - br := bufio.NewReader(bytes.NewReader(tt.data)) - req, err := parseHeadersFromReader(br) - assert.NoError(t, err) - for k, v := range tt.expectHeaders { - assert.Equal(t, v, req.Value(k)) - } - }) - } -} diff --git a/internal/http/header/parser.go b/internal/http/header/parser.go index 861c49e..9a58d59 100644 --- a/internal/http/header/parser.go +++ b/internal/http/header/parser.go @@ -1,7 +1,6 @@ package header import ( - "bufio" "bytes" "fmt" ) @@ -36,31 +35,6 @@ func setRemainingHeaders(remaining []byte, header interface { } } -func parseHeadersFromBytes(headerData []byte) (RequestHeader, error) { - header := &requestHeader{ - headers: make(map[string]string, 16), - } - - lineEnd := bytes.Index(headerData, []byte("\r\n")) - if lineEnd == -1 { - return nil, fmt.Errorf("invalid request: no CRLF found in start line") - } - - startLine := headerData[:lineEnd] - header.startLine = startLine - var err error - header.method, header.path, header.version, err = parseStartLine(startLine) - if err != nil { - return nil, err - } - - remaining := headerData[lineEnd+2:] - - setRemainingHeaders(remaining, header) - - return header, nil -} - func parseStartLine(startLine []byte) (method, path, version string, err error) { firstSpace := bytes.IndexByte(startLine, ' ') if firstSpace == -1 { @@ -80,51 +54,6 @@ func parseStartLine(startLine []byte) (method, path, version string, err error) return method, path, version, nil } -func parseHeadersFromReader(br *bufio.Reader) (RequestHeader, error) { - header := &requestHeader{ - headers: make(map[string]string, 16), - } - - startLineBytes, err := br.ReadSlice('\n') - if err != nil { - return nil, err - } - - startLineBytes = bytes.TrimRight(startLineBytes, "\r\n") - header.startLine = make([]byte, len(startLineBytes)) - copy(header.startLine, startLineBytes) - - header.method, header.path, header.version, err = parseStartLine(header.startLine) - if err != nil { - return nil, err - } - - for { - lineBytes, err := br.ReadSlice('\n') - if err != nil { - return nil, err - } - - lineBytes = bytes.TrimRight(lineBytes, "\r\n") - - if len(lineBytes) == 0 { - break - } - - colonIdx := bytes.IndexByte(lineBytes, ':') - if colonIdx == -1 { - continue - } - - key := bytes.TrimSpace(lineBytes[:colonIdx]) - value := bytes.TrimSpace(lineBytes[colonIdx+1:]) - - header.headers[string(key)] = string(value) - } - - return header, nil -} - func finalize(startLine []byte, headers map[string]string) []byte { size := len(startLine) + 2 for key, val := range headers { diff --git a/internal/http/header/request.go b/internal/http/header/request.go index 1fbe57a..e35f169 100644 --- a/internal/http/header/request.go +++ b/internal/http/header/request.go @@ -1,19 +1,33 @@ package header import ( - "bufio" + "bytes" "fmt" ) -func NewRequest(r interface{}) (RequestHeader, error) { - switch v := r.(type) { - case []byte: - return parseHeadersFromBytes(v) - case *bufio.Reader: - return parseHeadersFromReader(v) - default: - return nil, fmt.Errorf("unsupported type: %T", r) +func NewRequest(headerData []byte) (RequestHeader, error) { + header := &requestHeader{ + headers: make(map[string]string, 16), } + + lineEnd := bytes.Index(headerData, []byte("\r\n")) + if lineEnd == -1 { + return nil, fmt.Errorf("invalid request: no CRLF found in start line") + } + + startLine := headerData[:lineEnd] + header.startLine = startLine + var err error + header.method, header.path, header.version, err = parseStartLine(startLine) + if err != nil { + return nil, err + } + + remaining := headerData[lineEnd+2:] + + setRemainingHeaders(remaining, header) + + return header, nil } func (req *requestHeader) Value(key string) string {