From 271e1cd5bd6022e6011109a543123e74d337d2bd Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 26 Mar 2024 22:46:16 +0100 Subject: [PATCH] Removed net/http --- Context.go | 92 ++++------------- Request.go | 57 +++-------- Response.go | 87 ++++++++++++---- Server.go | 168 +++++++++++++++++++++---------- Server_test.go | 218 +++------------------------------------- benchmarks_test.go | 63 ------------ common_test.go | 12 --- config.go | 30 ------ content/content_test.go | 80 --------------- examples/logger/main.go | 24 ----- examples/stream/main.go | 28 ------ 11 files changed, 230 insertions(+), 629 deletions(-) delete mode 100644 benchmarks_test.go delete mode 100644 common_test.go delete mode 100644 config.go delete mode 100644 content/content_test.go delete mode 100644 examples/logger/main.go delete mode 100644 examples/stream/main.go diff --git a/Context.go b/Context.go index d58bf62..46b3b8c 100644 --- a/Context.go +++ b/Context.go @@ -2,50 +2,35 @@ package web import ( "errors" - "io" - "net/http" - - "git.akyoto.dev/go/router" ) -// Context represents the interface for a request & response context. +// Context is the interface for a request and its response. type Context interface { - Copy(io.Reader) error Bytes([]byte) error Error(...any) error - File(string) error - Get(string) string Next() error - Redirect(int, string) error Request() Request Response() Response Status(int) Context String(string) error } -// ctx represents a request & response context. -type ctx struct { - request request - response response +// context contains the request and response data. +type context struct { + request + response server *server - params []router.Parameter handlerCount uint8 } -// Bytes responds with a raw byte slice. -func (ctx *ctx) Bytes(body []byte) error { - _, err := ctx.response.Write(body) - return err +// Bytes adds the raw byte slice to the response body. +func (ctx *context) Bytes(body []byte) error { + ctx.response.body = append(ctx.response.body, body...) + return nil } -// Copy sends the contents of the io.Reader without creating an in-memory copy. -func (ctx *ctx) Copy(reader io.Reader) error { - _, err := io.Copy(ctx.response.ResponseWriter, reader) - return err -} - -// Error is used for sending error messages to the client. -func (ctx *ctx) Error(messages ...any) error { +// Error provides a convenient way to wrap multiple errors. +func (ctx *context) Error(messages ...any) error { var combined []error for _, msg := range messages { @@ -60,64 +45,31 @@ func (ctx *ctx) Error(messages ...any) error { return errors.Join(combined...) } -// Get retrieves a parameter. -func (ctx *ctx) Get(param string) string { - for i := range len(ctx.params) { - p := ctx.params[i] - - if p.Key == param { - return p.Value - } - } - - return "" -} - -// File serves the file at the given path. -func (ctx *ctx) File(path string) error { - http.ServeFile(ctx.response.ResponseWriter, ctx.request.Request, path) - return nil -} - // Next executes the next handler in the middleware chain. -func (ctx *ctx) Next() error { +func (ctx *context) Next() error { ctx.handlerCount++ return ctx.server.handlers[ctx.handlerCount](ctx) } // Request returns the HTTP request. -func (ctx *ctx) Request() Request { +func (ctx *context) Request() Request { return &ctx.request } // Response returns the HTTP response. -func (ctx *ctx) Response() Response { +func (ctx *context) Response() Response { return &ctx.response } -// Redirect sets the Location header and writes the headers with the given status code. -func (ctx *ctx) Redirect(code int, url string) error { - ctx.Response().SetHeader("Location", url) - ctx.Status(code) - return nil -} - -// Status sets the HTTP status of the response. -func (ctx *ctx) Status(status int) Context { - ctx.response.WriteHeader(status) +// Status sets the HTTP status of the response +// and returns the context for method chaining. +func (ctx *context) Status(status int) Context { + ctx.response.SetStatus(status) return ctx } -// String responds with the given string. -func (ctx *ctx) String(body string) error { - _, err := ctx.response.WriteString(body) - return err -} - -// addParameter adds a new parameter to the context. -func (ctx *ctx) addParameter(key string, value string) { - ctx.params = append(ctx.params, router.Parameter{ - Key: key, - Value: value, - }) +// String adds the given string to the response body. +func (ctx *context) String(body string) error { + ctx.response.body = append(ctx.response.body, body...) + return nil } diff --git a/Request.go b/Request.go index 9715449..954ff33 100644 --- a/Request.go +++ b/Request.go @@ -1,63 +1,34 @@ package web -import ( - "context" - "net/http" -) +import "git.akyoto.dev/go/router" // Request is an interface for HTTP requests. type Request interface { - Context() context.Context - Header(key string) string - Host() string Method() string Path() string - Protocol() string - Read([]byte) (int, error) - Scheme() string } // request represents the HTTP request used in the given context. type request struct { - *http.Request -} - -// Context returns the request context. -func (req request) Context() context.Context { - return req.Request.Context() -} - -// Header returns the header value for the given key. -func (req request) Header(key string) string { - return req.Request.Header.Get(key) + method string + path string + params []router.Parameter } // Method returns the request method. -func (req request) Method() string { - return req.Request.Method -} - -// Protocol returns the request protocol. -func (req request) Protocol() string { - return req.Request.Proto -} - -// Host returns the requested host. -func (req request) Host() string { - return req.Request.Host +func (req *request) Method() string { + return req.method } // Path returns the requested path. -func (req request) Path() string { - return req.Request.URL.Path +func (req *request) Path() string { + return req.path } -// // Read implements the io.Reader interface and reads the request body. -func (req request) Read(buffer []byte) (int, error) { - return req.Request.Body.Read(buffer) -} - -// Scheme returns either `http` or `https`. -func (req request) Scheme() string { - return req.Request.URL.Scheme +// addParameter adds a new parameter to the request. +func (req *request) addParameter(key string, value string) { + req.params = append(req.params, router.Parameter{ + Key: key, + Value: value, + }) } diff --git a/Response.go b/Response.go index 519e56d..cc66cf8 100644 --- a/Response.go +++ b/Response.go @@ -2,48 +2,95 @@ package web import ( "io" - "net/http" + "strings" + + "git.akyoto.dev/go/router" ) // Response is the interface for an HTTP response. type Response interface { - Flush() + io.Writer + io.StringWriter + Body() []byte Header(key string) string SetHeader(key string, value string) - Write([]byte) (int, error) - WriteString(string) (int, error) + SetBody([]byte) + SetStatus(status int) + Status() int } // response represents the HTTP response used in the given context. type response struct { - http.ResponseWriter + body []byte + status uint16 + headers []router.Parameter } -// Flush flushes the response buffers to the client. -func (res response) Flush() { - flusher, ok := res.ResponseWriter.(http.Flusher) - - if ok { - flusher.Flush() - } +// Body returns the response body. +func (res *response) Body() []byte { + return res.body } // Header returns the header value for the given key. -func (res response) Header(key string) string { - return res.ResponseWriter.Header().Get(key) +func (res *response) Header(key string) string { + for _, header := range res.headers { + if header.Key == key { + return header.Value + } + } + + return "" } // SetHeader sets the header value for the given key. -func (res response) SetHeader(key string, value string) { - res.ResponseWriter.Header().Set(key, value) +func (res *response) SetHeader(key string, value string) { + for _, header := range res.headers { + if header.Key == key { + header.Value = value + return + } + } + + res.headers = append(res.headers, router.Parameter{Key: key, Value: value}) +} + +// SetBody replaces the response body with the new contents. +func (res *response) SetBody(body []byte) { + res.body = body +} + +// SetStatus sets the HTTP status code. +func (res *response) SetStatus(status int) { + res.status = uint16(status) +} + +// Status returns the HTTP status code. +func (res *response) Status() int { + return int(res.status) } // Write implements the io.Writer interface. -func (res response) Write(body []byte) (int, error) { - return res.ResponseWriter.Write(body) +func (res *response) Write(body []byte) (int, error) { + res.body = append(res.body, body...) + return len(body), nil } // WriteString implements the io.StringWriter interface. -func (res response) WriteString(body string) (int, error) { - return res.ResponseWriter.(io.StringWriter).WriteString(body) +func (res *response) WriteString(body string) (int, error) { + res.body = append(res.body, body...) + return len(body), nil +} + +// headerText combines all HTTP headers into a single string. +func (res *response) headerText() string { + combined := strings.Builder{} + + for _, header := range res.headers { + combined.WriteString(header.Key) + combined.WriteString(": ") + combined.WriteString(header.Value) + combined.WriteString("\r\n") + } + + return combined.String() } diff --git a/Server.go b/Server.go index 9fde8cc..ddc99ef 100644 --- a/Server.go +++ b/Server.go @@ -1,12 +1,14 @@ package web import ( - "context" + "bufio" + "fmt" + "io" "log" "net" - "net/http" "os" "os/signal" + "strings" "sync" "syscall" @@ -15,13 +17,13 @@ import ( // Server is the interface for an HTTP server. type Server interface { - http.Handler Delete(path string, handler Handler) Get(path string, handler Handler) Post(path string, handler Handler) Put(path string, handler Handler) Router() *router.Router[Handler] Run(address string) error + Test(method string, path string, body io.Reader) Response Use(handlers ...Handler) } @@ -29,41 +31,35 @@ type Server interface { type server struct { pool sync.Pool handlers []Handler - router router.Router[Handler] + router *router.Router[Handler] errorHandler func(Context, error) - config config } // NewServer creates a new HTTP server. func NewServer() Server { + r := &router.Router[Handler]{} s := &server{ - router: router.Router[Handler]{}, - config: defaultConfig(), + router: r, handlers: []Handler{ func(c Context) error { - ctx := c.(*ctx) - method := ctx.request.Method() - path := ctx.request.Path() - handler := ctx.server.router.LookupNoAlloc(method, path, ctx.addParameter) + ctx := c.(*context) + handler := r.LookupNoAlloc(ctx.request.method, ctx.request.path, ctx.request.addParameter) if handler == nil { - return ctx.Status(http.StatusNotFound).String(http.StatusText(http.StatusNotFound)) + ctx.SetStatus(404) + return nil } return handler(c) }, }, errorHandler: func(ctx Context, err error) { - ctx.Response().WriteString(err.Error()) log.Println(ctx.Request().Path(), err) }, } s.pool.New = func() any { - return &ctx{ - server: s, - params: make([]router.Parameter, 0, 8), - } + return s.newContext() } return s @@ -71,72 +67,55 @@ func NewServer() Server { // Get registers your function to be called when the given GET path has been requested. func (s *server) Get(path string, handler Handler) { - s.Router().Add(http.MethodGet, path, handler) + s.Router().Add("GET", path, handler) } // Post registers your function to be called when the given POST path has been requested. func (s *server) Post(path string, handler Handler) { - s.Router().Add(http.MethodPost, path, handler) + s.Router().Add("POST", path, handler) } // Delete registers your function to be called when the given DELETE path has been requested. func (s *server) Delete(path string, handler Handler) { - s.Router().Add(http.MethodDelete, path, handler) + s.Router().Add("DELETE", path, handler) } // Put registers your function to be called when the given PUT path has been requested. func (s *server) Put(path string, handler Handler) { - s.Router().Add(http.MethodPut, path, handler) -} - -// ServeHTTP responds to the given request. -func (s *server) ServeHTTP(res http.ResponseWriter, req *http.Request) { - ctx := s.pool.Get().(*ctx) - ctx.request = request{req} - ctx.response = response{res} - err := s.handlers[0](ctx) - - if err != nil { - s.errorHandler(ctx, err) - } - - ctx.params = ctx.params[:0] - ctx.handlerCount = 0 - s.pool.Put(ctx) + s.Router().Add("PUT", path, handler) } // Run starts the server on the given address. -func (server *server) Run(address string) error { - srv := &http.Server{ - Addr: address, - Handler: server, - ReadTimeout: server.config.Timeout.Read, - WriteTimeout: server.config.Timeout.Write, - IdleTimeout: server.config.Timeout.Idle, - ReadHeaderTimeout: server.config.Timeout.ReadHeader, - } - +func (s *server) Run(address string) error { listener, err := net.Listen("tcp", address) if err != nil { return err } - go srv.Serve(listener) + defer listener.Close() + + go func() { + for { + conn, err := listener.Accept() + + if err != nil { + continue + } + + go s.handleConnection(conn) + } + }() stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop - - ctx, cancel := context.WithTimeout(context.Background(), server.config.Timeout.Shutdown) - defer cancel() - - return srv.Shutdown(ctx) + return nil } // Router returns the router used by the server. func (s *server) Router() *router.Router[Handler] { - return &s.router + return s.router } // Use adds handlers to your handlers chain. @@ -145,3 +124,84 @@ func (s *server) Use(handlers ...Handler) { s.handlers = append(s.handlers[:len(s.handlers)-1], handlers...) s.handlers = append(s.handlers, last) } + +// handleConnection handles an accepted connection. +func (s *server) handleConnection(conn net.Conn) { + defer conn.Close() + reader := bufio.NewReader(conn) + + for { + message, err := reader.ReadString('\n') + + if err != nil { + return + } + + space := strings.IndexByte(message, ' ') + + if space <= 0 { + continue + } + + method := message[:space] + + if method != "GET" { + continue + } + + lastSpace := strings.LastIndexByte(message, ' ') + + if lastSpace == -1 { + lastSpace = len(message) + } + + path := message[space+1 : lastSpace] + + ctx := s.pool.Get().(*context) + s.handleRequest(ctx, method, path, conn) + ctx.body = ctx.body[:0] + ctx.params = ctx.params[:0] + ctx.handlerCount = 0 + ctx.status = 200 + s.pool.Put(ctx) + } +} + +// handleRequest handles the given request. +func (s *server) handleRequest(ctx *context, method string, path string, writer io.Writer) { + ctx.method = method + ctx.path = path + + err := s.handlers[0](ctx) + + if err != nil { + s.errorHandler(ctx, err) + } + + _, err = fmt.Fprintf(writer, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n%s\r\n%s", ctx.status, "OK", len(ctx.body), ctx.response.headerText(), ctx.body) + + if err != nil { + s.errorHandler(ctx, err) + } +} + +func (s *server) Test(method string, path string, body io.Reader) Response { + ctx := s.newContext() + ctx.method = method + ctx.path = path + s.handleRequest(ctx, method, path, io.Discard) + return ctx.Response() +} + +func (s *server) newContext() *context { + return &context{ + server: s, + request: request{ + params: make([]router.Parameter, 0, 8), + }, + response: response{ + body: make([]byte, 0, 1024), + status: 200, + }, + } +} diff --git a/Server_test.go b/Server_test.go index a6bbe3b..50737bb 100644 --- a/Server_test.go +++ b/Server_test.go @@ -1,224 +1,32 @@ package web_test import ( - "errors" - "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "strings" - "syscall" "testing" "git.akyoto.dev/go/assert" "git.akyoto.dev/go/web" ) -func TestRouter(t *testing.T) { +func TestBytes(t *testing.T) { s := web.NewServer() s.Get("/", func(ctx web.Context) error { return ctx.Bytes([]byte("Hello")) }) - s.Get("/string", func(ctx web.Context) error { + response := s.Test("GET", "/", nil) + assert.Equal(t, response.Status(), 200) + assert.DeepEqual(t, response.Body(), []byte("Hello")) +} + +func TestString(t *testing.T) { + s := web.NewServer() + + s.Get("/", func(ctx web.Context) error { return ctx.String("Hello") }) - s.Get("/write", func(ctx web.Context) error { - _, err := ctx.Response().Write([]byte("Hello")) - return err - }) - - s.Get("/writestring", func(ctx web.Context) error { - _, err := io.WriteString(ctx.Response(), "Hello") - return err - }) - - s.Get("/error", func(ctx web.Context) error { - return ctx.Status(http.StatusUnauthorized).Error("Not logged in") - }) - - s.Get("/error2", func(ctx web.Context) error { - return ctx.Status(http.StatusUnauthorized).Error("Not logged in", errors.New("Missing auth token")) - }) - - s.Get("/reader", func(ctx web.Context) error { - return ctx.Copy(strings.NewReader("Hello")) - }) - - s.Get("/file", func(ctx web.Context) error { - return ctx.File("testdata/file.txt") - }) - - s.Get("/flush", func(ctx web.Context) error { - ctx.Response().WriteString("Hello 1\n") - ctx.Response().WriteString("Hello 2\n") - ctx.Response().Flush() - return nil - }) - - s.Get("/echo", func(ctx web.Context) error { - return ctx.Copy(ctx.Request()) - }) - - s.Get("/context", func(ctx web.Context) error { - return ctx.Request().Context().Err() - }) - - s.Get("/redirect", func(ctx web.Context) error { - return ctx.Redirect(http.StatusTemporaryRedirect, "/") - }) - - s.Get("/request/data", func(ctx web.Context) error { - request := ctx.Request() - method := request.Method() - protocol := request.Protocol() - host := request.Host() - path := request.Path() - return ctx.String(fmt.Sprintf("%s %s %s %s", method, protocol, host, path)) - }) - - s.Get("/request/header", func(ctx web.Context) error { - acceptEncoding := ctx.Request().Header("Accept-Encoding") - return ctx.String(acceptEncoding) - }) - - s.Get("/response/header", func(ctx web.Context) error { - ctx.Response().SetHeader("Content-Type", "text/plain") - contentType := ctx.Response().Header("Content-Type") - return ctx.String(contentType) - }) - - s.Get("/blog/:article", func(ctx web.Context) error { - article := ctx.Get("article") - return ctx.String(article) - }) - - s.Get("/missing-parameter", func(ctx web.Context) error { - missing := ctx.Get("missing") - return ctx.String(missing) - }) - - s.Get("/scheme", func(ctx web.Context) error { - return ctx.String(ctx.Request().Scheme()) - }) - - s.Post("/", func(ctx web.Context) error { - return ctx.String("Post") - }) - - s.Delete("/", func(ctx web.Context) error { - return ctx.String("Delete") - }) - - s.Put("/", func(ctx web.Context) error { - return ctx.String("Put") - }) - - tests := []struct { - Method string - URL string - Body string - Status int - Response string - }{ - {Method: "GET", URL: "/", Body: "", Status: http.StatusOK, Response: "Hello"}, - {Method: "GET", URL: "/context", Body: "", Status: http.StatusOK, Response: ""}, - {Method: "GET", URL: "/echo", Body: "Echo", Status: http.StatusOK, Response: "Echo"}, - {Method: "GET", URL: "/error", Body: "", Status: http.StatusUnauthorized, Response: "Not logged in"}, - {Method: "GET", URL: "/error2", Body: "", Status: http.StatusUnauthorized, Response: "Not logged in\nMissing auth token"}, - {Method: "GET", URL: "/file", Body: "", Status: http.StatusOK, Response: "Hello File"}, - {Method: "GET", URL: "/flush", Body: "", Status: http.StatusOK, Response: "Hello 1\nHello 2\n"}, - {Method: "GET", URL: "/not-found", Body: "", Status: http.StatusNotFound, Response: http.StatusText(http.StatusNotFound)}, - {Method: "GET", URL: "/request/data", Body: "", Status: http.StatusOK, Response: "GET HTTP/1.1 example.com /request/data"}, - {Method: "GET", URL: "/request/header", Body: "", Status: http.StatusOK, Response: ""}, - {Method: "GET", URL: "/response/header", Body: "", Status: http.StatusOK, Response: "text/plain"}, - {Method: "GET", URL: "/reader", Body: "", Status: http.StatusOK, Response: "Hello"}, - {Method: "GET", URL: "/redirect", Body: "", Status: http.StatusTemporaryRedirect, Response: ""}, - {Method: "GET", URL: "/string", Body: "", Status: http.StatusOK, Response: "Hello"}, - {Method: "GET", URL: "/scheme", Body: "", Status: http.StatusOK, Response: "http"}, - {Method: "GET", URL: "/write", Body: "", Status: http.StatusOK, Response: "Hello"}, - {Method: "GET", URL: "/writestring", Body: "", Status: http.StatusOK, Response: "Hello"}, - {Method: "GET", URL: "/blog/testing-my-router", Body: "", Status: http.StatusOK, Response: "testing-my-router"}, - {Method: "GET", URL: "/missing-parameter", Body: "", Status: http.StatusOK, Response: ""}, - {Method: "POST", URL: "/", Body: "", Status: http.StatusOK, Response: "Post"}, - {Method: "DELETE", URL: "/", Body: "", Status: http.StatusOK, Response: "Delete"}, - {Method: "PUT", URL: "/", Body: "", Status: http.StatusOK, Response: "Put"}, - } - - for _, test := range tests { - t.Run("example.com"+test.URL, func(t *testing.T) { - request := httptest.NewRequest(test.Method, "http://example.com"+test.URL, strings.NewReader(test.Body)) - response := httptest.NewRecorder() - s.ServeHTTP(response, request) - - result := response.Result() - assert.Equal(t, result.StatusCode, test.Status) - - body, err := io.ReadAll(result.Body) - assert.Nil(t, err) - assert.Equal(t, string(body), test.Response) - }) - } -} - -func TestMiddleware(t *testing.T) { - s := web.NewServer() - - s.Use(func(ctx web.Context) error { - ctx.Response().SetHeader("Middleware", "true") - return ctx.Next() - }) - - request := httptest.NewRequest(http.MethodGet, "/", nil) - response := httptest.NewRecorder() - s.ServeHTTP(response, request) - - assert.Equal(t, response.Header().Get("Middleware"), "true") -} - -func TestPanic(t *testing.T) { - s := web.NewServer() - - s.Router().Add(http.MethodGet, "/panic", func(ctx web.Context) error { - panic("Something unbelievable happened") - }) - - t.Run("example.com/panic", func(t *testing.T) { - defer func() { - r := recover() - - if r == nil { - t.Error("Didn't panic") - } - }() - - request := httptest.NewRequest(http.MethodGet, "/panic", nil) - response := httptest.NewRecorder() - s.ServeHTTP(response, request) - }) -} - -func TestRun(t *testing.T) { - s := web.NewServer() - - go func() { - _, err := http.Get("http://127.0.0.1:8080/") - assert.Nil(t, err) - err = syscall.Kill(syscall.Getpid(), syscall.SIGTERM) - assert.Nil(t, err) - }() - - s.Run(":8080") -} - -func TestUnavailablePort(t *testing.T) { - listener, err := net.Listen("tcp", ":8080") - assert.Nil(t, err) - defer listener.Close() - - s := web.NewServer() - s.Run(":8080") + response := s.Test("GET", "/", nil) + assert.Equal(t, response.Status(), 200) + assert.DeepEqual(t, response.Body(), []byte("Hello")) } diff --git a/benchmarks_test.go b/benchmarks_test.go deleted file mode 100644 index 3c5e2c4..0000000 --- a/benchmarks_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package web_test - -import ( - "net/http/httptest" - "strings" - "testing" - - "git.akyoto.dev/go/router/testdata" - "git.akyoto.dev/go/web" -) - -func BenchmarkStatic(b *testing.B) { - paths := []string{ - "/", - "/hello", - "/hello/world", - } - - s := web.NewServer() - - for _, path := range paths { - s.Get(path, func(ctx web.Context) error { - return ctx.String("Hello") - }) - } - - for _, path := range paths { - b.Run(strings.TrimPrefix(path, "/"), func(b *testing.B) { - request := httptest.NewRequest("GET", path, nil) - response := &NullResponse{} - - for range b.N { - s.ServeHTTP(response, request) - } - }) - } -} - -func BenchmarkGitHub(b *testing.B) { - paths := []string{ - "/gists/:id", - "/repos/:a/:b", - } - - s := web.NewServer() - - for _, route := range testdata.Routes("testdata/github.txt") { - s.Router().Add(route.Method, route.Path, func(ctx web.Context) error { - return ctx.String("Hello") - }) - } - - for _, path := range paths { - b.Run(strings.TrimPrefix(path, "/"), func(b *testing.B) { - request := httptest.NewRequest("GET", path, nil) - response := &NullResponse{} - - for range b.N { - s.ServeHTTP(response, request) - } - }) - } -} diff --git a/common_test.go b/common_test.go deleted file mode 100644 index 25f06f3..0000000 --- a/common_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package web_test - -import "net/http" - -// NullResponse implements the http.ResponseWriter interface with -// empty methods to better understand memory usage in benchmarks. -type NullResponse struct{} - -func (r *NullResponse) Header() http.Header { return nil } -func (r *NullResponse) Write([]byte) (int, error) { return 0, nil } -func (r *NullResponse) WriteString(string) (int, error) { return 0, nil } -func (r *NullResponse) WriteHeader(int) {} diff --git a/config.go b/config.go deleted file mode 100644 index 6f590c4..0000000 --- a/config.go +++ /dev/null @@ -1,30 +0,0 @@ -package web - -import "time" - -// config represents the server configuration. -type config struct { - Timeout timeoutConfig `json:"timeout"` -} - -// timeoutConfig lets you configure the different timeout durations. -type timeoutConfig struct { - Idle time.Duration `json:"idle"` - Read time.Duration `json:"read"` - ReadHeader time.Duration `json:"readHeader"` - Write time.Duration `json:"write"` - Shutdown time.Duration `json:"shutdown"` -} - -// Reset resets all fields to the default configuration. -func defaultConfig() config { - return config{ - Timeout: timeoutConfig{ - Idle: 3 * time.Minute, - Write: 2 * time.Minute, - Read: 5 * time.Second, - ReadHeader: 5 * time.Second, - Shutdown: 250 * time.Millisecond, - }, - } -} diff --git a/content/content_test.go b/content/content_test.go deleted file mode 100644 index 901eaeb..0000000 --- a/content/content_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package content_test - -import ( - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "git.akyoto.dev/go/assert" - "git.akyoto.dev/go/web" - "git.akyoto.dev/go/web/content" -) - -func TestContentTypes(t *testing.T) { - s := web.NewServer() - - s.Get("/css", func(ctx web.Context) error { - return content.CSS(ctx, "body{}") - }) - - s.Get("/csv", func(ctx web.Context) error { - return content.CSV(ctx, "ID;Name\n") - }) - - s.Get("/html", func(ctx web.Context) error { - return content.HTML(ctx, "") - }) - - s.Get("/js", func(ctx web.Context) error { - return content.JS(ctx, "console.log(42)") - }) - - s.Get("/json", func(ctx web.Context) error { - return content.JSON(ctx, struct{ Name string }{Name: "User 1"}) - }) - - s.Get("/text", func(ctx web.Context) error { - return content.Text(ctx, "Hello") - }) - - s.Get("/xml", func(ctx web.Context) error { - return content.XML(ctx, "") - }) - - tests := []struct { - Method string - URL string - Body string - Status int - Response string - ContentType string - }{ - {Method: "GET", URL: "/css", Body: "", Status: http.StatusOK, Response: "body{}", ContentType: "text/css"}, - {Method: "GET", URL: "/csv", Body: "", Status: http.StatusOK, Response: "ID;Name\n", ContentType: "text/csv"}, - {Method: "GET", URL: "/html", Body: "", Status: http.StatusOK, Response: "", ContentType: "text/html"}, - {Method: "GET", URL: "/js", Body: "", Status: http.StatusOK, Response: "console.log(42)", ContentType: "text/javascript"}, - {Method: "GET", URL: "/json", Body: "", Status: http.StatusOK, Response: "{\"Name\":\"User 1\"}\n", ContentType: "application/json"}, - {Method: "GET", URL: "/text", Body: "", Status: http.StatusOK, Response: "Hello", ContentType: "text/plain"}, - {Method: "GET", URL: "/xml", Body: "", Status: http.StatusOK, Response: "", ContentType: "text/xml"}, - } - - for _, test := range tests { - t.Run("example.com"+test.URL, func(t *testing.T) { - request := httptest.NewRequest(test.Method, "http://example.com"+test.URL, strings.NewReader(test.Body)) - response := httptest.NewRecorder() - s.ServeHTTP(response, request) - - result := response.Result() - assert.Equal(t, result.StatusCode, test.Status) - - contentType := result.Header.Get("Content-Type") - assert.Equal(t, contentType, test.ContentType) - - body, err := io.ReadAll(result.Body) - assert.Nil(t, err) - assert.Equal(t, string(body), test.Response) - }) - } -} diff --git a/examples/logger/main.go b/examples/logger/main.go deleted file mode 100644 index 7ccea7d..0000000 --- a/examples/logger/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "fmt" - "time" - - "git.akyoto.dev/go/web" -) - -func main() { - s := web.NewServer() - - s.Use(func(ctx web.Context) error { - start := time.Now() - - defer func() { - fmt.Println(ctx.Request().Path(), time.Since(start)) - }() - - return ctx.Next() - }) - - s.Run(":8080") -} diff --git a/examples/stream/main.go b/examples/stream/main.go deleted file mode 100644 index 3c2f490..0000000 --- a/examples/stream/main.go +++ /dev/null @@ -1,28 +0,0 @@ -package main - -import ( - "time" - - "git.akyoto.dev/go/web" -) - -func main() { - s := web.NewServer() - - s.Get("/", func(ctx web.Context) error { - ticker := time.NewTicker(time.Second) - - for { - select { - case <-ctx.Request().Context().Done(): - return nil - - case <-ticker.C: - ctx.Response().WriteString("Hello\n") - ctx.Response().Flush() - } - } - }) - - s.Run(":8080") -}