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
© 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.
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.

View File

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

124
Server.go
View File

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

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
}