Compare commits
No commits in common. "c5ad01daee1a5d959091b96751937421679c39d8" and "fdd6e7b213efcc19754c8c72852d712027d0ecb5" have entirely different histories.
c5ad01daee
...
fdd6e7b213
13 changed files with 40 additions and 132 deletions
|
@ -9,7 +9,6 @@ type Context interface {
|
|||
Bytes([]byte) error
|
||||
Error(...any) error
|
||||
Next() error
|
||||
Redirect(int, string) error
|
||||
Request() Request
|
||||
Response() Response
|
||||
Status(int) Context
|
||||
|
@ -52,14 +51,6 @@ func (ctx *context) Next() error {
|
|||
return ctx.server.handlers[ctx.handlerCount](ctx)
|
||||
}
|
||||
|
||||
// Redirect redirects the client to a different location
|
||||
// with the specified status code.
|
||||
func (ctx *context) Redirect(status int, location string) error {
|
||||
ctx.response.SetStatus(status)
|
||||
ctx.response.SetHeader("Location", location)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request returns the HTTP request.
|
||||
func (ctx *context) Request() Request {
|
||||
return &ctx.request
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"errors"
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/go/assert"
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
|
@ -55,15 +55,3 @@ func TestErrorMultiple(t *testing.T) {
|
|||
assert.Equal(t, response.Status(), 401)
|
||||
assert.Equal(t, string(response.Body()), "")
|
||||
}
|
||||
|
||||
func TestRedirect(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
s.Get("/", func(ctx web.Context) error {
|
||||
return ctx.Redirect(301, "/target")
|
||||
})
|
||||
|
||||
response := s.Request("GET", "/", nil, nil)
|
||||
assert.Equal(t, response.Status(), 301)
|
||||
assert.Equal(t, response.Header("Location"), "/target")
|
||||
}
|
||||
|
|
24
README.md
24
README.md
|
@ -1,6 +1,6 @@
|
|||
# web
|
||||
|
||||
A minimal HTTP/1.1 web server that sits behind a reverse proxy like `caddy` or `nginx` for HTTP 1/2/3 support.
|
||||
A fast HTTP/1.1 web server that can sit behind a reverse proxy like `caddy` or `nginx` for HTTP 1/2/3 support.
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -11,7 +11,7 @@ A minimal HTTP/1.1 web server that sits behind a reverse proxy like `caddy` or `
|
|||
## Installation
|
||||
|
||||
```shell
|
||||
go get git.urbach.dev/go/web
|
||||
go get git.akyoto.dev/go/web
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
@ -26,23 +26,12 @@ s.Get("/", func(ctx web.Context) error {
|
|||
|
||||
// Parameter route
|
||||
s.Get("/blog/:post", func(ctx web.Context) error {
|
||||
return ctx.String(ctx.Request().Param("post"))
|
||||
return ctx.String(ctx.Get("post"))
|
||||
})
|
||||
|
||||
// Wildcard route
|
||||
s.Get("/images/*file", func(ctx web.Context) error {
|
||||
return ctx.String(ctx.Request().Param("file"))
|
||||
})
|
||||
|
||||
// Middleware
|
||||
s.Use(func(ctx web.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
defer func() {
|
||||
fmt.Println(ctx.Request().Path(), time.Since(start))
|
||||
}()
|
||||
|
||||
return ctx.Next()
|
||||
return ctx.String(ctx.Get("file"))
|
||||
})
|
||||
|
||||
s.Run(":8080")
|
||||
|
@ -55,7 +44,6 @@ PASS: TestBytes
|
|||
PASS: TestString
|
||||
PASS: TestError
|
||||
PASS: TestErrorMultiple
|
||||
PASS: TestRedirect
|
||||
PASS: TestRequest
|
||||
PASS: TestRequestHeader
|
||||
PASS: TestRequestParam
|
||||
|
@ -69,9 +57,7 @@ PASS: TestRun
|
|||
PASS: TestBadRequest
|
||||
PASS: TestBadRequestHeader
|
||||
PASS: TestBadRequestMethod
|
||||
PASS: TestBadRequestPath
|
||||
PASS: TestBadRequestProtocol
|
||||
PASS: TestConnectionClose
|
||||
PASS: TestEarlyClose
|
||||
PASS: TestUnavailablePort
|
||||
coverage: 100.0% of statements
|
||||
|
@ -83,7 +69,7 @@ coverage: 100.0% of statements
|
|||
|
||||
## License
|
||||
|
||||
Please see the [license documentation](https://urbach.dev/license).
|
||||
Please see the [license documentation](https://akyoto.dev/license).
|
||||
|
||||
## Copyright
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package web
|
|||
import (
|
||||
"bufio"
|
||||
|
||||
"git.urbach.dev/go/router"
|
||||
"git.akyoto.dev/go/router"
|
||||
)
|
||||
|
||||
// Request is an interface for HTTP requests.
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/go/assert"
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
func TestRequest(t *testing.T) {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"io"
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/go/assert"
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
|
|
22
Server.go
22
Server.go
|
@ -13,7 +13,7 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"git.urbach.dev/go/router"
|
||||
"git.akyoto.dev/go/router"
|
||||
)
|
||||
|
||||
// Server is the interface for an HTTP server.
|
||||
|
@ -121,7 +121,6 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
ctx = s.contextPool.Get().(*context)
|
||||
method string
|
||||
url string
|
||||
close bool
|
||||
)
|
||||
|
||||
ctx.reader.Reset(conn)
|
||||
|
@ -129,7 +128,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
defer conn.Close()
|
||||
defer s.contextPool.Put(ctx)
|
||||
|
||||
for !close {
|
||||
for {
|
||||
// Read the HTTP request line
|
||||
message, err := ctx.reader.ReadString('\n')
|
||||
|
||||
|
@ -157,14 +156,7 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
lastSpace = len(message) - len("\r\n")
|
||||
}
|
||||
|
||||
space += 1
|
||||
|
||||
if space > lastSpace {
|
||||
io.WriteString(conn, "HTTP/1.1 400 Bad Request\r\n\r\n")
|
||||
return
|
||||
}
|
||||
|
||||
url = message[space:lastSpace]
|
||||
url = message[space+1 : lastSpace]
|
||||
|
||||
// Add headers until we meet an empty line
|
||||
for {
|
||||
|
@ -184,10 +176,6 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
continue
|
||||
}
|
||||
|
||||
if colon > len(message)-4 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := message[:colon]
|
||||
value := message[colon+2 : len(message)-2]
|
||||
|
||||
|
@ -195,10 +183,6 @@ func (s *server) handleConnection(conn net.Conn) {
|
|||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
if value == "close" && strings.EqualFold(key, "connection") {
|
||||
close = true
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the request
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"syscall"
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/go/assert"
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
func TestPanic(t *testing.T) {
|
||||
|
@ -77,7 +77,7 @@ func TestBadRequestHeader(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
_, err = io.WriteString(conn, "GET / HTTP/1.1\r\nBad\r\nBad:\r\nGood: Header\r\n\r\n")
|
||||
_, err = io.WriteString(conn, "GET / HTTP/1.1\r\nBadHeader\r\nGood: Header\r\n\r\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
buffer := make([]byte, len("HTTP/1.1 200"))
|
||||
|
@ -110,27 +110,6 @@ func TestBadRequestMethod(t *testing.T) {
|
|||
s.Run(":8080")
|
||||
}
|
||||
|
||||
func TestBadRequestPath(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
go func() {
|
||||
defer syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||
|
||||
conn, err := net.Dial("tcp", ":8080")
|
||||
assert.Nil(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
_, err = io.WriteString(conn, "GET \n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
response, err := io.ReadAll(conn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, string(response), "HTTP/1.1 400 Bad Request\r\n\r\n")
|
||||
}()
|
||||
|
||||
s.Run(":8080")
|
||||
}
|
||||
|
||||
func TestBadRequestProtocol(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
|
@ -157,26 +136,6 @@ func TestBadRequestProtocol(t *testing.T) {
|
|||
s.Run(":8080")
|
||||
}
|
||||
|
||||
func TestConnectionClose(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
go func() {
|
||||
defer syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||
|
||||
conn, err := net.Dial("tcp", ":8080")
|
||||
assert.Nil(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
_, err = io.WriteString(conn, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = io.ReadAll(conn)
|
||||
assert.Nil(t, err)
|
||||
}()
|
||||
|
||||
s.Run(":8080")
|
||||
}
|
||||
|
||||
func TestEarlyClose(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package send
|
||||
package content
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
// CSS sends the body with the content type set to `text/css`.
|
|
@ -1,42 +1,42 @@
|
|||
package send_test
|
||||
package content_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.urbach.dev/go/assert"
|
||||
"git.urbach.dev/go/web"
|
||||
"git.urbach.dev/go/web/send"
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/web"
|
||||
"git.akyoto.dev/go/web/content"
|
||||
)
|
||||
|
||||
func TestContentTypes(t *testing.T) {
|
||||
s := web.NewServer()
|
||||
|
||||
s.Get("/css", func(ctx web.Context) error {
|
||||
return send.CSS(ctx, "body{}")
|
||||
return content.CSS(ctx, "body{}")
|
||||
})
|
||||
|
||||
s.Get("/csv", func(ctx web.Context) error {
|
||||
return send.CSV(ctx, "ID;Name\n")
|
||||
return content.CSV(ctx, "ID;Name\n")
|
||||
})
|
||||
|
||||
s.Get("/html", func(ctx web.Context) error {
|
||||
return send.HTML(ctx, "<html></html>")
|
||||
return content.HTML(ctx, "<html></html>")
|
||||
})
|
||||
|
||||
s.Get("/js", func(ctx web.Context) error {
|
||||
return send.JS(ctx, "console.log(42)")
|
||||
return content.JS(ctx, "console.log(42)")
|
||||
})
|
||||
|
||||
s.Get("/json", func(ctx web.Context) error {
|
||||
return send.JSON(ctx, struct{ Name string }{Name: "User 1"})
|
||||
return content.JSON(ctx, struct{ Name string }{Name: "User 1"})
|
||||
})
|
||||
|
||||
s.Get("/text", func(ctx web.Context) error {
|
||||
return send.Text(ctx, "Hello")
|
||||
return content.Text(ctx, "Hello")
|
||||
})
|
||||
|
||||
s.Get("/xml", func(ctx web.Context) error {
|
||||
return send.XML(ctx, "<xml></xml>")
|
||||
return content.XML(ctx, "<xml></xml>")
|
||||
})
|
||||
|
||||
tests := []struct {
|
|
@ -1,7 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.urbach.dev/go/web"
|
||||
"git.akyoto.dev/go/web"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,8 +1,8 @@
|
|||
module git.urbach.dev/go/web
|
||||
module git.akyoto.dev/go/web
|
||||
|
||||
go 1.24
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf
|
||||
git.urbach.dev/go/router v0.0.0-20250601162231-e35d5715d1a5
|
||||
git.akyoto.dev/go/assert v0.1.3
|
||||
git.akyoto.dev/go/router v0.1.4
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -1,4 +1,4 @@
|
|||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf h1:BQWa5GKNUsA5CSUa/+UlFWYCEVe3IDDKRbVqBLK0mAE=
|
||||
git.urbach.dev/go/assert v0.0.0-20250225153414-fc1f84f19edf/go.mod h1:y9jGII9JFiF1HNIju0u87OyPCt82xKCtqnAFyEreCDo=
|
||||
git.urbach.dev/go/router v0.0.0-20250601162231-e35d5715d1a5 h1:3qlZjgWbrHw4LWM4uBzOTldbpqCLJdeSgvcYK6f3xpc=
|
||||
git.urbach.dev/go/router v0.0.0-20250601162231-e35d5715d1a5/go.mod h1:O+doTe0DZdT2XMsTY5pe6qUMqEEIAbwtX6Xp6EqHw34=
|
||||
git.akyoto.dev/go/assert v0.1.3 h1:QwCUbmG4aZYsNk/OuRBz1zWVKmGlDUHhOnnDBfn8Qw8=
|
||||
git.akyoto.dev/go/assert v0.1.3/go.mod h1:0GzMaM0eURuDwtGkJJkCsI7r2aUKr+5GmWNTFPgDocM=
|
||||
git.akyoto.dev/go/router v0.1.4 h1:ZL5HPl4aNn4QKihf3VVs0Mm9R6ZGn2StAHGRQxjEbws=
|
||||
git.akyoto.dev/go/router v0.1.4/go.mod h1:rbHbkLJlQOafuOuvBalO3O8E0JtMFPT3zzTKX3h9T08=
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue