diff --git a/README.md b/README.md index eaec92a..1b715b6 100644 --- a/README.md +++ b/README.md @@ -64,4 +64,4 @@ Please see the [license documentation](https://akyoto.dev/license). ## Copyright -© 2023 Eduard Urbach +© 2024 Eduard Urbach diff --git a/Request.go b/Request.go index 54dd6f8..ed067b3 100644 --- a/Request.go +++ b/Request.go @@ -4,6 +4,7 @@ import "git.akyoto.dev/go/router" // Request is an interface for HTTP requests. type Request interface { + Header(string) string Host() string Method() string Path() string @@ -13,12 +14,25 @@ type Request interface { // request represents the HTTP request used in the given context. type request struct { - scheme string - host string - method string - path string - query string - params []router.Parameter + scheme string + host string + method string + path string + query string + headers []header + body []byte + params []router.Parameter +} + +// Header returns the header value for the given key. +func (req *request) Header(key string) string { + for _, header := range req.headers { + if header.Key == key { + return header.Value + } + } + + return "" } // Host returns the requested host. diff --git a/Response.go b/Response.go index dc46c03..24ed518 100644 --- a/Response.go +++ b/Response.go @@ -3,8 +3,6 @@ package web import ( "io" "strings" - - "git.akyoto.dev/go/router" ) // Response is the interface for an HTTP response. @@ -22,8 +20,8 @@ type Response interface { // response represents the HTTP response used in the given context. type response struct { body []byte + headers []header status uint16 - headers []router.Parameter } // Body returns the response body. @@ -51,7 +49,7 @@ func (res *response) SetHeader(key string, value string) { } } - res.headers = append(res.headers, router.Parameter{Key: key, Value: value}) + res.headers = append(res.headers, header{Key: key, Value: value}) } // SetBody replaces the response body with the new contents. diff --git a/Server.go b/Server.go index 940a2f1..c30d991 100644 --- a/Server.go +++ b/Server.go @@ -121,36 +121,77 @@ func (s *server) handleConnection(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) + var ( + ctx *context + method string + url string + ) + for { - message, err := reader.ReadString('\n') + // Search for a line containing HTTP method and url + for { + message, err := reader.ReadString('\n') - if err != nil { - return + 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) + } + + ctx = s.pool.Get().(*context) + url = message[space+1 : lastSpace] + break } - space := strings.IndexByte(message, ' ') + // Add headers until we meet an empty line + for { + message, err := reader.ReadString('\n') - if space <= 0 { - continue + if err != nil { + return + } + + if message == "\r\n" { + break + } + + colon := strings.IndexByte(message, ':') + + if colon <= 0 { + continue + } + + key := message[:colon] + value := message[colon+2 : len(message)-2] + + ctx.request.headers = append(ctx.request.headers, header{ + Key: key, + Value: value, + }) } - method := message[:space] - - if method != "GET" { - continue - } - - lastSpace := strings.LastIndexByte(message, ' ') - - if lastSpace == -1 { - lastSpace = len(message) - } - - url := message[space+1 : lastSpace] - - ctx := s.pool.Get().(*context) + // Handle the request s.handleRequest(ctx, method, url, conn) - ctx.body = ctx.body[:0] + ctx.request.headers = ctx.request.headers[:0] + ctx.request.body = ctx.request.body[:0] + ctx.response.headers = ctx.response.headers[:0] + ctx.response.body = ctx.response.body[:0] ctx.params = ctx.params[:0] ctx.handlerCount = 0 ctx.status = 200 @@ -169,7 +210,7 @@ func (s *server) handleRequest(ctx *context, method string, url string, writer i s.errorHandler(ctx, 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) + fmt.Fprintf(writer, "HTTP/1.1 %d %s\r\nContent-Length: %d\r\n%s\r\n%s", ctx.status, "OK", len(ctx.response.body), ctx.response.headerText(), ctx.response.body) } // newContext allocates a new context with the default state. @@ -177,39 +218,14 @@ func (s *server) newContext() *context { return &context{ server: s, request: request{ - params: make([]router.Parameter, 0, 8), + body: make([]byte, 0), + headers: make([]header, 0, 8), + params: make([]router.Parameter, 0, 8), }, response: response{ - body: make([]byte, 0, 1024), - status: 200, + body: make([]byte, 0, 1024), + headers: make([]header, 0, 8), + status: 200, }, } } - -// parseURL parses a URL and returns the scheme, host, path and query. -func parseURL(url string) (scheme string, host string, path string, query string) { - schemePos := strings.Index(url, "://") - - if schemePos != -1 { - scheme = url[:schemePos] - url = url[schemePos+len("://"):] - } - - pathPos := strings.IndexByte(url, '/') - - if pathPos != -1 { - host = url[:pathPos] - url = url[pathPos:] - } - - queryPos := strings.IndexByte(url, '?') - - if queryPos != -1 { - path = url[:queryPos] - query = url[queryPos+1:] - return - } - - path = url - return -} diff --git a/header.go b/header.go new file mode 100644 index 0000000..c0dbbd6 --- /dev/null +++ b/header.go @@ -0,0 +1,7 @@ +package web + +// header is used to store HTTP headers. +type header struct { + Key string + Value string +} diff --git a/parseURL.go b/parseURL.go new file mode 100644 index 0000000..1b2709d --- /dev/null +++ b/parseURL.go @@ -0,0 +1,31 @@ +package web + +import "strings" + +// parseURL parses a URL and returns the scheme, host, path and query. +func parseURL(url string) (scheme string, host string, path string, query string) { + schemePos := strings.Index(url, "://") + + if schemePos != -1 { + scheme = url[:schemePos] + url = url[schemePos+len("://"):] + } + + pathPos := strings.IndexByte(url, '/') + + if pathPos != -1 { + host = url[:pathPos] + url = url[pathPos:] + } + + queryPos := strings.IndexByte(url, '?') + + if queryPos != -1 { + path = url[:queryPos] + query = url[queryPos+1:] + return + } + + path = url + return +}