mirror of
https://github.com/soypat/lneto.git
synced 2026-08-08 17:03:40 +00:00
5c54030f19
* add http/httphi * begin adding httphi tests * claude found neat bugs * add low level Handle function and more tests * more tests, run go generate * add Hijacker-like functionality * improve locking and acquisition of Exchanges in reconfiguring * several bugfixes, add internal.IntLen, round up http-linux example with new router API * small nit * add benchmarks * add query handling * remove ForEach pattern, allocates in TinyGo * massive documentation push and code reordering in files * Router.Handle returns error after being torn down * run go fix * rework Mux interface to receive a string request path * add MethodFrom * minor doc nit * fail on incomplete staging * add raw buffer access * add streaming API distinct from Exchange * begin adding multipart form logic * finish rounding up multipart form parsing * remove status type * first Multipart approach * begin adding readMultiPart * add Exchange.ReadMultiparts reimagining of clanker slop * ai insists with backoffs * simplify clanker slop * apply go fix * add a pattern argument to Mux * explicit header key/value alloc and add ExchangeConfig * fix tests after excplicit header alloc change * fix examples * run go fix * expose rawsock as experimental package (will use for external benchmarks) * remove backoff from form parsing * @MDr164 suggestions get potential fixes * apply go fix * add examples * add README.md * fix rawsock tinygo implementation * apply @MDr164 various fixes * update documentation on ContentLength methods and fix bug in Form reset on empty body * fix tests * add fuzz tests * run go fix * io.ErrNoProgress on parsing form spin * run go fix * remove backoff assumption from Router * httphi.Handle rejects unsupported protocols * go format router.go * add kvbuffer * rewrite Cookie with KVBuffer * mid refactor of KVBuffer into Header * work on KVBuffer exhausted semantics * add Go's ServeMux Request.PathValue access semantics to Exchange, Mux and MuxSlice * add PathValue example * document all the things; improve req Query semantics; add Form.EnableBufferGrowth * unexport kvBuffer * add Exchange.PathValueAppend * use stdlib in example instead of rawsock * remove rawsock from http example * add darwin arch rawsock * fix example * rename Router.TeardownGoroutines to Shutdown matching http.Server.Shutdown * rename types and identifiers * @MDr164 Content-Type and Transfer-Encoding bug catches
135 lines
3.5 KiB
Go
135 lines
3.5 KiB
Go
package httphi
|
|
|
|
import (
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/soypat/lneto/http/httpraw"
|
|
"github.com/soypat/lneto/internal"
|
|
)
|
|
|
|
// benchConn replays a fixed request and discards the response. It allocates
|
|
// nothing itself so benchmark alloc counts belong to the package under test.
|
|
type benchConn struct {
|
|
request string
|
|
read int
|
|
written int
|
|
}
|
|
|
|
func (c *benchConn) rewind() { c.read, c.written = 0, 0 }
|
|
|
|
func (c *benchConn) Read(b []byte) (int, error) {
|
|
if c.read >= len(c.request) {
|
|
return 0, io.EOF
|
|
}
|
|
n := copy(b, c.request[c.read:])
|
|
c.read += n
|
|
return n, nil
|
|
}
|
|
|
|
func (c *benchConn) Write(b []byte) (int, error) {
|
|
c.written += len(b)
|
|
return len(b), nil
|
|
}
|
|
|
|
func (c *benchConn) Close() error { return nil }
|
|
|
|
// benchBody is package level: converting a string literal to []byte inside the
|
|
// handler would allocate on every request and hide the router's own cost.
|
|
var benchBody = []byte("hello world")
|
|
|
|
func benchExchange(b *testing.B, conn conn) *Exchange {
|
|
b.Helper()
|
|
const bufferSize = 1024
|
|
const numHeaderCap = 2
|
|
exch := new(Exchange)
|
|
exch.Configure(ExchangeConfig{
|
|
RawBuf: make([]byte, 2*bufferSize),
|
|
RequestBufferLim: bufferSize,
|
|
NumHeaderKVCap: numHeaderCap,
|
|
})
|
|
if !exch.Acquire(conn) {
|
|
b.Fatal("fresh exchange failed to acquire connection")
|
|
}
|
|
return exch
|
|
}
|
|
|
|
// BenchmarkHandle measures a whole exchange: read, parse, mux and respond.
|
|
func BenchmarkHandle(b *testing.B) {
|
|
expect := []byte("123")
|
|
buf := make([]byte, 64)
|
|
for _, bb := range []struct {
|
|
name string
|
|
request string
|
|
handler HandlerFunc
|
|
}{
|
|
{
|
|
name: "GETWithHeadersAndQuery",
|
|
request: "GET /?abc=123 HTTP/1.1\r\nHost: tinygo.org\r\nUser-Agent: bench\r\nAccept: */*\r\nConnection: close\r\n\r\n",
|
|
handler: func(ex *Exchange) {
|
|
ex.StageHeader("Content-Type", "text/plain")
|
|
ex.StageHeaderInt("Content-Length", int64(len(benchBody)), 10)
|
|
data, present := ex.RequestQueryAppend(buf[:0], "abc", true)
|
|
if !present || !internal.BytesEqual(data, expect) {
|
|
panic("invalid result")
|
|
}
|
|
ex.WriteBody(benchBody)
|
|
},
|
|
},
|
|
{
|
|
name: "NotFound",
|
|
request: "GET /nowhere HTTP/1.1\r\nHost: tinygo.org\r\n\r\n",
|
|
handler: nil, // Unregistered: exercises the 404 path.
|
|
},
|
|
} {
|
|
b.Run(bb.name, func(b *testing.B) {
|
|
var mux MuxSlice
|
|
if bb.handler != nil {
|
|
mux.Handle("GET /", bb.handler)
|
|
}
|
|
conn := &benchConn{request: bb.request}
|
|
exch := benchExchange(b, conn)
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(bb.request)))
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
conn.rewind()
|
|
exch.Release()
|
|
exch.Acquire(conn)
|
|
Handle(exch, &mux, nopBackoff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// benchForm is package level so the Form's pair slice is reused across requests,
|
|
// as a real handler holding one per goroutine would.
|
|
var benchForm httpraw.Form
|
|
|
|
// BenchmarkRequestParseForm measures reading and parsing a urlencoded body into
|
|
// a buffer the caller owns. Nothing on the path may allocate.
|
|
func BenchmarkRequestParseForm(b *testing.B) {
|
|
const request = "POST /f HTTP/1.1\r\nHost: tinygo.org\r\n" +
|
|
"Content-Type: application/x-www-form-urlencoded\r\nContent-Length: 27\r\n\r\n" +
|
|
"user=gopher&msg=hello+world"
|
|
buf := make([]byte, 64)
|
|
var mux MuxSlice
|
|
mux.Handle("POST /f", func(ex *Exchange) {
|
|
err := ex.RequestParseForm(&benchForm, buf)
|
|
if err != nil || benchForm.Len() != 2 {
|
|
panic("invalid result")
|
|
}
|
|
})
|
|
conn := &benchConn{request: request}
|
|
exch := benchExchange(b, conn)
|
|
b.ReportAllocs()
|
|
b.SetBytes(int64(len(request)))
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
conn.rewind()
|
|
exch.Release()
|
|
exch.Acquire(conn)
|
|
Handle(exch, &mux, nopBackoff)
|
|
}
|
|
}
|