Added request headers

This commit is contained in:
Eduard Urbach 2024-03-27 22:12:16 +01:00
parent b6c208c75b
commit c3bb8336a3
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
6 changed files with 131 additions and 65 deletions

View File

@ -64,4 +64,4 @@ Please see the [license documentation](https://akyoto.dev/license).
## Copyright ## Copyright
© 2023 Eduard Urbach © 2024 Eduard Urbach

View File

@ -4,6 +4,7 @@ import "git.akyoto.dev/go/router"
// Request is an interface for HTTP requests. // Request is an interface for HTTP requests.
type Request interface { type Request interface {
Header(string) string
Host() string Host() string
Method() string Method() string
Path() string Path() string
@ -13,12 +14,25 @@ type Request interface {
// request represents the HTTP request used in the given context. // request represents the HTTP request used in the given context.
type request struct { type request struct {
scheme string scheme string
host string host string
method string method string
path string path string
query string query string
params []router.Parameter 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. // Host returns the requested host.

View File

@ -3,8 +3,6 @@ package web
import ( import (
"io" "io"
"strings" "strings"
"git.akyoto.dev/go/router"
) )
// Response is the interface for an HTTP response. // 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. // response represents the HTTP response used in the given context.
type response struct { type response struct {
body []byte body []byte
headers []header
status uint16 status uint16
headers []router.Parameter
} }
// Body returns the response body. // 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. // SetBody replaces the response body with the new contents.

124
Server.go
View File

@ -121,36 +121,77 @@ func (s *server) handleConnection(conn net.Conn) {
defer conn.Close() defer conn.Close()
reader := bufio.NewReader(conn) reader := bufio.NewReader(conn)
var (
ctx *context
method string
url string
)
for { for {
message, err := reader.ReadString('\n') // Search for a line containing HTTP method and url
for {
message, err := reader.ReadString('\n')
if err != nil { if err != nil {
return 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 { if err != nil {
continue 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] // Handle the request
if method != "GET" {
continue
}
lastSpace := strings.LastIndexByte(message, ' ')
if lastSpace == -1 {
lastSpace = len(message)
}
url := message[space+1 : lastSpace]
ctx := s.pool.Get().(*context)
s.handleRequest(ctx, method, url, conn) 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.params = ctx.params[:0]
ctx.handlerCount = 0 ctx.handlerCount = 0
ctx.status = 200 ctx.status = 200
@ -169,7 +210,7 @@ func (s *server) handleRequest(ctx *context, method string, url string, writer i
s.errorHandler(ctx, err) 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. // newContext allocates a new context with the default state.
@ -177,39 +218,14 @@ func (s *server) newContext() *context {
return &context{ return &context{
server: s, server: s,
request: request{ 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{ response: response{
body: make([]byte, 0, 1024), body: make([]byte, 0, 1024),
status: 200, 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
}

7
header.go Normal file
View File

@ -0,0 +1,7 @@
package web
// header is used to store HTTP headers.
type header struct {
Key string
Value string
}

31
parseURL.go Normal file
View File

@ -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
}