go>> bon>> 返回
项目作者: nissy

项目描述 :
Go http router
高级语言: Go
项目地址: git://github.com/nissy/bon.git
创建时间: 2017-02-23T13:52:08Z
项目社区:https://github.com/nissy/bon

开源协议:MIT License

下载


BON

Bon - Fast HTTP Router for Go

Bon is a high-performance HTTP router for Go that uses a double array trie data structure for efficient route matching. It focuses on speed, simplicity, and zero external dependencies.

GoDoc Widget Go Report Card

Table of Contents

Features

  • High Performance: Double array trie-based routing for optimal performance
  • Zero Dependencies: Uses only Go standard library
  • Middleware Support: Flexible middleware at router, group, and route levels
  • Standard HTTP Compatible: Works with http.Handler interface
  • Flexible Routing: Static, parameter (:param), and wildcard (*) patterns
  • All HTTP Methods: GET, POST, PUT, DELETE, HEAD, OPTIONS, PATCH, CONNECT, TRACE
  • File Server: Built-in static file serving with security protections
  • Context Pooling: Efficient memory usage with sync.Pool
  • Thread-Safe: Lock-free reads using atomic operations
  • Panic Recovery: Built-in recovery middleware available
  • WebSocket Ready: Full support for WebSocket connections
  • SSE Support: Server-Sent Events with proper flushing
  • HTTP/2 Push: Server push support for modern browsers

Quick Start

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/nissy/bon"
  5. "github.com/nissy/bon/middleware"
  6. )
  7. func main() {
  8. r := bon.NewRouter()
  9. // Global middleware
  10. r.Use(middleware.Recovery()) // Panic recovery
  11. // Simple route
  12. r.Get("/", func(w http.ResponseWriter, r *http.Request) {
  13. w.Write([]byte("Hello, Bon!"))
  14. })
  15. // Route with parameter
  16. r.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
  17. userID := bon.URLParam(r, "id")
  18. w.Write([]byte("User: " + userID))
  19. })
  20. http.ListenAndServe(":8080", r)
  21. }

Installation

  1. go get github.com/nissy/bon

Route Patterns

Pattern Types and Priority

Routes are matched in the following priority order (highest to lowest):

  1. Static routes - Exact path match

    1. r.Get("/users/profile", handler) // Highest priority
    2. r.Get("/api/v1/status", handler)
  2. Parameter routes - Named parameter capture

    1. r.Get("/users/:id", handler) // Captures id parameter
    2. r.Get("/posts/:category/:slug", handler)
  3. Wildcard routes - Catch-all pattern

    1. r.Get("/files/*", handler) // Lowest priority
    2. r.Get("/api/*", handler)

Parameter Extraction

  1. // Single parameter
  2. r.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
  3. userID := bon.URLParam(r, "id")
  4. // Use userID...
  5. })
  6. // Multiple parameters
  7. r.Get("/posts/:category/:id", func(w http.ResponseWriter, r *http.Request) {
  8. category := bon.URLParam(r, "category")
  9. postID := bon.URLParam(r, "id")
  10. // Use parameters...
  11. })
  12. // Unicode parameter names are supported
  13. r.Get("/users/:name", func(w http.ResponseWriter, r *http.Request) {
  14. name := bon.URLParam(r, "name")
  15. // Use name...
  16. })

Middleware

Middleware Execution Order

Middleware executes in the order it was added, creating a chain:

  1. r := bon.NewRouter()
  2. // Execution order: Recovery -> CORS -> Auth -> Handler
  3. r.Use(middleware.Recovery()) // 1st - Catches panics
  4. r.Use(middleware.CORS(config)) // 2nd - Handles CORS
  5. api := r.Group("/api")
  6. api.Use(middleware.BasicAuth(users)) // 3rd - Authenticates
  7. api.Get("/data", handler) // Finally, the handler

Built-in Middleware

Recovery Middleware

Catches panics and returns 500 Internal Server Error:

  1. r.Use(middleware.Recovery())
  2. // With custom handler
  3. r.Use(middleware.RecoveryWithHandler(func(w http.ResponseWriter, r *http.Request, err interface{}) {
  4. w.WriteHeader(500)
  5. w.Write([]byte(fmt.Sprintf("Panic: %v", err)))
  6. }))

CORS Middleware

Handles Cross-Origin Resource Sharing:

  1. r.Use(middleware.CORS(middleware.AccessControlConfig{
  2. AllowOrigin: "*",
  3. AllowCredentials: true,
  4. AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
  5. AllowHeaders: []string{"Authorization", "Content-Type"},
  6. ExposeHeaders: []string{"X-Total-Count"},
  7. MaxAge: 86400,
  8. }))

