

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: Testing With Testify
incomplete
2: Parsing the Request Line
incomplete
3: Parsing a Stream
incomplete
4: Request Line Review
incomplete
5: State Machine
incomplete
6: Connect the Parsing
incomplete
This lesson's interactive features are locked, please to keep using them
Now it's time for the best part of any project, the string parsing! By building on top of TCP, we already have code that handles plain-text data, now we just need to take that plain text and turn it into structured data, ensuring that it follows the HTTP protocol.
For example, given:
POST /coffee HTTP/1.1
Host: localhost:42069
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 21
{"flavor":"dark mode"}
We want our HTTP parser to return a struct that looks like this:
type Request struct {
RequestLine RequestLine
Headers map[string]string
Body []byte
}
That way our application logic (the server code) has nicely structured HTTP data to work with.
Our goal is to take the server we created in the last section and have it parse out the start-line according to the RFC message parsing section.
Remember how HTTP messages start with a start-line? Well, if it's a request (not a response), then the start-line is called the request-line and has a specific format.
HTTP-version = HTTP-name "/" DIGIT "." DIGIT
HTTP-name = %s"HTTP"
request-line = method SP request-target SP HTTP-version
Which is a bit hairy to read (because its accounting for all the possible options), but for HTTP/1.1 it's pretty simple, an example request-line looks like this:
GET /coffee HTTP/1.1
For now, we're only going to parse the request-line.
Here we are, using test-driven-development, Uncle Bob would be proud.
On a real note, when it comes to blackbox RFCs like this, good tests help us move (blazingly) faster and avoid bugs.
type Request struct {
RequestLine RequestLine
}
type RequestLine struct {
HttpVersion string
RequestTarget string
Method string
}
Don't forget to capitalize your exports smh...
Hey, it's still better than public static void main...
// Test: Good GET Request line
r, err := RequestFromReader(strings.NewReader("GET / HTTP/1.1\r\nHost: localhost:42069\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n"))
require.NoError(t, err)
require.NotNil(t, r)
assert.Equal(t, "GET", r.RequestLine.Method)
assert.Equal(t, "/", r.RequestLine.RequestTarget)
assert.Equal(t, "1.1", r.RequestLine.HttpVersion)
// Test: Good GET Request line with path
r, err = RequestFromReader(strings.NewReader("GET /coffee HTTP/1.1\r\nHost: localhost:42069\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n"))
require.NoError(t, err)
require.NotNil(t, r)
assert.Equal(t, "GET", r.RequestLine.Method)
assert.Equal(t, "/coffee", r.RequestLine.RequestTarget)
assert.Equal(t, "1.1", r.RequestLine.HttpVersion)
// Test: Invalid number of parts in request line
_, err = RequestFromReader(strings.NewReader("/coffee HTTP/1.1\r\nHost: localhost:42069\r\nUser-Agent: curl/7.81.0\r\nAccept: */*\r\n\r\n"))
require.Error(t, err)
When you're done with each lesson, look at the tests in the solution files to see if I did anything you didn't think of.
Don't worry about connecting the request package to any application (cmd) code yet - let's just get our tests passing first.
Run and submit the CLI tests.