dwshttp/main.go
2024-07-15 18:35:32 -04:00

211 lines
4.6 KiB
Go

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