From 98e1a4732834b376a367a4e5c6164088763a9f36 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 27 Mar 2024 14:06:13 +0100 Subject: [PATCH] Added URL parsing --- Request.go | 14 ++++++++ Server.go | 33 ++++++++++++++---- Server_test.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 12 deletions(-) diff --git a/Request.go b/Request.go index 954ff33..7c1d996 100644 --- a/Request.go +++ b/Request.go @@ -4,17 +4,26 @@ import "git.akyoto.dev/go/router" // Request is an interface for HTTP requests. type Request interface { + Host() string Method() string Path() string + Scheme() string } // request represents the HTTP request used in the given context. type request struct { + scheme string + host string method string path string params []router.Parameter } +// Host returns the requested host. +func (req *request) Host() string { + return req.host +} + // Method returns the request method. func (req *request) Method() string { return req.method @@ -25,6 +34,11 @@ func (req *request) Path() string { return req.path } +// Scheme returns either `http`, `https` or an empty string. +func (req request) Scheme() string { + return req.scheme +} + // addParameter adds a new parameter to the request. func (req *request) addParameter(key string, value string) { req.params = append(req.params, router.Parameter{ diff --git a/Server.go b/Server.go index 79ee63b..459356f 100644 --- a/Server.go +++ b/Server.go @@ -70,11 +70,9 @@ func (s *server) Get(path string, handler Handler) { // Request performs a synthetic request and returns the response. // This function keeps the response in memory so it's slightly slower than a real request. // However it is very useful inside tests where you don't want to spin up a real web server. -func (s *server) Request(method string, path string, body io.Reader) Response { +func (s *server) Request(method string, url string, body io.Reader) Response { ctx := s.newContext() - ctx.method = method - ctx.path = path - s.handleRequest(ctx, method, path, io.Discard) + s.handleRequest(ctx, method, url, io.Discard) return ctx.Response() } @@ -148,10 +146,10 @@ func (s *server) handleConnection(conn net.Conn) { lastSpace = len(message) } - path := message[space+1 : lastSpace] + url := message[space+1 : lastSpace] ctx := s.pool.Get().(*context) - s.handleRequest(ctx, method, path, conn) + s.handleRequest(ctx, method, url, conn) ctx.body = ctx.body[:0] ctx.params = ctx.params[:0] ctx.handlerCount = 0 @@ -161,8 +159,29 @@ func (s *server) handleConnection(conn net.Conn) { } // handleRequest handles the given request. -func (s *server) handleRequest(ctx *context, method string, path string, writer io.Writer) { +func (s *server) handleRequest(ctx *context, method string, url string, writer io.Writer) { + schemePos := strings.Index(url, "://") + schemeEnd := 0 + + if schemePos != -1 { + schemeEnd = schemePos + len("://") + } else { + schemePos = 0 + } + + pathPos := strings.IndexByte(url[schemeEnd:], '/') + + if pathPos == -1 { + return + } + + scheme := url[:schemePos] + host := url[schemeEnd : schemeEnd+pathPos] + path := url[schemeEnd+pathPos:] + ctx.method = method + ctx.scheme = scheme + ctx.host = host ctx.path = path err := s.handlers[0](ctx) diff --git a/Server_test.go b/Server_test.go index 3e529c3..a09df47 100644 --- a/Server_test.go +++ b/Server_test.go @@ -1,22 +1,103 @@ package web_test import ( + "errors" + "fmt" + "io" + "strings" "testing" "git.akyoto.dev/go/assert" "git.akyoto.dev/go/web" ) -func TestBytes(t *testing.T) { +func TestContext(t *testing.T) { s := web.NewServer() - s.Get("/", func(ctx web.Context) error { + s.Get("/bytes", func(ctx web.Context) error { return ctx.Bytes([]byte("Hello")) }) - response := s.Request("GET", "/", nil) - assert.Equal(t, response.Status(), 200) - assert.DeepEqual(t, response.Body(), []byte("Hello")) + s.Get("/string", func(ctx web.Context) error { + return ctx.String("Hello") + }) + + s.Get("/write", func(ctx web.Context) error { + _, err := ctx.Response().Write([]byte("Hello")) + return err + }) + + s.Get("/writestring", func(ctx web.Context) error { + _, err := io.WriteString(ctx.Response(), "Hello") + return err + }) + + s.Get("/error", func(ctx web.Context) error { + return ctx.Status(401).Error("Not logged in") + }) + + s.Get("/error2", func(ctx web.Context) error { + return ctx.Status(401).Error("Not logged in", errors.New("Missing auth token")) + }) + + s.Get("/request/data", func(ctx web.Context) error { + req := ctx.Request() + method := req.Method() + scheme := req.Scheme() + host := req.Host() + path := req.Path() + return ctx.String(fmt.Sprintf("%s %s %s %s", method, scheme, host, path)) + }) + + s.Router().Add("POST", "/", func(ctx web.Context) error { + return ctx.String("Post") + }) + + s.Router().Add("DELETE", "/", func(ctx web.Context) error { + return ctx.String("Delete") + }) + + s.Router().Add("PUT", "/", func(ctx web.Context) error { + return ctx.String("Put") + }) + + tests := []struct { + Method string + Path string + Body string + Status int + Response string + }{ + {Method: "GET", Path: "/bytes", Body: "", Status: 200, Response: "Hello"}, + // {Method: "GET", Path: "/context", Body: "", Status: 200, Response: ""}, + // {Method: "GET", Path: "/echo", Body: "Echo", Status: 200, Response: "Echo"}, + {Method: "GET", Path: "/error", Body: "", Status: 401, Response: ""}, + {Method: "GET", Path: "/error2", Body: "", Status: 401, Response: ""}, + // {Method: "GET", Path: "/file", Body: "", Status: 200, Response: "Hello File"}, + // {Method: "GET", Path: "/flush", Body: "", Status: 200, Response: "Hello 1\nHello 2\n"}, + // {Method: "GET", Path: "/not-found", Body: "", Status: 404, Response: ""}, + {Method: "GET", Path: "/request/data", Body: "", Status: 200, Response: "GET http example.com /request/data"}, + // {Method: "GET", Path: "/request/header", Body: "", Status: 200, Response: ""}, + // {Method: "GET", Path: "/response/header", Body: "", Status: 200, Response: "text/plain"}, + // {Method: "GET", Path: "/reader", Body: "", Status: 200, Response: "Hello"}, + // {Method: "GET", Path: "/redirect", Body: "", Status: 307, Response: ""}, + {Method: "GET", Path: "/string", Body: "", Status: 200, Response: "Hello"}, + {Method: "GET", Path: "/write", Body: "", Status: 200, Response: "Hello"}, + {Method: "GET", Path: "/writestring", Body: "", Status: 200, Response: "Hello"}, + // {Method: "GET", Path: "/blog/testing-my-router", Body: "", Status: 200, Response: "testing-my-router"}, + // {Method: "GET", Path: "/missing-parameter", Body: "", Status: 200, Response: ""}, + {Method: "POST", Path: "/", Body: "", Status: 200, Response: "Post"}, + {Method: "DELETE", Path: "/", Body: "", Status: 200, Response: "Delete"}, + {Method: "PUT", Path: "/", Body: "", Status: 200, Response: "Put"}, + } + + for _, test := range tests { + t.Run(test.Path, func(t *testing.T) { + response := s.Request(test.Method, "http://example.com"+test.Path, strings.NewReader(test.Body)) + assert.Equal(t, response.Status(), test.Status) + assert.Equal(t, string(response.Body()), test.Response) + }) + } } func TestString(t *testing.T) {