This commit is contained in:
Niccolo Borgioli 2024-05-26 02:04:59 +02:00
parent 899f7828a5
commit de8a68ac05
No known key found for this signature in database
GPG Key ID: 4897ACD13A65977C
6 changed files with 44 additions and 45 deletions

View File

@ -5,7 +5,7 @@ import (
"compress/gzip" "compress/gzip"
) )
func gzipCompress(data []byte) *bytes.Buffer { func gzipCompress(data []byte) []byte {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
gz := gzip.NewWriter(buf) gz := gzip.NewWriter(buf)
if _, err := gz.Write(data); err != nil { if _, err := gz.Write(data); err != nil {
@ -14,7 +14,5 @@ func gzipCompress(data []byte) *bytes.Buffer {
if err := gz.Close(); err != nil { if err := gz.Close(); err != nil {
panic(err) panic(err)
} }
return buf.Bytes()
// fmt.Println("Hexadecimal Representation:", hex.EncodeToString(buf.Bytes()))
return buf
} }

View File

@ -9,8 +9,8 @@ import (
var DIR string = "" var DIR string = ""
func getFilepath(filename string) string { func getFilepath(filename string) string {
// Set the DIR if not already set
if DIR == "" { if DIR == "" {
if len(os.Args) != 3 { if len(os.Args) != 3 {
log.Fatal("Not enough args") log.Fatal("Not enough args")
} }

View File

@ -13,10 +13,6 @@ const (
HTTPDelimiter = "\r\n" HTTPDelimiter = "\r\n"
) )
// type Header struct {
// Name string
// Value string
// }
type Request struct { type Request struct {
Method string Method string
Path string Path string
@ -64,37 +60,44 @@ type Routes struct {
} }
func Respond(conn net.Conn, req Request, res Response) { func Respond(conn net.Conn, req Request, res Response) {
// Create headers if not existent
if res.Headers == nil { if res.Headers == nil {
res.Headers = make(map[string]string) res.Headers = make(map[string]string)
} }
// isGzip := false // Base response line
isGzip := strings.Contains(req.Headers["Accept-Encoding"], "gzip")
// if isGzip {
// res.Headers["Content-Encoding"] = "gzip"
// }
fmt.Fprintf(conn, "%s %d %s%s", res.Version, res.Code.Code, res.Code.Message, HTTPDelimiter) fmt.Fprintf(conn, "%s %d %s%s", res.Version, res.Code.Code, res.Code.Message, HTTPDelimiter)
var body []byte var body []byte
if res.Body != "" { if res.Body != "" {
body = []byte(res.Body) body = []byte(res.Body)
} else { } else {
body = res.BodyRaw body = res.BodyRaw
} }
if isGzip && len(body) > 0 {
res.Headers["Content-Encoding"] = "gzip"
body = gzipCompress(body).Bytes()
}
bodySize := len(body) bodySize := len(body)
// Check if gzip is accepted and encode the body
isGzip := strings.Contains(req.Headers["Accept-Encoding"], "gzip")
if isGzip && bodySize > 0 {
body = gzipCompress(body)
bodySize = len(body)
res.Headers["Content-Encoding"] = "gzip"
}
// Set the size of the content
if bodySize > 0 { if bodySize > 0 {
res.Headers["Content-Length"] = strconv.Itoa(bodySize) res.Headers["Content-Length"] = strconv.Itoa(bodySize)
} }
// Write headers
for header, value := range res.Headers { for header, value := range res.Headers {
fmt.Fprintf(conn, "%s: %s%s", header, value, HTTPDelimiter) fmt.Fprintf(conn, "%s: %s%s", header, value, HTTPDelimiter)
} }
// Delimiter for the body, always present
fmt.Fprint(conn, HTTPDelimiter) fmt.Fprint(conn, HTTPDelimiter)
// Write body if available
if bodySize > 0 { if bodySize > 0 {
conn.Write(body) conn.Write(body)
} }
@ -112,6 +115,7 @@ func parseRequest(conn net.Conn) (Request, bool) {
request := Request{Headers: map[string]string{}} request := Request{Headers: map[string]string{}}
isBody := false isBody := false
for i, part := range parts { for i, part := range parts {
// The first part is always the basic information
if i == 0 { if i == 0 {
head := strings.Split(part, " ") head := strings.Split(part, " ")
if len(head) != 3 { if len(head) != 3 {
@ -123,6 +127,7 @@ func parseRequest(conn net.Conn) (Request, bool) {
continue continue
} }
// Body
if isBody { if isBody {
request.Body = part request.Body = part
break break
@ -130,6 +135,7 @@ func parseRequest(conn net.Conn) (Request, bool) {
// Headers // Headers
if part == "" { if part == "" {
// If the header is "empty" it means that we have arrived at the body, and we'll skip to it
isBody = true isBody = true
continue continue
} }

View File

@ -43,6 +43,7 @@ var routes = Routes{
}, },
}, },
// Read file
{ {
regex: regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)`), regex: regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)`),
method: "GET", method: "GET",
@ -60,6 +61,7 @@ var routes = Routes{
}, },
}, },
// Write file
{ {
regex: regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)`), regex: regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)`),
method: "POST", method: "POST",

View File

@ -6,17 +6,6 @@ import (
"os" "os"
) )
// type Handler = func(req Request, res Response)
// type Middleware = func(next Handler) Handler
// var m Middleware = func(next Handler) Handler {
// return func(req Request, res Response) {
// fmt.Println("Start")
// next(req, res)
// fmt.Println("End")
// }
// }
func handleConnection(conn net.Conn, routes Routes) { func handleConnection(conn net.Conn, routes Routes) {
defer conn.Close() defer conn.Close()
@ -28,6 +17,7 @@ func handleConnection(conn net.Conn, routes Routes) {
fmt.Println(req) fmt.Println(req)
// Loop over the available routes. First string, then regexp
for _, route := range routes.stringRoutes { for _, route := range routes.stringRoutes {
if req.Path == route.path && req.Method == route.method { if req.Path == route.path && req.Method == route.method {
Respond(conn, req, route.handler(req)) Respond(conn, req, route.handler(req))
@ -45,12 +35,11 @@ func handleConnection(conn net.Conn, routes Routes) {
} }
} }
// Catch all 404
Respond(conn, req, Response{Version: req.Version, Code: NotFound}) Respond(conn, req, Response{Version: req.Version, Code: NotFound})
} }
func main() { func main() {
fmt.Println("Logs from your program will appear here!")
l, err := net.Listen("tcp", "0.0.0.0:4221") l, err := net.Listen("tcp", "0.0.0.0:4221")
if err != nil { if err != nil {
fmt.Println("Failed to bind to port 4221") fmt.Println("Failed to bind to port 4221")

View File

@ -38,8 +38,11 @@ func checkResponse(t *testing.T, res *http.Response, expected Expected) {
t.Errorf(`Expected body to be "%s" but got "%s"`, expected.body, body) t.Errorf(`Expected body to be "%s" but got "%s"`, expected.body, body)
} }
log.Println("HEADERS", res.Header)
for header, value := range expected.headers { for header, value := range expected.headers {
if actual := res.Header[header][0]; actual != value { if res.Header[header] == nil {
t.Errorf(`Expected "%s" header to be present`, header)
} else if actual := res.Header[header][0]; actual != value {
t.Errorf(`Expected "%s" header to be "%s" but got "%s"`, header, value, actual) t.Errorf(`Expected "%s" header to be "%s" but got "%s"`, header, value, actual)
} }
} }
@ -64,23 +67,24 @@ func TestEcho(t *testing.T) {
}}) }})
} }
// func TestEchoGzip(t *testing.T) { func TestEchoGzip(t *testing.T) {
// input := "abc" input := "abc"
// req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:4221/echo/%s", input), nil) req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:4221/echo/%s", input), nil)
// req.Header.Set("Accept-Encoding", "gzip") req.Header.Set("Accept-Encoding", "gzip")
// client := &http.Client{} client := &http.Client{}
// res, _ := client.Do(req) res, _ := client.Do(req)
// checkResponse(t, res, Expected{status: 200, body: input, headers: map[string]string{ checkResponse(t, res, Expected{status: 200, body: input, headers: map[string]string{
// "Content-Length": strconv.Itoa(len(input)), "Content-Length": "27", // Size of gzip "abc"
// "Content-Type": "text/plain", "Content-Type": "text/plain",
// "Content-Encoding": "gzip", "Content-Encoding": "gzip",
// }}) }})
// } }
func TestUserAgent(t *testing.T) { func TestUserAgent(t *testing.T) {
input := "CodeCrafters/1.0" input := "CodeCrafters/1.0"
req, _ := http.NewRequest("GET", "http://localhost:4221/user-agent", nil) req, _ := http.NewRequest("GET", "http://localhost:4221/user-agent", nil)
req.Header.Del("")
req.Header.Set("User-Agent", input) req.Header.Set("User-Agent", input)
client := &http.Client{} client := &http.Client{}
res, _ := client.Do(req) res, _ := client.Do(req)