mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
157a8537a2
* testing.B.Loop() standardization and StackAsync.Debug heap debugging improvement * apply @MDr164 fixes * @MDr164 great suggestions to prevent panic and print correct mallocs
243 lines
6.5 KiB
Go
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)
|
|
}
|
|
}
|