Skip to content

Commit

Permalink
Refactor and implement headers parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
Anwar Ziani committed Oct 20, 2023
1 parent e87f49d commit a46716e
Showing 1 changed file with 73 additions and 27 deletions.
100 changes: 73 additions & 27 deletions app/server.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package main

import (
"bufio"
"fmt"
"log"
"net"
"strings"
)

const (
lineTerminator = "\r\n"
port = "4221"
lineTerminator = "\r\n"
doubleLineTerminator = "\r\n\r\n"
port = "4221"
)

type Request struct {
method string
path string
method string
path string
headers map[string]string
body string
}

type Response struct {
Expand All @@ -40,30 +42,55 @@ func main() {
handleConn(conn)
}

func parseRequest(conn net.Conn) (*Request, error) {
lines := readRequestLines(conn)
method, path, ok := parseStatusLineParts(lines[0])
func readRequest(conn net.Conn) (*Request, error) {
buffer := make([]byte, 4096)
n, err := conn.Read(buffer)
if err != nil {
return nil, err
}
return NewRequest(string(buffer[0:n]))
}

func NewRequest(s string) (*Request, error) {
req := &Request{headers: map[string]string{}}

statusLineAndHeaders, body, ok := strings.Cut(s, doubleLineTerminator)
if !ok {
return nil, fmt.Errorf("failed to parse request status line %s", lines[0])
return nil, fmt.Errorf("invalid request")
}
req := &Request{
method: method,
path: path,

statusLine, headers, ok := strings.Cut(statusLineAndHeaders, lineTerminator)
if !ok {
return nil, fmt.Errorf("invalid request")
}
return req, nil
}

func readRequestLines(conn net.Conn) []string {
lines := []string{}
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil || line == "\r\n" {
break
// fmt.Printf("%q\n", statusLine)
// fmt.Printf("%q\n", headers)
// fmt.Printf("%q\n", body)

// Parse status line (first line)
method, path, ok := parseStatusLineParts(statusLine)
if !ok {
return nil, fmt.Errorf("failed to parse request status line %s", statusLine)
}
req.method = method
req.path = path

// Parse headers
if len(headers) > 0 {
for _, h := range strings.Split(headers, lineTerminator) {
k, v, _ := strings.Cut(h, ":")
req.headers[strings.ToLower(strings.TrimSpace(k))] = strings.ToLower(strings.TrimSpace(v))
}
lines = append(lines, line)
}
return lines

// Parse body
if len(body) > 0 {
fmt.Println("body:", body)
req.body = body
}

return req, nil
}

func parseStatusLineParts(statusLine string) (string, string, bool) {
Expand All @@ -79,7 +106,7 @@ func handleConn(conn net.Conn) {
defer conn.Close()

// Read and parse request
req, err := parseRequest(conn)
req, err := readRequest(conn)
if err != nil {
log.Printf("failed to parse request: %v\n", err)
return
Expand Down Expand Up @@ -128,13 +155,32 @@ func handleRequest(req *Request) Response {
return Response{status: "HTTP/1.1 200 OK", headers: defaultHeaders}
}
if strings.HasPrefix(req.path, "/echo/") {
return handleEcho(req.path)
return handleGetEcho(req)
}
if strings.HasPrefix(req.path, "/user-agent") {
return handleGetUserAgent(req)
}
return Response{status: "HTTP/1.1 404 Not Found", headers: defaultHeaders}
}

func handleEcho(path string) Response {
path = strings.TrimPrefix(path, "/")
func handleGetUserAgent(req *Request) Response {
body := ""
if v, ok := req.headers["user-agent"]; ok {
body = v
}

return Response{
status: "HTTP/1.1 200 OK",
body: body,
headers: map[string]string{
"Content-Type": "text/plain",
"Content-Length": fmt.Sprintf("%d", len(body)),
},
}
}

func handleGetEcho(req *Request) Response {
path := strings.TrimPrefix(req.path, "/")
_, content, _ := strings.Cut(path, "/")

return Response{
Expand Down

0 comments on commit a46716e

Please sign in to comment.