diff --git a/app/http.go b/app/http.go new file mode 100644 index 0000000..c5eb472 --- /dev/null +++ b/app/http.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "net" + "regexp" + "strconv" +) + +const ( + HTTPDelimiter = "\r\n" +) + +type Header struct { + Name string + Value string +} +type Request struct { + Method string + Path string + Version string + Body string + Headers []Header +} + +type HttpCode struct { + Code uint + Message string +} + +var ( + BadRequest = HttpCode{Code: 400, Message: "Bad Response"} + NotFound = HttpCode{Code: 404, Message: "Not Found"} + OK = HttpCode{Code: 200, Message: "OK"} +) + +type Response struct { + Code HttpCode + Version string + Body string + BodyRaw []byte + Headers []Header +} + +type StringRoute struct { + path string + method string + handler func(req Request) Response +} + +type RegexRoute struct { + regex *regexp.Regexp + method string + handler func(req Request, matches []string) Response +} + +type Routes struct { + stringRoutes []StringRoute + regexpRoutes []RegexRoute +} + +// func createHandler(routes Routes) func(conn net.Conn) { +// return func(conn net.Conn) { + +// for _, route := range(routes.stringRoutes){ + +// } + +// } +// } + +func Respond(conn net.Conn, response Response) { + fmt.Fprintf(conn, "%s %d %s%s", response.Version, response.Code.Code, response.Code.Message, HTTPDelimiter) + bodySize := 0 + if response.Body != "" { + bodySize = len(response.Body) + } else { + bodySize = len(response.BodyRaw) + } + if bodySize > 0 { + response.Headers = append(response.Headers, Header{Name: "Content-Length", Value: strconv.Itoa(bodySize)}) + } + for _, header := range response.Headers { + fmt.Fprintf(conn, "%s: %s%s", header.Name, header.Value, HTTPDelimiter) + } + + fmt.Fprint(conn, HTTPDelimiter) + if bodySize > 0 { + if response.Body != "" { + fmt.Fprint(conn, response.Body) + } else { + conn.Write(response.BodyRaw) + } + } + + // fmt.Fprintf(conn, "HTTP/1.1 %d %s%s%s", response.Code, response.Message, HTTPDelimiter, HTTPDelimiter) +} diff --git a/app/server.go b/app/server.go index b5e7d3f..ec5bd3a 100644 --- a/app/server.go +++ b/app/server.go @@ -3,79 +3,25 @@ package main import ( "fmt" "log" - "path/filepath" - "regexp" - "strconv" - "strings" - - // Uncomment this block to pass the first stage "net" "os" + "path/filepath" + "regexp" + "strings" ) -const ( - HTTPDelimiter = "\r\n" -) - -type Header struct { - Name string - Value string -} -type Request struct { - Method string - Path string - Version string - Body string - Headers []Header -} - -type HttpCode struct { - Code uint - Message string -} - -var ( - BadRequest = HttpCode{Code: 400, Message: "Bad Response"} - NotFound = HttpCode{Code: 404, Message: "Not Found"} - OK = HttpCode{Code: 200, Message: "OK"} -) - -type Response struct { - Code HttpCode - Version string - Body string - BodyRaw []byte - Headers []Header -} - -func Respond(conn net.Conn, response Response) { - fmt.Fprintf(conn, "%s %d %s%s", response.Version, response.Code.Code, response.Code.Message, HTTPDelimiter) - bodySize := 0 - if response.Body != "" { - bodySize = len(response.Body) - } else { - bodySize = len(response.BodyRaw) +func getFilepath(filename string) string { + if len(os.Args) != 3 { + log.Fatal("Not enough args") } - if bodySize > 0 { - response.Headers = append(response.Headers, Header{Name: "Content-Length", Value: strconv.Itoa(bodySize)}) + dir, err := filepath.Abs(os.Args[2]) + if err != nil { + log.Fatal(err) } - for _, header := range response.Headers { - fmt.Fprintf(conn, "%s: %s%s", header.Name, header.Value, HTTPDelimiter) - } - - fmt.Fprint(conn, HTTPDelimiter) - if bodySize > 0 { - if response.Body != "" { - fmt.Fprint(conn, response.Body) - } else { - conn.Write(response.BodyRaw) - } - } - - // fmt.Fprintf(conn, "HTTP/1.1 %d %s%s%s", response.Code, response.Message, HTTPDelimiter, HTTPDelimiter) + return filepath.Join(dir, filename) } -func handleConnection(conn net.Conn) { +func handleConnection(conn net.Conn, routes Routes) { defer conn.Close() buffer := make([]byte, 1024) n, err := conn.Read(buffer) @@ -116,92 +62,136 @@ func handleConnection(conn net.Conn) { } fmt.Println(request) - if request.Path == "/" { - Respond(conn, Response{Version: request.Version, Code: OK}) - return - } - - if request.Path == "/user-agent" { - for _, header := range request.Headers { - if header.Name != "User-Agent" { - continue - } - Respond(conn, Response{ - Version: request.Version, - Code: OK, - Body: header.Value, - Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, - }) + for _, route := range routes.stringRoutes { + if request.Path == route.path && request.Method == route.method { + Respond(conn, route.handler(request)) return } - Respond(conn, Response{Version: request.Version, Code: BadRequest}) } - reFiles := regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)$`) - if matches := reFiles.FindStringSubmatch(request.Path); len(matches) > 0 { - - if len(os.Args) != 3 { - log.Fatal("Not enough args") + for _, route := range routes.regexpRoutes { + if request.Method != route.method { + continue } - dir, err := filepath.Abs(os.Args[2]) - if err != nil { - log.Fatal(err) - } - filename := filepath.Join(dir, matches[1]) - - // _, err:=os.Stat(filename) - // fmt.Println(file) - file, err := os.ReadFile(filename) - if os.IsNotExist(err) { - Respond(conn, Response{Version: request.Version, Code: NotFound}) + if matches := route.regex.FindStringSubmatch(request.Path); len(matches) > 0 { + Respond(conn, route.handler(request, matches)) return } - if err != nil { - log.Fatal(err) - } - - Respond(conn, Response{ - Version: request.Version, - Code: OK, - BodyRaw: file, - Headers: []Header{{Name: "Content-Type", Value: "application/octet-stream"}}, - }) - return } - reEcho := regexp.MustCompile(`^/echo/([A-Za-z]+)$`) - if matches := reEcho.FindStringSubmatch(request.Path); len(matches) > 0 { - Respond(conn, Response{ - Version: request.Version, - Code: OK, - Body: matches[1], - Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, - }) - return - } + // if request.Path == "/" { + // Respond(conn, Response{Version: request.Version, Code: OK}) + // return + // } + + // if request.Path == "/user-agent" { + // for _, header := range request.Headers { + // if header.Name != "User-Agent" { + // continue + // } + // Respond(conn, Response{ + // Version: request.Version, + // Code: OK, + // Body: header.Value, + // Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, + // }) + // return + // } + // Respond(conn, Response{Version: request.Version, Code: BadRequest}) + // } + + // reFiles := regexp.MustCompile(`^/files/([A-Za-z0-9_\-.]+)$`) + // if matches := reFiles.FindStringSubmatch(request.Path); len(matches) > 0 { + // filename := getFilepath(matches[1]) + // file, err := os.ReadFile(filename) + // if os.IsNotExist(err) { + // Respond(conn, Response{Version: request.Version, Code: NotFound}) + // return + // } + // if err != nil { + // log.Fatal(err) + // } + + // Respond(conn, Response{ + // Version: request.Version, + // Code: OK, + // BodyRaw: file, + // Headers: []Header{{Name: "Content-Type", Value: "application/octet-stream"}}, + // }) + // return + // } + + // reEcho := regexp.MustCompile(`^/echo/([A-Za-z]+)$`) + // if matches := reEcho.FindStringSubmatch(request.Path); len(matches) > 0 { + // Respond(conn, Response{ + // Version: request.Version, + // Code: OK, + // Body: matches[1], + // Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, + // }) + // return + // } Respond(conn, Response{Version: request.Version, Code: NotFound}) } func main() { - // You can use print statements as follows for debugging, they'll be visible when running tests. fmt.Println("Logs from your program will appear here!") - // Uncomment this block to pass the first stage - // l, err := net.Listen("tcp", "0.0.0.0:4221") if err != nil { fmt.Println("Failed to bind to port 4221") os.Exit(1) } + routes := Routes{ + stringRoutes: []StringRoute{ + // ROOT + {path: "/", method: "GET", handler: func(req Request) Response { + return Response{Version: req.Version, Code: OK} + }}, + + // USER AGENT + {path: "/user-agent", method: "GET", handler: func(req Request) Response { + for _, header := range req.Headers { + if header.Name != "User-Agent" { + continue + } + return Response{ + Version: req.Version, + Code: OK, + Body: header.Value, + Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, + } + } + return Response{Version: req.Version, Code: BadRequest} + }}, + }, + + regexpRoutes: []RegexRoute{ + + // PATH PARAMETER + { + regex: regexp.MustCompile(`^/echo/([A-Za-z]+)$`), + method: "GET", + handler: func(req Request, matches []string) Response { + return Response{ + Version: req.Version, + Code: OK, + Body: matches[1], + Headers: []Header{{Name: "Content-Type", Value: "text/plain"}}, + } + }}, + }, + } + for { conn, err := l.Accept() if err != nil { fmt.Println("Error accepting connection: ", err.Error()) os.Exit(1) } - go handleConnection(conn) + go handleConnection(conn, routes) } }