This is a continuation of the work done in part 1, in this post we will introduce a new http parser and a new http request type to better handler the incoming request.
HTTP Request
In the last chapter we left ourselves with a first version of the server in which they are handled as strings and passed through the various functions. This part needed a more suitable structure for better maintenance, so in the http package is defined a HttpRequest struct which defines the fields of a request.
type HttpRequest struct {
Method string
Url string
Headers map[string]string
Body string
Query string
}
HTTP Parser
To transform incoming request from
GET /echo/hello HTTP/1.1
Host: localhost:3000
User-Agent: curl/8.1.2
Accept: */*
Content-Type: application/json
Content-Length: 22
{
hello: "world"
}
to a struct like the one defined before we need a parser.
The HTTP standard suggest to read the start-line into a structure, read each header field line into a hash table by field name until the empty line, and then use the parsed data to determine if a message body is expected. If a message body has been indicated, then it is read as a stream until an amount of octets equal to the message body length is read or the connection is closed.
A possibile implementation is defined in http package:
func HttpParser(rawRequest string) (HttpRequest, error) {
var request HttpRequest
if len(rawRequest) == 0 {
return HttpRequest{}, errors.New("empty http request")
}
if request.Headers == nil {
request.Headers = make(map[string]string)
}
scanner := bufio.NewScanner(strings.NewReader(rawRequest))
scanner.Scan()
line := strings.Split(scanner.Text(), " ")
request.Method = line[0]
request.Url = line[1]
for scanner.Scan() {
line := scanner.Text()
if line == "" {
break
}
header := strings.Split(line, ":")
request.Headers[header[0]] = header[1]
}
for scanner.Scan() {
request.Body += scanner.Text()
}
return request, nil
}
The function checks if the raw request is empty, returns an error if so, then initializes an empty headers map if not already created.
Uses bufio.NewScanner to read the request line by line splitting the first line to extract HTTP method and URL and set it on request.Method and request.Url
After the first line scans through subsequent lines until an empty line, and for each line splits each header line by β:β and stores headers in request.Headers map.
After headers, continues scanning to collect request body.
Conclusion
Now we have a functional http parser for converting raw HTTP request string into a structured HttpRequest object.
In the next steps, I would like to delve deeper into what happens if the requests exceed 1024 bytes.
req := make([]byte, 1024)
n, err := con.Read(req)
Currently, the server handles each request over a fixed length of 1024 bytes, but what happens if the request exceeds that size?