Basic Auth Middleware

HTTP Basic Authentication:

  1. users := []middleware.BasicAuthUser{
  2. {Name: "admin", Password: "secret"},
  3. {Name: "user", Password: "pass123"},
  4. }
  5. r.Use(middleware.BasicAuth(users))

Timeout Middleware

Request timeout handling:

  1. r.Use(middleware.Timeout(30 * time.Second))

Groups and Routes

Group - Inherits Middleware

Groups inherit middleware from their parent and prefix all routes:

  1. r := bon.NewRouter()
  2. r.Use(middleware.Recovery()) // Global middleware
  3. // API group inherits Recovery
  4. api := r.Group("/api")
  5. api.Use(middleware.BasicAuth(users)) // Group middleware
  6. // All routes inherit Recovery + BasicAuth
  7. api.Get("/users", listUsers) // GET /api/users
  8. api.Post("/users", createUser) // POST /api/users
  9. // Nested group inherits all parent middleware
  10. v1 := api.Group("/v1")
  11. v1.Get("/posts", listPosts) // GET /api/v1/posts (Recovery + BasicAuth)

Route - Standalone

Routes are completely independent and don’t inherit any middleware:

  1. r := bon.NewRouter()
  2. r.Use(middleware.BasicAuth(users)) // Global middleware
  3. // This route is NOT affected by global middleware
  4. standalone := r.Route()
  5. standalone.Get("/public", handler) // No auth required
  6. // Must explicitly add middleware if needed
  7. webhook := r.Route()
  8. webhook.Use(webhookMiddleware)
  9. webhook.Post("/webhook", handler) // Only webhook validation, no auth

HTTP Methods

All standard HTTP methods are supported:

  1. r.Get("/users", handler)
  2. r.Post("/users", handler)
  3. r.Put("/users/:id", handler)
  4. r.Delete("/users/:id", handler)
  5. r.Head("/", handler)
  6. r.Options("/", handler)
  7. r.Patch("/users/:id", handler)
  8. r.Connect("/proxy", handler)
  9. r.Trace("/debug", handler)
  10. // Generic method handler
  11. r.Handle("CUSTOM", "/", handler)

File Server

Serve static files with built-in security:

  1. // Serve files from ./public directory at /static/*
  2. r.FileServer("/static", "./public")
  3. // With middleware
  4. r.FileServer("/assets", "./assets",
  5. middleware.BasicAuth(users),
  6. middleware.CORS(corsConfig),
  7. )
  8. // In a group
  9. admin := r.Group("/admin")
  10. admin.Use(middleware.BasicAuth(adminUsers))
  11. admin.FileServer("/files", "./admin-files")

Security features:

  • Path traversal protection (blocks .., ./, etc.)
  • Hidden file protection (blocks . prefix files)
  • Null byte protection
  • Automatic index.html serving for directories

Custom 404 Handler

  1. r := bon.NewRouter()
  2. // Method 1: Direct assignment
  3. r.NotFound = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  4. w.Header().Set("Content-Type", "application/json")
  5. w.WriteHeader(404)
  6. w.Write([]byte(`{"error":"not found"}`))
  7. })
  8. // Method 2: Using SetNotFound (respects middleware)
  9. r.SetNotFound(func(w http.ResponseWriter, r *http.Request) {
  10. w.WriteHeader(404)
  11. w.Write([]byte("Custom 404 page"))
  12. })

WebSocket, SSE, and HTTP/2 Push Support

Bon supports WebSocket, Server-Sent Events (SSE), and HTTP/2 Push through Go’s standard interfaces. When using middleware that wraps the ResponseWriter (like the Timeout middleware), you need to access the underlying ResponseWriter through the Unwrap() method.

WebSocket Support

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/nissy/bon"
  5. "github.com/nissy/bon/middleware"
  6. "github.com/gorilla/websocket"
  7. )
  8. var upgrader = websocket.Upgrader{
  9. CheckOrigin: func(r *http.Request) bool {
  10. return true // Configure appropriately for production
  11. },
  12. }
  13. func main() {
  14. r := bon.NewRouter()
  15. r.Use(middleware.Recovery())
  16. r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
  17. // When using middleware that wraps ResponseWriter
  18. var conn *websocket.Conn
  19. var err error
  20. // Try direct upgrade first
  21. conn, err = upgrader.Upgrade(w, r, nil)
  22. if err != nil {
  23. // If failed, try through Unwrap
  24. if unwrapper, ok := w.(interface{ Unwrap() http.ResponseWriter }); ok {
  25. conn, err = upgrader.Upgrade(unwrapper.Unwrap(), r, nil)
  26. }
  27. if err != nil {
  28. http.Error(w, "WebSocket upgrade failed", http.StatusBadRequest)
  29. return
  30. }
  31. }
  32. defer conn.Close()
  33. // Handle WebSocket connection
  34. for {
  35. messageType, p, err := conn.ReadMessage()
  36. if err != nil {
  37. break
  38. }
  39. if err := conn.WriteMessage(messageType, p); err != nil {
  40. break
  41. }
  42. }
  43. })
  44. http.ListenAndServe(":8080", r)
  45. }

