go-tower: first release v0.1.0
This commit is contained in:
328
README.md
328
README.md
@@ -1,2 +1,328 @@
|
||||
# go-tower
|
||||
# Tower
|
||||
|
||||
Tower is a minimal, composable HTTP web framework for Go built on top of `net/http` and `httprouter`.
|
||||
It provides a small, explicit API for routing, middleware composition, grouping, and structured request/response handling via a custom context.
|
||||
|
||||
The framework favors:
|
||||
- Small surface area
|
||||
- Explicit middleware chaining
|
||||
- Typed handler signatures
|
||||
- Zero magic abstractions
|
||||
- Compatibility with the standard library
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get git.trcreatives.at/TR_Creatives/go-tower
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.trcreatives.at/TR_Creatives/go-tower"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := tower.New()
|
||||
|
||||
app.GET("/hello/:name", func(c *tower.Context) error {
|
||||
name := c.Param("name")
|
||||
return c.WriteJSON(200, tower.Map{
|
||||
"message": "Hello " + name,
|
||||
})
|
||||
})
|
||||
|
||||
log.Fatal(app.Start(":8080"))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Handler
|
||||
|
||||
All route handlers use a unified signature:
|
||||
|
||||
```go
|
||||
type Handler func(c *Context) error
|
||||
```
|
||||
|
||||
Returning an error forwards it to the configured error handler.
|
||||
|
||||
```go
|
||||
app.GET("/ping", func(c *tower.Context) error {
|
||||
return c.WriteString(200, "pong")
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Middleware
|
||||
|
||||
Middleware wraps handlers and composes into a chain:
|
||||
|
||||
```go
|
||||
type Middleware func(h Handler) Handler
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
func Logger() tower.Middleware {
|
||||
return func(next tower.Handler) tower.Handler {
|
||||
return func(c *tower.Context) error {
|
||||
err := next(c)
|
||||
log.Printf("%s %s -> %d",
|
||||
c.Request().Method,
|
||||
c.Request().URL.Path,
|
||||
c.Status(),
|
||||
)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Register globally:
|
||||
|
||||
```go
|
||||
app.Use(Logger())
|
||||
```
|
||||
|
||||
Or per route:
|
||||
|
||||
```go
|
||||
app.GET("/secure", handler, AuthMiddleware())
|
||||
```
|
||||
|
||||
Middleware execution order (outer → inner):
|
||||
|
||||
1. Global middleware
|
||||
2. Group middleware
|
||||
3. Route middleware
|
||||
4. Handler
|
||||
|
||||
---
|
||||
|
||||
## Routing
|
||||
|
||||
Tower uses `httprouter`, so parameters are declared with `:name`.
|
||||
|
||||
```go
|
||||
app.GET("/users/:id", func(c *tower.Context) error {
|
||||
id := c.Param("id")
|
||||
return c.WriteString(200, "User: "+id)
|
||||
})
|
||||
```
|
||||
|
||||
Available helpers:
|
||||
|
||||
```go
|
||||
app.GET(path, handler, mws...)
|
||||
app.POST(path, handler, mws...)
|
||||
app.PUT(path, handler, mws...)
|
||||
app.PATCH(path, handler, mws...)
|
||||
app.DELETE(path, handler, mws...)
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```go
|
||||
app.Handle("GET", "/custom", handler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Groups
|
||||
|
||||
Groups allow route prefixing and shared middleware.
|
||||
|
||||
```go
|
||||
api := app.Group("/api")
|
||||
|
||||
api.GET("/health", func(c *tower.Context) error {
|
||||
return c.WriteString(200, "ok")
|
||||
})
|
||||
```
|
||||
|
||||
Nested groups:
|
||||
|
||||
```go
|
||||
v1 := api.Group("/v1")
|
||||
v1.GET("/users", listUsers)
|
||||
```
|
||||
|
||||
Group-level middleware:
|
||||
|
||||
```go
|
||||
auth := app.Group("/auth", AuthMiddleware())
|
||||
auth.GET("/profile", profileHandler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Static Files
|
||||
|
||||
Serve a directory under a URL prefix:
|
||||
|
||||
```go
|
||||
app.Static("/assets", "./public")
|
||||
```
|
||||
|
||||
Group variant:
|
||||
|
||||
```go
|
||||
api.Static("/docs", "./docs")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Context API
|
||||
|
||||
`Context` wraps `http.Request` and `http.ResponseWriter` and provides helpers.
|
||||
|
||||
### Request Access
|
||||
|
||||
```go
|
||||
c.Request() // *http.Request
|
||||
c.Param("id") // path parameter
|
||||
c.Query("q") // query string value
|
||||
c.Header("X-Token") // request header
|
||||
c.Cookie("session") // cookie
|
||||
c.ClientIP() // best-effort client IP
|
||||
```
|
||||
|
||||
### Response Helpers
|
||||
|
||||
```go
|
||||
c.SetStatus(201)
|
||||
c.SetHeader("X-App", "tower")
|
||||
|
||||
c.WriteString(200, "ok")
|
||||
c.WriteJSON(200, data)
|
||||
c.WriteXML(200, data)
|
||||
c.WriteBytes(200, []byte("raw"), "application/octet-stream")
|
||||
c.Redirect(302, "/login")
|
||||
```
|
||||
|
||||
### Binding Request Bodies
|
||||
|
||||
```go
|
||||
var payload CreateUserRequest
|
||||
if err := c.BindJSON(&payload); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
Also supported:
|
||||
|
||||
```go
|
||||
c.BindXML(&payload)
|
||||
```
|
||||
|
||||
### Context Values
|
||||
|
||||
```go
|
||||
type userKey tower.ContextKey // define your own alias if desired
|
||||
|
||||
c.SetValue("userID", "123")
|
||||
id := c.Value("userID")
|
||||
```
|
||||
|
||||
(Values are stored in the underlying `request.Context()`.)
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
Handlers return errors which are passed to the global error handler.
|
||||
|
||||
Set a custom handler:
|
||||
|
||||
```go
|
||||
app.SetErrorHandler(func(c *tower.Context, err error) error {
|
||||
return c.WriteJSON(500, tower.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
If the error handler itself returns an error, it is logged.
|
||||
|
||||
---
|
||||
|
||||
## Server Lifecycle
|
||||
|
||||
### Start HTTP
|
||||
|
||||
```go
|
||||
if err := app.Start(":8080"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Start HTTPS
|
||||
|
||||
```go
|
||||
log.Fatal(app.StartTLS(":8443", "cert.pem", "key.pem"))
|
||||
```
|
||||
|
||||
### Graceful Shutdown
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_ = app.Shutdown(ctx)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Flow
|
||||
|
||||
For each incoming request:
|
||||
|
||||
1. Route matched via `httprouter`
|
||||
2. `Context` constructed
|
||||
3. Route middleware applied
|
||||
4. Global middleware applied
|
||||
5. Handler executed
|
||||
6. Returned error passed to `ErrorHandler`
|
||||
|
||||
---
|
||||
|
||||
## Map Helper
|
||||
|
||||
Tower provides a small convenience type:
|
||||
|
||||
```go
|
||||
type Map map[string]any
|
||||
```
|
||||
|
||||
Useful for JSON responses:
|
||||
|
||||
```go
|
||||
return c.WriteJSON(200, tower.Map{
|
||||
"status": "ok",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Goals
|
||||
|
||||
* Minimal abstraction over `net/http`
|
||||
* Deterministic middleware composition
|
||||
* No reflection or hidden state
|
||||
* Explicit error propagation
|
||||
* Fully standard-library compatible
|
||||
Reference in New Issue
Block a user