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) } }