diff --git a/cmd/gwstester/tester.go b/cmd/gwstester/tester.go new file mode 100644 index 0000000..21a382d --- /dev/null +++ b/cmd/gwstester/tester.go @@ -0,0 +1,59 @@ +package main + +import ( + "bufio" + "fmt" + "net" + "os" + "time" + "git.xdrm.io/gws/http/upgrade/request" +) + +func main() { + + startTime := time.Now().UnixNano() + + /* (1) Create listening socket + ---------------------------------------------------------*/ + /* (1) Create socket */ + lsock, err := net.Listen("tcp", ":4444") + if err != nil { + panic(err) + } + + /* (2) Accept clients */ + for { + + sock, err := lsock.Accept() + if err != nil { + os.Stderr.WriteString(fmt.Sprintf("Connection error: %s\n", err)) + return + } + + fmt.Printf("+ new client\n") + + go manageClient(sock) + + } + + fmt.Printf("Elapsed: %d ns\n", time.Now().UnixNano()-startTime) + +} + +func manageClient(sock net.Conn) { + + defer sock.Close() + + for { + + reader := bufio.NewReader(sock) + + _, err := request.Parse(reader) + if err != nil { + panic(err) + } + + sock.Write([]byte("coucou")) + + } +} diff --git a/http/upgrade/request/private.go b/http/upgrade/request/private.go new file mode 100644 index 0000000..9c52dfd --- /dev/null +++ b/http/upgrade/request/private.go @@ -0,0 +1,78 @@ +package request + +import ( + "strconv" + "fmt" + "strings" +) + + +func (r *T) parseHeader(b []byte) { + + /* (1) First line -> GET {uri} HTTP/{version} + ---------------------------------------------------------*/ + if !r.first { + + /* (1) Split by ' ' */ + parts := strings.Split(string(b), " ") + + /* (2) Abort on failure */ + if len(parts) != 3 { + return + } + + /* (3) Store method */ + r.httpMethod = parts[0] + + /* (4) Store URI */ + r.uri = parts[1] + + /* (5) Fail if wrong version format */ + if len(parts[2]) < len("HTTP/0") { + r.Err = fmt.Errorf("Invalid HTTP version [%s] expected [HTTP/] prefix") + return + } + + /* (6) Extract version part */ + versionString := parts[2][len("HTTP/"):] + version, err := strconv.ParseFloat(versionString, 32) + + if err != nil { + r.Err = fmt.Errorf("Cannot parse HTTP version as float") + return + } + + /* (7) Store HTTP version */ + r.httpVersion = float32(version) + + + r.first = true + return + + } + + + /* (2) Other lines -> Header-Name: Header-Value + ---------------------------------------------------------*/ + + /* (1) Split by ': ' */ + parts := strings.Split(string(b), ": ") + + /* (2) Abort on failure */ + if len(parts) != 2 { + return + } + + /* (3) Match header */ + switch strings.ToLower(parts[0]) { + case "host": fmt.Printf("[host] '%s'\n", parts[1]) + case "upgrade": fmt.Printf("[upgrade] '%s'\n", parts[1]) + case "connection": fmt.Printf("[connection] '%s'\n", parts[1]) + case "sec-websocket-key": fmt.Printf("[sec-websocket-key] '%s'\n", parts[1]) + case "origin": fmt.Printf("[origin] '%s'\n", parts[1]) + case "sec-websocket-protocol": fmt.Printf("[sec-websocket-protocol] '%s'\n", parts[1]) + case "sec-websocket-version": fmt.Printf("[sec-websocket-version] '%s'\n", parts[1]) + + } + +} \ No newline at end of file diff --git a/http/upgrade/request/public.go b/http/upgrade/request/public.go new file mode 100644 index 0000000..83049b7 --- /dev/null +++ b/http/upgrade/request/public.go @@ -0,0 +1,56 @@ +package request + +import ( + "git.xdrm.io/gws/internal/http/reader" + "fmt" + "io" +) + +// Parse parses a byte array build a request object +func Parse(r io.Reader) (request *T, err error) { + + /* (1) Get chunk reader */ + cr, err := reader.NewReader(r) + if err != nil { + return nil, fmt.Errorf("Error while creating chunk reader: %s", err) + } + + /* (2) Init request */ + req := new(T) + + /* (3) Parse header line by line */ + for { + + line, err := cr.Read() + if err == io.EOF { + fmt.Printf("END OF READING\n"); + break + } + + if err != nil { + return nil, fmt.Errorf("Cannot read from reader: %s", err) + } + + req.parseHeader(line) + + if req.Err != nil { + return nil, fmt.Errorf("Parsing error: %s\n", req.Err); + } + + // fmt.Printf("Read line: %s\n", line) + + } + + + + + /* (2) GET {uri} HTTP/{version} + ---------------------------------------------------------*/ + /* (1) Break apart */ + + + + /* (1) Check method */ + return nil, nil + +} diff --git a/http/upgrade/request/types.go b/http/upgrade/request/types.go new file mode 100644 index 0000000..5a7d506 --- /dev/null +++ b/http/upgrade/request/types.go @@ -0,0 +1,17 @@ +package request + +// T represents an HTTP Upgrade request +type T struct { + httpMethod string + uri string + httpVersion float32 + host string + origin string + key []byte + protocols []string + version int + + first bool // whether the first line has been read (GET uri HTTP/version) + + Err error +} diff --git a/internal/http/reader/reader.go b/internal/http/reader/reader.go new file mode 100644 index 0000000..25fd5b3 --- /dev/null +++ b/internal/http/reader/reader.go @@ -0,0 +1,83 @@ +package reader + +// DISCLAIMER +// ---------- +// Some of the content of this file is inspired or copied from +// the golang standard library + +import ( + "fmt" + "io" + "bufio" +) + +// Maximum line length +var maxLineLength = 4096 + +// Chunk reader +type chunkReader struct { + reader *bufio.Reader // the reader + isEnded bool // If we are done (2 consecutive CRLF) +} + + +// New creates a new reader +func NewReader(r io.Reader) (reader *chunkReader, err error) { + + br, ok := r.(*bufio.Reader) + if !ok { + return nil, fmt.Errorf("Invalid reader (must implement bufio.Reader") + } + + return &chunkReader{reader: br}, nil + +} + + + +// Read reads a chunk, err is io.EOF when done +func (r *chunkReader) Read() ([]byte, error){ + + /* (1) If already ended */ + if r.isEnded { + return nil, io.EOF + } + + /* (2) Read line */ + var line []byte + line, err := r.reader.ReadSlice('\n') + + /* (3) manage errors */ + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + + if err != nil { + return nil, err + } + + /* (4) Trim */ + line = removeTrailingSpace(line) + + /* (5) Manage ending line */ + if len(line) == 0 { + r.isEnded = true + return line, io.EOF + } + + return line, nil + +} + + + +func removeTrailingSpace(b []byte) []byte{ + for len(b) > 0 && isASCIISpace(b[len(b)-1]) { + b = b[:len(b)-1] + } + return b +} + +func isASCIISpace(b byte) bool { + return b == ' ' || b == '\t' || b == '\r' || b =='\n' +} \ No newline at end of file