Server-Sent Events (SSE) Support

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "time"
  6. "github.com/nissy/bon"
  7. "github.com/nissy/bon/middleware"
  8. )
  9. func main() {
  10. r := bon.NewRouter()
  11. r.Use(middleware.Recovery())
  12. r.Use(middleware.Timeout(30 * time.Second))
  13. r.Get("/events", func(w http.ResponseWriter, r *http.Request) {
  14. // Set SSE headers
  15. w.Header().Set("Content-Type", "text/event-stream")
  16. w.Header().Set("Cache-Control", "no-cache")
  17. w.Header().Set("Connection", "keep-alive")
  18. // Get flusher
  19. var flusher http.Flusher
  20. var ok bool
  21. // Try direct cast first
  22. flusher, ok = w.(http.Flusher)
  23. if !ok {
  24. // Try through Unwrap
  25. if unwrapper, ok := w.(interface{ Unwrap() http.ResponseWriter }); ok {
  26. flusher, ok = unwrapper.Unwrap().(http.Flusher)
  27. }
  28. if !ok {
  29. http.Error(w, "SSE not supported", http.StatusInternalServerError)
  30. return
  31. }
  32. }
  33. // Send events
  34. ticker := time.NewTicker(1 * time.Second)
  35. defer ticker.Stop()
  36. for {
  37. select {
  38. case <-r.Context().Done():
  39. return
  40. case t := <-ticker.C:
  41. fmt.Fprintf(w, "data: %s\n\n", t.Format(time.RFC3339))
  42. flusher.Flush()
  43. }
  44. }
  45. })
  46. http.ListenAndServe(":8080", r)
  47. }

HTTP/2 Push Support

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/nissy/bon"
  5. "github.com/nissy/bon/middleware"
  6. )
  7. func main() {
  8. r := bon.NewRouter()
  9. r.Use(middleware.Recovery())
  10. r.Get("/", func(w http.ResponseWriter, r *http.Request) {
  11. // Get pusher
  12. var pusher http.Pusher
  13. var ok bool
  14. // Try direct cast first
  15. pusher, ok = w.(http.Pusher)
  16. if !ok {
  17. // Try through Unwrap
  18. if unwrapper, ok := w.(interface{ Unwrap() http.ResponseWriter }); ok {
  19. pusher, ok = unwrapper.Unwrap().(http.Pusher)
  20. }
  21. }
  22. // Push resources if available
  23. if pusher != nil {
  24. // Push CSS and JS files
  25. pusher.Push("/static/style.css", &http.PushOptions{
  26. Header: http.Header{
  27. "Content-Type": []string{"text/css"},
  28. },
  29. })
  30. pusher.Push("/static/app.js", &http.PushOptions{
  31. Header: http.Header{
  32. "Content-Type": []string{"application/javascript"},
  33. },
  34. })
  35. }
  36. // Serve main content
  37. w.Header().Set("Content-Type", "text/html")
  38. w.Write([]byte(`
  39. <!DOCTYPE html>
  40. <html>
  41. <head>
  42. <link rel="stylesheet" href="/static/style.css">
  43. <script src="/static/app.js"></script>
  44. </head>
  45. <body>
  46. <h1>Hello with HTTP/2 Push!</h1>
  47. </body>
  48. </html>
  49. `))
  50. })
  51. // Serve static files
  52. r.FileServer("/static", "./static")
  53. // Note: HTTP/2 requires TLS
  54. http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", r)
  55. }

Using http.ResponseController (Go 1.20+)

For Go 1.20 and later, you can use http.ResponseController which automatically handles the Unwrap() method:

  1. func sseHandler(w http.ResponseWriter, r *http.Request) {
  2. rc := http.NewResponseController(w)
  3. w.Header().Set("Content-Type", "text/event-stream")
  4. w.WriteHeader(http.StatusOK)
  5. for {
  6. select {
  7. case <-r.Context().Done():
  8. return
  9. case <-time.After(1 * time.Second):
  10. fmt.Fprintf(w, "data: ping\n\n")
  11. if err := rc.Flush(); err != nil {
  12. return
  13. }
  14. }
  15. }
  16. }

