commit b740f57488945b90d9629ea63508ec5858a6b783 Author: Tanishq Dubey Date: Mon Jul 15 18:35:32 2024 -0400 Basic server diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..24cf7a4 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.dws.rip/dwshttp + +go 1.22.5 diff --git a/main.go b/main.go new file mode 100644 index 0000000..10c2caa --- /dev/null +++ b/main.go @@ -0,0 +1,210 @@ +package main + +import ( + "errors" + "fmt" + "io" + "log" + "net" + "os" + "strings" + "time" +) + +type HTTPMethod string + +const ( + HTTPMETHOD_GET HTTPMethod = "GET" + HTTPMETHOD_POST HTTPMethod = "POST" +) + +/* + A request-line begins with a method token, followed by a single space + +(SP), the request-target, another single space (SP), the protocol +version, and ends with CRLF. + + request-line = method SP request-target SP HTTP-version CRLF + HTTP-version = HTTP-name "/" DIGIT "." DIGIT +*/ +type RequestLine struct { + Method HTTPMethod + RequestTarget string + HTTPVersion string +} + +/* +The first line of a response message is the status-line, consisting +of the protocol version, a space (SP), the status code, another +space, a possibly empty textual phrase describing the status code, +and ending with CRLF. + + status-line = HTTP-version SP status-code SP reason-phrase CRLF +*/ +type StatusLine struct { +} + +/* +All HTTP/1.1 messages consist of a start-line followed by a sequence +of octets in a format similar to the Internet Message Format +[RFC5322]: zero or more header fields (collectively referred to as +the "headers" or the "header section"), an empty line indicating the +end of the header section, and an optional message body. + + HTTP-message = start-line + *( header-field CRLF ) + CRLF + [ message-body ] +*/ +type HTTPRequest struct { + StartLine RequestLine +} + +type HTTPResponse struct { + StartLine StatusLine +} + +func printDiff(s1, s2 string) { + length := len(s1) + if len(s2) > length { + length = len(s2) + } + + for i := 0; i < length; i++ { + var char1, char2 byte + + if i < len(s1) { + char1 = s1[i] + } else { + char1 = ' ' // padding for shorter string + } + + if i < len(s2) { + char2 = s2[i] + } else { + char2 = ' ' // padding for shorter string + } + + if char1 != char2 { + fmt.Printf("Difference at index %d: '%c' != '%c'\n", i, char1, char2) + } + } +} + +func ParseHTTPRequest(b []byte) (HTTPRequest, error) { + ret := HTTPRequest{} + rs := string(b[:]) + + method, rs, found := strings.Cut(rs, " ") + if !found { + return ret, errors.New("could not find method in request") + } + + method = strings.ToUpper(strings.TrimSpace(method)) + ret.StartLine = RequestLine{} + switch method { + case string(HTTPMETHOD_GET): + ret.StartLine.Method = HTTPMETHOD_GET + case string(HTTPMETHOD_POST): + ret.StartLine.Method = HTTPMETHOD_POST + default: + if method != "GET" { + result1 := strings.Compare("GET", method) + fmt.Println(result1) + fmt.Println(len("GET"), len(method)) + } + + return ret, fmt.Errorf("unsupported method '%s'", method) + } + + rt, rs, found := strings.Cut(rs, " ") + if !found { + return ret, errors.New("could not find target in request") + } + ret.StartLine.RequestTarget = rt + + hv, rs, found := strings.Cut(rs, "\r\n") + if !found { + return ret, errors.New("could not find http version in request") + } + if hv != "HTTP/1.0" && hv != "HTTP/1.1" { + return ret, fmt.Errorf("unsupported http version %s", hv) + } + ret.StartLine.HTTPVersion = hv + fmt.Println("rm: ", rs) + + return ret, nil +} + +func main() { + logger := log.New(os.Stdout, "DWSHTTP", log.LstdFlags) + + l, err := net.Listen("tcp", ":4221") + if err != nil { + logger.Fatal(err) + } + defer l.Close() + logger.Print("Started server on 4221") + + for { + conn, err := l.Accept() + if err != nil { + logger.Fatal(err) + } + + logger.Printf("New connection: %s", conn.RemoteAddr().String()) + go func(c net.Conn) { + defer c.Close() + + bSize := 0 + rSize := 256 + buf := make([]byte, bSize) + tmp := make([]byte, rSize) + err := c.SetReadDeadline(time.Now().Add(time.Second * 5)) + if err != nil { + logger.Printf("ERROR %s - (%s) %s", c.RemoteAddr().String(), "connsetup", err) + } + + for { + n, err := c.Read(tmp) + if err != nil { + if err != io.EOF { + logger.Fatalf("ERROR %s - (%s) %s", c.RemoteAddr().String(), "unexpected error while reading", err) + } + break + } + buf = append(buf, tmp[:n]...) + if n > 0 { + if n < rSize { + break + } + continue + } + } + + req, err := ParseHTTPRequest(buf) + if err != nil { + logger.Printf("ERROR %s - (%s) %s", c.RemoteAddr().String(), "parsing", err) + bw, err := c.Write([]byte("HTTP/1.1 500 Internal Server Error\r\n\r\n")) + if err != nil { + logger.Printf("ERROR %s - (Could not write err) %s", c.RemoteAddr().String(), err) + return + } + if bw < 1 { + return + } + } + + logger.Printf("%s - %s - %s -> 200", req.StartLine.Method, req.StartLine.HTTPVersion, req.StartLine.RequestTarget) + bw, err := c.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) + if err != nil { + logger.Printf("ERROR %s - (Could not write err) %s", c.RemoteAddr().String(), err) + return + } + if bw < 1 { + return + } + + }(conn) + } +}