1 Commits

Author SHA1 Message Date
7716eb7f29 perf: optimize header parsing with zero-copy ReadSlice
All checks were successful
renovate / renovate (push) Successful in 35s
Docker Build and Push / build-and-push-branches (push) Successful in 4m39s
Docker Build and Push / build-and-push-tags (push) Successful in 4m52s
- Replace ReadString with ReadSlice to eliminate allocations
- Use bytes operations instead of strings
- Add FromBytes variant for in-memory parsing
2025-12-31 23:18:53 +07:00
2 changed files with 110 additions and 23 deletions

View File

@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"strings"
) )
type HeaderManager interface { type HeaderManager interface {
@@ -44,43 +43,132 @@ type requestHeaderFactory struct {
headers map[string]string headers map[string]string
} }
func NewRequestHeaderFactory(br *bufio.Reader) (RequestHeaderManager, error) { func NewRequestHeaderFactory(r interface{}) (RequestHeaderManager, 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 parseHeadersFromBytes(headerData []byte) (RequestHeaderManager, error) {
header := &requestHeaderFactory{ header := &requestHeaderFactory{
headers: make(map[string]string), headers: make(map[string]string, 16),
} }
startLine, err := br.ReadString('\n') lineEnd := bytes.IndexByte(headerData, '\n')
if err != nil { if lineEnd == -1 {
return nil, err return nil, fmt.Errorf("invalid request: no newline found")
} }
startLine = strings.TrimRight(startLine, "\r\n")
header.startLine = []byte(startLine)
parts := strings.Split(startLine, " ") startLine := bytes.TrimRight(headerData[:lineEnd], "\r\n")
header.startLine = make([]byte, len(startLine))
copy(header.startLine, startLine)
parts := bytes.Split(startLine, []byte{' '})
if len(parts) < 3 { if len(parts) < 3 {
return nil, fmt.Errorf("invalid request line") return nil, fmt.Errorf("invalid request line")
} }
header.method = parts[0] header.method = string(parts[0])
header.path = parts[1] header.path = string(parts[1])
header.version = parts[2] header.version = string(parts[2])
for { remaining := headerData[lineEnd+1:]
line, err := br.ReadString('\n')
if err != nil { for len(remaining) > 0 {
return nil, err lineEnd = bytes.IndexByte(remaining, '\n')
if lineEnd == -1 {
lineEnd = len(remaining)
} }
line = strings.TrimRight(line, "\r\n")
if line == "" { line := bytes.TrimRight(remaining[:lineEnd], "\r\n")
if len(line) == 0 {
break break
} }
kv := strings.SplitN(line, ":", 2) colonIdx := bytes.IndexByte(line, ':')
if len(kv) != 2 { if colonIdx != -1 {
key := bytes.TrimSpace(line[:colonIdx])
value := bytes.TrimSpace(line[colonIdx+1:])
header.headers[string(key)] = string(value)
}
if lineEnd == len(remaining) {
break
}
remaining = remaining[lineEnd+1:]
}
return header, nil
}
func parseHeadersFromReader(br *bufio.Reader) (RequestHeaderManager, error) {
header := &requestHeaderFactory{
headers: make(map[string]string, 16),
}
startLineBytes, err := br.ReadSlice('\n')
if err != nil {
if err == bufio.ErrBufferFull {
var startLine string
startLine, err = br.ReadString('\n')
if err != nil {
return nil, err
}
startLineBytes = []byte(startLine)
} else {
return nil, err
}
}
startLineBytes = bytes.TrimRight(startLineBytes, "\r\n")
header.startLine = make([]byte, len(startLineBytes))
copy(header.startLine, startLineBytes)
parts := bytes.Split(startLineBytes, []byte{' '})
if len(parts) < 3 {
return nil, fmt.Errorf("invalid request line")
}
header.method = string(parts[0])
header.path = string(parts[1])
header.version = string(parts[2])
for {
lineBytes, err := br.ReadSlice('\n')
if err != nil {
if err == bufio.ErrBufferFull {
var line string
line, err = br.ReadString('\n')
if err != nil {
return nil, err
}
lineBytes = []byte(line)
} else {
return nil, err
}
}
lineBytes = bytes.TrimRight(lineBytes, "\r\n")
if len(lineBytes) == 0 {
break
}
colonIdx := bytes.IndexByte(lineBytes, ':')
if colonIdx == -1 {
continue continue
} }
header.headers[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
key := bytes.TrimSpace(lineBytes[:colonIdx])
value := bytes.TrimSpace(lineBytes[colonIdx+1:])
header.headers[string(key)] = string(value)
} }
return header, nil return header, nil

View File

@@ -99,8 +99,7 @@ func (cw *customWriter) Read(p []byte) (int, error) {
} }
} }
headerReader := bufio.NewReader(bytes.NewReader(header)) reqhf, err := NewRequestHeaderFactory(header)
reqhf, err := NewRequestHeaderFactory(headerReader)
if err != nil { if err != nil {
return 0, err return 0, err
} }