Examples

RESTful API

  1. package main
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "time"
  6. "github.com/nissy/bon"
  7. "github.com/nissy/bon/middleware"
  8. )
  9. type User struct {
  10. ID string `json:"id"`
  11. Name string `json:"name"`
  12. }
  13. func main() {
  14. r := bon.NewRouter()
  15. // Global middleware
  16. r.Use(middleware.Recovery())
  17. r.Use(middleware.CORS(middleware.AccessControlConfig{
  18. AllowOrigin: "*",
  19. }))
  20. // API routes
  21. api := r.Group("/api")
  22. api.Use(middleware.Timeout(30 * time.Second))
  23. // User routes
  24. api.Get("/users", listUsers)
  25. api.Post("/users", createUser)
  26. api.Get("/users/:id", getUser)
  27. api.Put("/users/:id", updateUser)
  28. api.Delete("/users/:id", deleteUser)
  29. // Nested resources
  30. api.Get("/users/:userId/posts", getUserPosts)
  31. api.Post("/users/:userId/posts", createUserPost)
  32. http.ListenAndServe(":8080", r)
  33. }
  34. func getUser(w http.ResponseWriter, r *http.Request) {
  35. userID := bon.URLParam(r, "id")
  36. user := User{ID: userID, Name: "John Doe"}
  37. w.Header().Set("Content-Type", "application/json")
  38. json.NewEncoder(w).Encode(user)
  39. }

API Versioning

  1. package main
  2. import (
  3. "net/http"
  4. "time"
  5. "github.com/nissy/bon"
  6. "github.com/nissy/bon/middleware"
  7. )
  8. func main() {
  9. r := bon.NewRouter()
  10. // API v1
  11. v1 := r.Group("/api/v1")
  12. v1.Use(middleware.CORS(middleware.AccessControlConfig{
  13. AllowOrigin: "*",
  14. }))
  15. v1.Get("/users", v1ListUsers)
  16. v1.Get("/posts", v1ListPosts)
  17. // API v2 with additional features
  18. v2 := r.Group("/api/v2")
  19. v2.Use(middleware.CORS(middleware.AccessControlConfig{
  20. AllowOrigin: "*",
  21. }))
  22. v2.Use(middleware.Timeout(30 * time.Second))
  23. v2.Get("/users", v2ListUsers) // New response format
  24. v2.Get("/posts", v2ListPosts) // Additional fields
  25. v2.Get("/comments", v2ListComments) // New endpoint
  26. // Health check (version independent)
  27. r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
  28. w.Write([]byte(`{"status":"ok"}`))
  29. })
  30. http.ListenAndServe(":8080", r)
  31. }

Authentication Example

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/nissy/bon"
  5. "github.com/nissy/bon/middleware"
  6. )
  7. func main() {
  8. r := bon.NewRouter()
  9. // Public endpoints
  10. r.Get("/", homeHandler)
  11. r.Get("/login", loginPageHandler)
  12. r.Post("/login", loginHandler)
  13. // Protected API
  14. api := r.Group("/api")
  15. api.Use(middleware.BasicAuth([]middleware.BasicAuthUser{
  16. {Name: "user", Password: "pass"},
  17. }))
  18. api.Get("/profile", profileHandler)
  19. api.Get("/settings", settingsHandler)
  20. // Admin area with different auth
  21. admin := r.Group("/admin")
  22. admin.Use(middleware.BasicAuth([]middleware.BasicAuthUser{
  23. {Name: "admin", Password: "admin123"},
  24. }))
  25. admin.Get("/users", listAllUsers)
  26. admin.Delete("/users/:id", deleteUser)
  27. // Webhooks - no auth but standalone
  28. webhooks := r.Route()
  29. webhooks.Post("/webhook/github", githubWebhook)
  30. webhooks.Post("/webhook/stripe", stripeWebhook)
  31. http.ListenAndServe(":8080", r)
  32. }

API Documentation

For detailed API documentation, see pkg.go.dev/github.com/nissy/bon.

Performance Tips

  1. Route Registration: Order doesn’t matter - the router automatically optimizes
  2. Middleware Placement: Apply at the appropriate level for best performance
  3. Static Routes: Use exact paths when possible for fastest matching
  4. Parameter Reuse: The router pools context objects automatically

Requirements

  • Go 1.18 or higher

Testing

  1. # Run all tests
  2. go test ./...
  3. # Run tests with race detection
  4. go test -race ./...
  5. # Run benchmarks
  6. go test -bench=. ./...

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT