Files
lneto/http/httpraw/header_test.go
T
Pat Whittingslow 157a8537a2 testing.B.Loop() standardization and StackAsync.Debug improvement (#156)
* testing.B.Loop() standardization and StackAsync.Debug heap debugging improvement

* apply @MDr164 fixes

* @MDr164 great suggestions to prevent panic and print correct mallocs
2026-07-17 11:59:27 -03:00

243 lines
6.5 KiB
Go

package httpraw
import (
"bytes"
"fmt"
"net/http"
"strconv"
"strings"
"testing"
"time"
)
func TestHeaderParseRequest(t *testing.T) {
const (
wantMethod = "GET"
wantURI = "/data/set"
wantMessage = "hello world!"
asRequest = false
asResponse = true
)
req, err := http.NewRequest(wantMethod, wantURI, strings.NewReader(wantMessage))
if err != nil {
t.Fatal(err)
}
var wantCookie http.Cookie
wantCookie.SameSite = http.SameSiteLaxMode
wantCookie.MaxAge = 360000
wantCookie.Name = "key"
wantCookie.Value = "value"
wantCookie.Expires = time.Now().Add(time.Hour)
wantCookie.Domain = "DOM"
wantCookie.HttpOnly = true
wantCookie.Secure = true
wantCookie.Path = "/abc"
req.Header.Set("Cookie", wantCookie.String())
t.Log("valid cookie:", wantCookie.Valid() == nil, wantCookie.String())
var buf bytes.Buffer
req.Write(&buf)
var hdr Header
msg := buf.Bytes()
start := time.Now()
err = hdr.ParseBytes(asRequest, msg)
elapsed := time.Since(start)
if err != nil {
t.Fatal(err)
}
fmt.Printf("%s\nparsed in %s\n\n", msg, elapsed.String())
if string(hdr.Method()) != wantMethod {
t.Errorf("want method %s, got %q", wantMethod, hdr.Method())
}
if !bytes.Equal(hdr.RequestURI(), []byte(wantURI)) {
t.Errorf("want request URI %q, got %q", wantURI, hdr.RequestURI())
}
contentLength, _ := strconv.Atoi(string(hdr.Get("Content-Length")))
if contentLength != len(wantMessage) {
t.Errorf("want Content-Length %d, got %d", len(wantMessage), contentLength)
}
var c Cookie
cookie := hdr.Get("Cookie")
c.Reset(cookie)
err = c.Parse()
if err != nil {
t.Error(err)
}
key := string(c.Name())
if key != wantCookie.Name {
t.Errorf("want cookie key %q, got %q", wantCookie.Name, key)
}
value := string(c.Value())
if value != wantCookie.Value {
t.Errorf("want cookie key %q, got %q", wantCookie.Value, value)
}
domain := string(c.Get("Domain"))
if domain != wantCookie.Domain {
t.Errorf("want domain %q, got %q", wantCookie.Domain, domain)
}
httpOnly := c.HasKeyOrSingleValue("HttpOnly")
if httpOnly != wantCookie.HttpOnly {
t.Errorf("want cookie HttpOnly %v, got %v", wantCookie.HttpOnly, httpOnly)
}
secure := c.HasKeyOrSingleValue("Secure")
if secure != wantCookie.Secure {
t.Errorf("want cookie HttpOnly %v, got %v", wantCookie.Secure, secure)
}
samesite := string(c.Get("SameSite"))
if samesite != strSameSite(wantCookie.SameSite) {
t.Errorf("want cookie SameSite %v, got %v", strSameSite(wantCookie.SameSite), samesite)
}
body, err := hdr.Body()
if err != nil {
t.Error(err)
}
if string(body) != wantMessage {
t.Errorf("want body message %q, got %q", wantMessage, body)
}
cookieStr := string(c.AppendKeyValues(nil))
if wantCookie.String() != cookieStr {
t.Errorf("want full cookie representation\n%qgot:\n%q", wantCookie.String(), cookieStr)
}
data, _ := hdr.AppendRequest(nil)
fmt.Printf("%s", data)
}
func BenchmarkParseBytes(b *testing.B) {
b.StopTimer()
const (
wantMethod = "GET"
wantURI = "/data/set"
wantMessage = "hello world!"
asRequest = false
)
req, _ := http.NewRequest(wantMethod, wantURI, strings.NewReader(wantMessage))
var rawBuf bytes.Buffer
req.Write(&rawBuf)
data := rawBuf.Bytes()
// hdr is declared outside the loop so that ParseBytes can reuse the
// backing arrays on Reset (headers slice and data buffer) without
// allocating on every iteration. Declaring it inside the loop causes
// two allocs per iteration: one for the headers slice (make in reset)
// and one for the data buffer (append in readFromBytes).
var hdr Header
b.StartTimer()
for b.Loop() {
err := hdr.ParseBytes(asRequest, data)
if err != nil {
b.Fatal(err)
}
_, err = hdr.Body()
if err != nil {
b.Fatal(err)
}
}
}
func strSameSite(mode http.SameSite) string {
switch mode {
case http.SameSiteLaxMode:
return "Lax"
case http.SameSiteDefaultMode:
return ""
case http.SameSiteStrictMode:
return "Strict"
case http.SameSiteNoneMode:
return "None"
default:
panic("invalid same site")
}
}
func TestHeaderNormalizeKey(t *testing.T) {
var tests = []struct {
key string
wantnorm string
}{
{key: "", wantnorm: ""},
{key: "a-a-a", wantnorm: "A-A-A"},
{key: "a-a-a-", wantnorm: "A-A-A-"},
{key: "-", wantnorm: "-"},
{key: "CONTENT", wantnorm: "Content"},
{key: "cONTENT", wantnorm: "Content"},
{key: "Content-Length", wantnorm: "Content-Length"},
{key: "Content-length", wantnorm: "Content-Length"},
{key: "content-length", wantnorm: "Content-Length"},
{key: "conTent-lENgth", wantnorm: "Content-Length"},
{key: "conTent-lENgth-", wantnorm: "Content-Length-"},
}
for _, test := range tests {
gotKey := []byte(test.key)
modified := NormalizeHeaderKey(gotKey)
if string(gotKey) != test.wantnorm {
t.Errorf("mismatch want %q got %q", test.wantnorm, gotKey)
}
wantMod := string(gotKey) != test.key
if wantMod != modified {
t.Errorf("mismatch want mod=%v, got mod=%v", wantMod, modified)
}
}
}
func TestCopyNormalizedHeaderValue(t *testing.T) {
var tests = []struct {
value string
wantnorm string
}{
{value: "abc\r\n\tdef\r\n\tghi", wantnorm: "abc def ghi"},
{value: "abc\r\n\tabc", wantnorm: "abc abc"},
{value: "abc\n\tdef\n\tghi", wantnorm: "abc def ghi"},
{value: "abc\n\tdef\n\tghi\n\t", wantnorm: "abc def ghi "},
{value: "abc\n\tdef\n\tghi\r\n\t", wantnorm: "abc def ghi "},
{value: "", wantnorm: ""},
{value: "abc", wantnorm: "abc"},
}
dst := make([]byte, 256)
for _, test := range tests {
value := []byte(test.value)
n, modified := CopyNormalizedHeaderValue(dst[:len(value)], value)
got := dst[:n]
if string(got) != test.wantnorm {
t.Errorf("mismatch want %q got %q", test.wantnorm, got)
}
wantMod := string(got) != test.value
if wantMod != modified {
t.Errorf("mismatch want mod=%v, got mod=%v", wantMod, modified)
}
}
}
func TestHeaderSetOverwrite(t *testing.T) {
var h Header
h.Reset(nil)
h.SetMethod("GET")
h.SetRequestURI("/")
h.SetProtocol("HTTP/1.1")
h.Set("Host", "first.example.com")
h.Set("Host", "second.example.com")
got := string(h.Get("Host"))
if got != "second.example.com" {
t.Errorf("want Host %q, got %q", "second.example.com", got)
}
req, err := h.AppendRequest(nil)
if err != nil {
t.Fatal(err)
}
if n := strings.Count(string(req), "Host:"); n != 1 {
t.Errorf("want 1 Host field in request, got %d:\n%s", n, req)
}
}
func TestHeaderSetBytesEmptyValue(t *testing.T) {
var h Header
h.Reset(nil)
h.SetBytes("X-Empty", nil)
if got := h.Get("X-Empty"); len(got) != 0 {
t.Errorf("want empty value, got %q", got)
}
}