Failing in Stage #FS3, Build HTTP server chanllenge, using Go

I’m stuck on Stage #FS3.

I’ve implemented the functionality and it works on my machine but fails on some test.

Here are my logs:

remote: [tester::#FS3] Running tests for Stage #FS3 (Read header)
remote: [tester::#FS3] $ ./your_program.sh
remote: [tester::#FS3] $ curl -v http://localhost:4221/user-agent -H "User-Agent: apple/grape-mango"
remote: [your_program] panic: runtime error: index out of range [1] with length 1
remote: [your_program] 
remote: [your_program] goroutine 1 [running]:
remote: [your_program] main.main()
remote: [your_program]  /app/app/server.go:44 +0x5aa
remote: [tester::#FS3] Failed to read response: 
remote: [tester::#FS3] Received: "" (no content received)
remote: [tester::#FS3]            ^ error
remote: [tester::#FS3] Error: Expected: HTTP-version, Received: ""
remote: [tester::#FS3] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details)

And here’s a snippet of my code:

package main

import (
	"fmt"
	"net"
	"os"
	"strings"
)

func main() {

	l, err := net.Listen("tcp", "0.0.0.0:4221")
	if err != nil {
		fmt.Println("Failed to bind to port 4221")
		os.Exit(1)
	}

	defer l.Close()

	conn, er := l.Accept()
	if er != nil {
		fmt.Println("Error accepting connection: ", err.Error())
		os.Exit(1)
	}

	buf := make([]byte, 1024)
	_, err = conn.Read(buf)
	if err != nil {
		fmt.Println("Error reading connection: ", err.Error())
		os.Exit(1)
	}

	req := string(buf)
	lines := strings.Split(req, "\r\n")
	path := strings.Split(lines[0], " ")[1]
	tokens := strings.Split(path, "/")
	res := ""

	if path == "/" {
		res = "HTTP/1.1 200 OK\r\n\r\n"
	} else if len(tokens) > 1 && tokens[1] == "echo" {
		res = fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(tokens[2]), tokens[2])
	} else if len(tokens) > 1 && strings.ToLower(tokens[1]) == "user-agent" {
		user_agent := strings.Split(lines[3], " ")[1]
		res = fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(user_agent), user_agent)
	} else {
		res = "HTTP/1.1 404 Not Found\r\n\r\n"
	}

	conn.Write([]byte(res))
}

Hey @mennatawfiq, could you upload your code to GitHub and share the link? It will be much easier to debug if I can run it directly.

1 Like

Thanks in advance! Here’s the repo link GitHub - mennatawfiq/Go-http-server: A Goland http server built from scratch to satisfy my curiosity. PS: still in progress.

Sorry! Had to delete the previous answer as it was an incorrect solution. I updated the code to print out the values of the all the lines like this:

    req := string(buf)
	lines := strings.Split(req, "\r\n")
	for i, line := range lines {
		fmt.Printf("lines[%d]: '%s'\n", i, line)
	}
	path := strings.Split(lines[0], " ")[1]

Now if you would run the tests, you will see the following logs:

[stage-5] Sent bytes: "GET /user-agent HTTP/1.1\r\nHost: localhost:4221\r\nUser-Agent: apple/grape\r\n\r\n"
[your_program] lines[0]: 'GET /user-agent HTTP/1.1'
[your_program] lines[1]: 'Host: localhost:4221'
[your_program] lines[2]: 'User-Agent: apple/grape'
[your_program] lines[3]: ''
[your_program] lines[4]: ''
[your_program] lines[3]: ''
[your_program] panic: runtime error: index out of range [1] with length 1

You can see now that the variable lines[3] is an empty string, which causes the panic on splitting and accessing [1] in the handler.

This is because you can’t always assume the User-Agent header is in the third index! It works locally because curl constructs the headers in a particular order (and keeps it at the third index), but the tester does not!

HTTP also does not guarantee any specific ordering of the headers. You should probably loop over all the header lines and see where the User-Agent header lives (if it does!) and then only proceed further. This might also motivate writing a more general parsing for headers, and their ordering and lengths would change between tests!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.