diff --git a/http/httphi/httphi.go b/http/httphi/httphi.go index 9414a32..9590955 100644 --- a/http/httphi/httphi.go +++ b/http/httphi/httphi.go @@ -41,7 +41,7 @@ type job struct { } type Mux interface { - Handler(get Method, uri []byte) HandlerFunc + LookupHandler(get Method, uri []byte) HandlerFunc } type RouterConfig struct { @@ -196,7 +196,7 @@ func (r *Router) goroHandle(exch *Exchange, backoff lneto.BackoffStrategy, mux M // Mux URI. uri := reqhdr.RequestURI() meth := reqhdr.Method() - handler := mux.Handler(MethodFromBytes(meth), uri) + handler := mux.LookupHandler(MethodFromBytes(meth), uri) if handler != nil { handler(exch) } diff --git a/http/httphi/router_test.go b/http/httphi/router_test.go new file mode 100644 index 0000000..5bbf98b --- /dev/null +++ b/http/httphi/router_test.go @@ -0,0 +1,127 @@ +package httphi + +import ( + "bytes" + "context" + "io" + "net" + "strings" + "testing" + "time" + + "github.com/soypat/lneto" + "github.com/soypat/lneto/internal" +) + +type rwconn struct { + readable bytes.Buffer + written bytes.Buffer + closed bool + deadline time.Time +} + +func (r *rwconn) Close() error { + r.closed = true + return nil +} +func (r *rwconn) Read(b []byte) (int, error) { + if r.closed { + return 0, net.ErrClosed + } else if r.deadlineExceeded() { + return 0, context.DeadlineExceeded + } + return r.readable.Read(b) +} +func (r *rwconn) Write(b []byte) (int, error) { + if r.closed { + return 0, net.ErrClosed + } else if r.deadlineExceeded() { + return 0, context.DeadlineExceeded + } + return r.written.Write(b) +} +func (r *rwconn) deadlineExceeded() bool { + return !r.deadline.IsZero() && time.Since(r.deadline) > 0 +} +func (r *rwconn) AddReadable(b []byte) { r.readable.Write(b) } +func (r *rwconn) ViewWritten() []byte { return r.written.Bytes() } + +var _ Mux = (*sliceMux)(nil) + +type sliceMux struct { + _handlers []struct { + method Method + uri string + handler HandlerFunc + } +} + +func (sm *sliceMux) LookupHandler(method Method, uri []byte) HandlerFunc { + for _, endpoint := range sm._handlers { + if endpoint.method != MethUndefined && endpoint.method != method { + continue + } + // Method matches. + if b2s(uri) == endpoint.uri { + return endpoint.handler + } + } + return nil +} +func (sm *sliceMux) Handle(reg string, handler HandlerFunc) { + v := internal.SliceReclaim(&sm._handlers) + method := MethUndefined + methodOrURL, url, methodFound := strings.Cut(reg, " ") + if methodFound { + method = MethodFromBytes([]byte(methodOrURL)) + } else { + url = methodOrURL + } + v.method = method + v.uri = url + v.handler = handler +} + +func configSynchronousRouter(t *testing.T, router *Router, bufferSize int, mux Mux) { + err := router.Configure(RouterConfig{ + FixedNumGoroutines: -1, + Mux: mux, + RequestBufferSize: bufferSize, + ResponseMinBufferSize: bufferSize, + Backoff: func(consecutiveBackoffs uint) (sleepOrFlag time.Duration) { + return lneto.BackoffFlagNop + }, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestRouterGet(t *testing.T) { + const bufferSize = 1024 + + const expectResponse = "its time" + var ( + sm sliceMux + router Router + conn rwconn + ) + conn.deadline = time.Now().Add(10 * time.Millisecond) // TODO: remove after tests run without blocking forever. + sm.Handle("GET /", staticPage(t, expectResponse)) + configSynchronousRouter(t, &router, bufferSize, &sm) + err := router.Handle(&conn) + if err != nil { + t.Fatal(err) + } +} + +func staticPage(t *testing.T, page string) HandlerFunc { + return func(ex *Exchange) { + n, err := io.WriteString(ex, page) + if err != nil { + t.Fatal(err) + } else if n != len(page) { + t.Fatal("expected written ", len(page), "got", n) + } + } +}