go-tower: first release v0.1.0
This commit is contained in:
29
.devcontainer/devcontainer.json
Normal file
29
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,29 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/go .
|
||||
{
|
||||
"name": "Go",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/go:2-1.25-trixie",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"golang.go"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "go version",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for more information:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
# https://containers.dev/guide/dependabot
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
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
|
||||
117
context.go
Normal file
117
context.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package tower
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
request *http.Request
|
||||
response *responseWriter
|
||||
params httprouter.Params
|
||||
}
|
||||
|
||||
type ContextKey string
|
||||
|
||||
func (c *Context) Request() *http.Request {
|
||||
return c.request
|
||||
}
|
||||
|
||||
func (c *Context) Response() http.ResponseWriter {
|
||||
return c.response
|
||||
}
|
||||
|
||||
func (c *Context) Query(key string) string {
|
||||
return c.request.URL.Query().Get(key)
|
||||
}
|
||||
|
||||
func (c *Context) Param(key string) string {
|
||||
return c.params.ByName(key)
|
||||
}
|
||||
|
||||
func (c *Context) Value(key ContextKey) any {
|
||||
return c.request.Context().Value(key)
|
||||
}
|
||||
|
||||
func (c *Context) SetValue(key ContextKey, value any) {
|
||||
ctx := context.WithValue(c.request.Context(), key, value)
|
||||
c.request = c.request.WithContext(ctx)
|
||||
}
|
||||
|
||||
func (c *Context) Cookie(key string) (*http.Cookie, error) {
|
||||
return c.request.Cookie(key)
|
||||
}
|
||||
|
||||
func (c *Context) SetCookie(cookie *http.Cookie) {
|
||||
http.SetCookie(c.response, cookie)
|
||||
}
|
||||
|
||||
func (c *Context) Header(key string) string {
|
||||
return c.request.Header.Get(key)
|
||||
}
|
||||
|
||||
func (c *Context) SetHeader(key, value string) {
|
||||
c.response.Header().Set(key, value)
|
||||
}
|
||||
|
||||
func (c *Context) Status() int {
|
||||
return c.response.Status()
|
||||
}
|
||||
|
||||
func (c *Context) SetStatus(code int) {
|
||||
c.response.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (c *Context) ClientIP() string {
|
||||
if ip := c.Header("X-Forwarded-For"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
if ip := c.Header("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
return c.request.RemoteAddr
|
||||
}
|
||||
|
||||
func (c *Context) Redirect(code int, url string) {
|
||||
http.Redirect(c.response, c.request, url, code)
|
||||
}
|
||||
|
||||
func (c *Context) WriteBytes(code int, b []byte, contentType string) error {
|
||||
c.SetStatus(code)
|
||||
c.SetHeader("Content-Type", contentType)
|
||||
_, err := c.response.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Context) WriteString(code int, v string) error {
|
||||
c.SetStatus(code)
|
||||
c.SetHeader("Content-Type", "text/plain; charset=utf-8")
|
||||
_, err := c.response.Write([]byte(v))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Context) WriteJSON(code int, v any) error {
|
||||
c.SetStatus(code)
|
||||
c.SetHeader("Content-Type", "application/json; charset=utf-8")
|
||||
return json.NewEncoder(c.response).Encode(v)
|
||||
}
|
||||
|
||||
func (c *Context) WriteXML(code int, v any) error {
|
||||
c.SetStatus(code)
|
||||
c.SetHeader("Content-Type", "application/xml; charset=utf-8")
|
||||
return xml.NewEncoder(c.response).Encode(v)
|
||||
}
|
||||
|
||||
func (c *Context) BindJSON(v any) error {
|
||||
return json.NewDecoder(c.request.Body).Decode(v)
|
||||
}
|
||||
|
||||
func (c *Context) BindXML(v any) error {
|
||||
return xml.NewDecoder(c.request.Body).Decode(v)
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module git.trcreatives.at/trcreatives/go-tower
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require github.com/julienschmidt/httprouter v1.3.0
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
82
group.go
Normal file
82
group.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package tower
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
prefix string
|
||||
parent *Tower
|
||||
groupMiddlewares []Middleware
|
||||
}
|
||||
|
||||
func (g *Group) Use(groupMiddlewares ...Middleware) {
|
||||
g.groupMiddlewares = append(g.groupMiddlewares, groupMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) Group(prefix string, mws ...Middleware) *Group {
|
||||
child := &Group{
|
||||
parent: g.parent,
|
||||
prefix: g.buildFullPath(prefix),
|
||||
}
|
||||
|
||||
child.groupMiddlewares = make([]Middleware, 0, len(g.groupMiddlewares)+len(mws))
|
||||
child.groupMiddlewares = append(child.groupMiddlewares, g.groupMiddlewares...)
|
||||
child.groupMiddlewares = append(child.groupMiddlewares, mws...)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
func (g *Group) Static(prefix, dir string) {
|
||||
if prefix == "" || prefix[0] != '/' {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
|
||||
if prefix[len(prefix)-1] == '/' {
|
||||
prefix = prefix[:len(prefix)-1]
|
||||
}
|
||||
prefix += "/*filepath"
|
||||
|
||||
g.parent.router.ServeFiles(g.buildFullPath(prefix), http.Dir(dir))
|
||||
}
|
||||
|
||||
func (g *Group) GET(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
g.Handle(http.MethodGet, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) POST(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
g.Handle(http.MethodPost, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) PUT(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
g.Handle(http.MethodPut, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) PATCH(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
g.Handle(http.MethodPatch, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) DELETE(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
g.Handle(http.MethodDelete, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (g *Group) Handle(method string, path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
fullPath := g.buildFullPath(path)
|
||||
|
||||
mws := make([]Middleware, 0, len(g.groupMiddlewares)+len(routeMiddlewares))
|
||||
mws = append(mws, g.groupMiddlewares...)
|
||||
mws = append(mws, routeMiddlewares...)
|
||||
|
||||
g.parent.Handle(method, fullPath, h, mws...)
|
||||
}
|
||||
|
||||
func (g *Group) buildFullPath(path string) string {
|
||||
normalizedPrefix := normalizePath(g.prefix)
|
||||
normalizedPath := normalizePath(path)
|
||||
|
||||
if normalizedPrefix == "/" && normalizedPath == "/" {
|
||||
return "/"
|
||||
}
|
||||
|
||||
return normalizePath(g.prefix) + normalizePath(path)
|
||||
}
|
||||
9
handler.go
Normal file
9
handler.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package tower
|
||||
|
||||
type Handler func(c *Context) error
|
||||
|
||||
type ErrorHandler func(c *Context, err error) error
|
||||
|
||||
func defaultErrorHandler(c *Context, err error) error {
|
||||
return nil
|
||||
}
|
||||
3
middleware.go
Normal file
3
middleware.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package tower
|
||||
|
||||
type Middleware func(h Handler) Handler
|
||||
14
path.go
Normal file
14
path.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package tower
|
||||
|
||||
import "strings"
|
||||
|
||||
func normalizePath(path string) string {
|
||||
if path == "" {
|
||||
return "/"
|
||||
}
|
||||
|
||||
path = strings.TrimLeft(path, "/")
|
||||
path = strings.TrimRight(path, "/")
|
||||
|
||||
return "/" + path
|
||||
}
|
||||
33
response_writer.go
Normal file
33
response_writer.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package tower
|
||||
|
||||
import "net/http"
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *responseWriter) WriteHeader(code int) {
|
||||
if !w.wroteHeader {
|
||||
w.status = code
|
||||
w.wroteHeader = true
|
||||
}
|
||||
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *responseWriter) Write(b []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w *responseWriter) Status() int {
|
||||
if w.status == 0 {
|
||||
return http.StatusOK
|
||||
}
|
||||
return w.status
|
||||
}
|
||||
117
tower.go
Normal file
117
tower.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package tower
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
type Tower struct {
|
||||
router *httprouter.Router
|
||||
errorHandler ErrorHandler
|
||||
globalMiddlewares []Middleware
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func New() *Tower {
|
||||
t := &Tower{
|
||||
router: httprouter.New(),
|
||||
errorHandler: defaultErrorHandler,
|
||||
globalMiddlewares: []Middleware{},
|
||||
}
|
||||
|
||||
t.server = &http.Server{
|
||||
Handler: t.router,
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Tower) Start(listenAddr string) error {
|
||||
t.server.Addr = listenAddr
|
||||
return t.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (t *Tower) StartTLS(listenAddr, certFile, keyFile string) error {
|
||||
t.server.Addr = listenAddr
|
||||
return t.server.ListenAndServeTLS(certFile, keyFile)
|
||||
}
|
||||
|
||||
func (t *Tower) Shutdown(ctx context.Context) error {
|
||||
return t.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func (t *Tower) SetErrorHandler(errorHandler ErrorHandler) {
|
||||
t.errorHandler = errorHandler
|
||||
}
|
||||
|
||||
func (t *Tower) Use(globalMiddlewares ...Middleware) {
|
||||
t.globalMiddlewares = append(t.globalMiddlewares, globalMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) Group(prefix string, groupMiddlewares ...Middleware) *Group {
|
||||
return &Group{
|
||||
prefix: normalizePath(prefix),
|
||||
parent: t,
|
||||
groupMiddlewares: groupMiddlewares,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tower) Static(prefix, dir string) {
|
||||
prefix = normalizePath(prefix)
|
||||
prefix += "/*filepath"
|
||||
|
||||
t.router.ServeFiles(prefix, http.Dir(dir))
|
||||
}
|
||||
|
||||
func (t *Tower) GET(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.Handle(http.MethodGet, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) POST(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.Handle(http.MethodPost, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) PUT(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.Handle(http.MethodPut, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) PATCH(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.Handle(http.MethodPatch, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) DELETE(path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.Handle(http.MethodDelete, path, h, routeMiddlewares...)
|
||||
}
|
||||
|
||||
func (t *Tower) Handle(method string, path string, h Handler, routeMiddlewares ...Middleware) {
|
||||
t.router.Handle(method, path, t.buildHTTPRouterHandle(h, routeMiddlewares...))
|
||||
}
|
||||
|
||||
func (t *Tower) buildHTTPRouterHandle(h Handler, routeMiddlewares ...Middleware) httprouter.Handle {
|
||||
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||
ctx := Context{
|
||||
request: r,
|
||||
response: &responseWriter{ResponseWriter: w},
|
||||
params: p,
|
||||
}
|
||||
|
||||
wrappedHandler := h
|
||||
|
||||
for i := len(routeMiddlewares) - 1; i >= 0; i-- {
|
||||
wrappedHandler = routeMiddlewares[i](wrappedHandler)
|
||||
}
|
||||
|
||||
for i := len(t.globalMiddlewares) - 1; i >= 0; i-- {
|
||||
wrappedHandler = t.globalMiddlewares[i](wrappedHandler)
|
||||
}
|
||||
|
||||
if err := wrappedHandler(&ctx); err != nil {
|
||||
if err := t.errorHandler(&ctx, err); err != nil {
|
||||
log.Printf("[Error] %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user