fix(go.sum): update ResolveSpec dependency to v1.0.87
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s

This commit is contained in:
Hein
2026-06-23 13:17:16 +02:00
parent 0227912325
commit 1adf50e3db
2436 changed files with 1078758 additions and 114 deletions
+6
View File
@@ -0,0 +1,6 @@
trailingComma: all
tabWidth: 2
semi: false
singleQuote: true
proseWrap: always
printWidth: 100
+240
View File
@@ -0,0 +1,240 @@
## [1.0.23](https://github.com/uptrace/bunrouter/compare/v1.0.22...v1.0.23) (2025-03-19)
### Bug Fixes
* **router:** ensure proper param handling in MethodNotAllowed fallback test ([1fe6546](https://github.com/uptrace/bunrouter/commit/1fe65464675f73552c05bf806bfd7adba2dee01a))
* **router:** improve error handling & nil checks ([094d78b](https://github.com/uptrace/bunrouter/commit/094d78ba93918f73506421fcb7c2215f275bbb34))
* **router:** improve error handling & nil checks ([d4bd43d](https://github.com/uptrace/bunrouter/commit/d4bd43dce23ab26ba0cdb65db3b10a2dcefccac6))
## [1.0.23](https://github.com/uptrace/bunrouter/compare/v1.0.22...v1.0.23) (2025-03-19)
### Features
* enhanced error handling and nil checks throughout the codebase ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
* implemented proper HTTP error interface for standardized error responses ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
* added more descriptive error messages with package prefix ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
### Bug Fixes
* prevent panics with comprehensive nil checks ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
* improved parameter validation with bounds checking ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
* ensured Map() and Slice() methods always return non-nil values ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
* fixed JSON handling for nil values ([0dba7e1](https://github.com/uptrace/bunrouter/commit/0dba7e1))
## [1.0.22](https://github.com/uptrace/bunrouter/compare/v1.0.21...v1.0.22) (2024-09-22)
### Features
* add params.Int ([1709e1d](https://github.com/uptrace/bunrouter/commit/1709e1de85ddd98bc324a99652424c616dfd846c))
## [1.0.21](https://github.com/uptrace/bunrouter/compare/v1.0.20...v1.0.21) (2023-11-20)
## [1.0.20](https://github.com/uptrace/bunrouter/compare/v1.0.19...v1.0.20) (2023-02-12)
## [1.0.19](https://github.com/uptrace/bunrouter/compare/v1.0.18...v1.0.19) (2022-08-30)
## [1.0.18](https://github.com/uptrace/bunrouter/compare/v1.0.17...v1.0.18) (2022-08-09)
### Bug Fixes
* panic when trying to register same routes with different param names ([ace1f17](https://github.com/uptrace/bunrouter/commit/ace1f177c3bae9819612209e6fbb9c0496c8f58a))
## [1.0.17](https://github.com/uptrace/bunrouter/compare/v1.0.16...v1.0.17) (2022-05-25)
### Bug Fixes
* fix another corner case with incorrect param resolving ([7e3d720](https://github.com/uptrace/bunrouter/commit/7e3d7205a85cb90de5b3ddee3e2737e699fe810a))
## [1.0.16](https://github.com/uptrace/bunrouter/compare/v1.0.15...v1.0.16) (2022-05-23)
### Bug Fixes
* fix extracting params from routes with wildcard and named params ([2741aad](https://github.com/uptrace/bunrouter/commit/2741aadef5f5fdf135ffa541cf8b3b8644898ae0))
### Features
* **reqlog:** add trace id ([b2d6582](https://github.com/uptrace/bunrouter/commit/b2d658268dc2d573f5e9e9010a415c6102faa1d1))
## [1.0.15](https://github.com/uptrace/bunrouter/compare/v1.0.14...v1.0.15) (2022-05-08)
### Bug Fixes
* call method not allowed handler once ([37425b7](https://github.com/uptrace/bunrouter/commit/37425b7af9f103a7f26c7f8cc9a04ddac8b805b4))
## [1.0.14](https://github.com/uptrace/bunrouter/compare/v1.0.13...v1.0.14) (2022-04-14)
### Bug Fixes
* use httpsnoop to wrap response writer ([3d86932](https://github.com/uptrace/bunrouter/commit/3d869325af1302c55fdeb8a8ff7e4b7cfd10ece0))
## [1.0.13](https://github.com/uptrace/bunrouter/compare/v1.0.12...v1.0.13) (2022-03-22)
### Bug Fixes
* dedup middlewares applied on the method not allowed handler ([2659f03](https://github.com/uptrace/bunrouter/commit/2659f039be323b8bc4901e35f20a5760e6445640))
### Features
* accept multiple middlewares in Use(fn1, fn2, fn3) ([1486279](https://github.com/uptrace/bunrouter/commit/14862790b51c418384700a5e86aa49833937a772))
## [1.0.12](https://github.com/uptrace/bunrouter/compare/v1.0.11...v1.0.12) (2022-01-19)
### Bug Fixes
* use RawPath when available ([9859bc7](https://github.com/uptrace/bunrouter/commit/9859bc722310e0af24cd8372f585318379ccbbd9))
## [1.0.11](https://github.com/uptrace/bunrouter/compare/v1.0.10...v1.0.11) (2022-01-18)
### Features
- add basicauth middleware
([363da1a](https://github.com/uptrace/bunrouter/commit/363da1a989d943c8bbcf7551ad1a06150f6d1f1f))
* Added `Use` function which is an alias for `WithMiddleware`.
* Updated docs to use cleaner version of the API. Instead of:
```go
router.NewGroup("/some/prefix",
bunrouter.WithMiddleware(middleware1),
bunrouter.WithMiddleware(middleware2),
bunrouter.WithGroup(func(group *bunrouter.Group) {}),
)
```
You can use:
```go
router.Use(middleware1).
Use(middleware2).
WithGroup("/some/prefix", func(group *bunrouter.Group) {})
```
## [1.0.10](https://github.com/uptrace/bunrouter/compare/v1.0.9...v1.0.10) (2022-01-08)
- harden redir checks
## [1.0.9](https://github.com/uptrace/bunrouter/compare/v1.0.8...v1.0.9) (2021-12-23)
### Bug Fixes
- properly handle wildcard node without a slash
([88b4d3e](https://github.com/uptrace/bunrouter/commit/88b4d3ea352c92fc7a87972fc95add8e7f99c328))
### Features
- add Router.ServeHTTPError that returns the error from the handler
([9add167](https://github.com/uptrace/bunrouter/commit/9add167b91c37b42846a486a9965f5212d49bafa))
## [1.0.8](https://github.com/uptrace/bunrouter/compare/v1.0.7...v1.0.8) (2021-11-16)
### Bug Fixes
- properly handle empty root node
([b37ad45](https://github.com/uptrace/bunrouter/commit/b37ad4595c66454f4a768356298c95976e01d7f2))
## [1.0.7](https://github.com/uptrace/bunrouter/compare/v1.0.6...v1.0.7) (2021-11-12)
### Bug Fixes
- don't panic on path that matches routes common prefix
([d89dc38](https://github.com/uptrace/bunrouter/commit/d89dc38defc44bdf4bab13ecb518c2aa42ad9e80))
## [1.0.6](https://github.com/uptrace/bunrouter/compare/v1.0.5...v1.0.6) (2021-11-09)
### Bug Fixes
- propagate error in HTTP compat handlers
([5ed4d41](https://github.com/uptrace/bunrouter/commit/5ed4d41e99e8f6614753393f13e3674df29e7fb9))
## [1.0.5](https://github.com/uptrace/bunrouter/compare/v1.0.4...v1.0.5) (2021-11-08)
### Bug Fixes
- fallback to context when params can't be found
([ee2eb33](https://github.com/uptrace/bunrouter/commit/ee2eb3339ff421dd80566802304a32265f6e28b1))
### Features
- apply middleware to method not allowed handler
([8e295d0](https://github.com/uptrace/bunrouter/commit/8e295d0f01fbdf16061b7a4c53b931e9d709b25b))
## [1.0.4](https://github.com/uptrace/bunrouter/compare/v1.0.3...v1.0.4) (2021-11-07)
### Features
- **reqlog:** support http.Flusher
([938d70a](https://github.com/uptrace/bunrouter/commit/938d70aa4743d3c1492af8421a3fff14df986fa0))
## [1.0.3](https://github.com/uptrace/bunrouter/compare/v1.0.2...v1.0.3) (2021-10-21)
### Bug Fixes
- make routes with only colon nodes work
([fffd754](https://github.com/uptrace/bunrouter/commit/fffd75448f70a508254b0327c933cfda19eac70f))
## [1.0.2](https://github.com/uptrace/bunrouter/compare/v1.0.1...v1.0.2) (2021-10-19)
### Features
- make redirects work for wildcard routes
([04cb9f3](https://github.com/uptrace/bunrouter/commit/04cb9f3fd564d76477dcba7218e29f980503b15d))
## [1.0.1](https://github.com/uptrace/bunrouter/compare/v1.0.0...v1.0.1) (2021-10-15)
### Bug Fixes
- change WithContext to preserve route params
([2ca195a](https://github.com/uptrace/bunrouter/commit/2ca195ac8e7d9242d5110b84ede8d50a360f9a47))
# [1.0.0](https://github.com/uptrace/bunrouter/compare/v1.0.0-rc.2...v1.0.0) (2021-10-14)
### Bug Fixes
- make Slice and Map work on empty request
([609c7a3](https://github.com/uptrace/bunrouter/commit/609c7a3fcb6f5140c1def406efeee01eb0d80a11))
### Features
- allow configuring reqlog from env variables
([486ec10](https://github.com/uptrace/bunrouter/commit/486ec1061ec244559bb072c5b9f78858df8d9fd4))
# [1.0.0-rc.2](https://github.com/uptrace/bunrouter/compare/v1.0.0-rc.1...v1.0.0-rc.2) (2021-10-04)
- Initial release. See the [documentation](https://bunrouter.uptrace.dev/) for details.
+23
View File
@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2014,2015 Daniel Imfeld
Copyright (c) 2015 José Santos
Copyright (c) 2020 Vladimir Mihailenco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
test:
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
echo "go test in $${dir}"; \
(cd "$${dir}" && \
go test ./... && \
go test ./... -short -race && \
go test ./... -run=NONE -bench=. -benchmem && \
env GOOS=linux GOARCH=386 go test ./... && \
go vet); \
done
go_mod_tidy:
go get -u && go mod tidy
set -e; for dir in $(ALL_GO_MOD_DIRS); do \
echo "go mod tidy in $${dir}"; \
(cd "$${dir}" && \
go get -u ./... && \
go mod tidy); \
done
fmt:
gofmt -w -s ./
goimports -w -local github.com/uptrace/bunrouter ./
+195
View File
@@ -0,0 +1,195 @@
# Fast and flexible HTTP router for Go
[![build workflow](https://github.com/uptrace/bunrouter/actions/workflows/build.yml/badge.svg)](https://github.com/uptrace/bunrouter/actions)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/uptrace/bunrouter)](https://pkg.go.dev/github.com/uptrace/bunrouter)
[![Documentation](https://img.shields.io/badge/bunrouter-documentation-informational)](https://bunrouter.uptrace.dev/)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
> BunRouter is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace).
> Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can
> use it to monitor applications and set up automatic alerts to receive notifications via email,
> Slack, Telegram, and others. Star it as well!
**TLDR** BunRouter is as fast as httprouter, but supports middlewares, routing rules priority, and
error handling.
BunRouter is an extremely fast HTTP router for Go with unique combination of features:
- [Middlewares](https://bunrouter.uptrace.dev/guide/golang-http-middlewares.html) allow to extract
common operations from HTTP handlers into reusable functions.
- [Error handling](https://bunrouter.uptrace.dev/guide/golang-http-error-handling.html) allows to
further reduce the size of HTTP handlers by handling errors in middlewares.
- [Routes priority](https://bunrouter.uptrace.dev/guide/golang-router.html#routes-priority) enables
meaningful matching priority for routing rules: first static nodes, then named nodes, lastly
wildcard nodes.
- net/http compatible API which means using minimal API without constructing huge wrappers that try
to do everything: from serving static files to XML generation (for example, `gin.Context` or
`echo.Context`).
| Router | Middlewares | Error handling | Routes priority | net/http API |
| --------------- | ------------------ | ------------------ | ------------------ | ------------------ |
| BunRouter | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| [httprouter][1] | :x: | :x: | :x: | :heavy_check_mark: |
| [Chi][2] | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: |
| [Echo][3] | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
| [Gin][4] | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: |
[1]: https://github.com/julienschmidt/httprouter
[2]: https://github.com/go-chi/chi
[3]: https://github.com/labstack/echo
[4]: https://github.com/go-gin/gin
Learn:
- [Documentation](https://bunrouter.uptrace.dev/)
- [Reference](https://pkg.go.dev/github.com/uptrace/bunrouter)
Examples:
- [Basic example](/example/basic/)
- [http.HandlerFunc example](/example/basic-compat/)
- [httprouter.Handle example](/example/basic-verbose/)
- [CORS example](/example/cors/)
- [Basic auth example](/example/basicauth/)
Projects using BunRouter:
- [Distributed tracing tool](https://github.com/uptrace/uptrace)
- [input-output-hk/cicero](https://github.com/input-output-hk/cicero)
- [RealWorld example application](https://github.com/go-bun/bun-realworld-app)
Benchmarks:
- [web-frameworks-benchmark](https://web-frameworks-benchmark.netlify.app/result?l=go)
- [go-http-routing-benchmark](https://github.com/go-bun/go-http-routing-benchmark)
<details>
<summary>Benchmark results</summary>
```
BenchmarkGin_Param 16019718 74.16 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_Param 12560001 95.04 ns/op 32 B/op 1 allocs/op
BenchmarkBunrouter_Param 50015306 23.81 ns/op 0 B/op 0 allocs/op
BenchmarkGin_Param5 8997234 131.5 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_Param5 4809441 261.3 ns/op 160 B/op 1 allocs/op
BenchmarkBunrouter_Param5 10789635 114.0 ns/op 0 B/op 0 allocs/op
BenchmarkGin_Param20 3953041 302.4 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_Param20 1661373 743.3 ns/op 640 B/op 1 allocs/op
BenchmarkBunrouter_Param20 2462354 482.8 ns/op 0 B/op 0 allocs/op
BenchmarkGin_ParamWrite 9258986 128.0 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_ParamWrite 9908178 123.0 ns/op 32 B/op 1 allocs/op
BenchmarkBunrouter_ParamWrite 15511226 70.62 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GithubStatic 12781513 94.17 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GithubStatic 30077443 37.36 ns/op 0 B/op 0 allocs/op
BenchmarkBunrouter_GithubStatic 37160334 32.41 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GithubParam 6971791 169.2 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GithubParam 5464755 217.4 ns/op 96 B/op 1 allocs/op
BenchmarkBunrouter_GithubParam 12047902 101.2 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GithubAll 32758 37382 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GithubAll 27324 43932 ns/op 13792 B/op 167 allocs/op
BenchmarkBunrouter_GithubAll 57910 20914 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GPlusStatic 17788194 69.13 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlusStatic 60191341 19.84 ns/op 0 B/op 0 allocs/op
BenchmarkBunrouter_GPlusStatic 87114368 14.06 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GPlusParam 10075399 119.5 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlusParam 8272046 149.2 ns/op 64 B/op 1 allocs/op
BenchmarkBunrouter_GPlusParam 37359979 32.43 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GPlus2Params 7375279 162.9 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlus2Params 6538942 186.7 ns/op 64 B/op 1 allocs/op
BenchmarkBunrouter_GPlus2Params 19681939 61.51 ns/op 0 B/op 0 allocs/op
BenchmarkGin_GPlusAll 647716 1752 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlusAll 590356 2085 ns/op 640 B/op 11 allocs/op
BenchmarkBunrouter_GPlusAll 1685287 712.8 ns/op 0 B/op 0 allocs/op
BenchmarkGin_ParseStatic 14566458 76.58 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_ParseStatic 52994076 21.02 ns/op 0 B/op 0 allocs/op
BenchmarkBunrouter_ParseStatic 50583933 23.83 ns/op 0 B/op 0 allocs/op
BenchmarkGin_ParseParam 13443874 90.66 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_ParseParam 8825664 135.6 ns/op 64 B/op 1 allocs/op
BenchmarkBunrouter_ParseParam 38058278 31.33 ns/op 0 B/op 0 allocs/op
BenchmarkGin_Parse2Params 10179813 118.1 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_Parse2Params 7801735 152.9 ns/op 64 B/op 1 allocs/op
BenchmarkBunrouter_Parse2Params 23704574 50.78 ns/op 0 B/op 0 allocs/op
BenchmarkGin_ParseAll 394884 3073 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_ParseAll 410238 3011 ns/op 640 B/op 16 allocs/op
BenchmarkBunrouter_ParseAll 810908 1487 ns/op 0 B/op 0 allocs/op
BenchmarkGin_StaticAll 50658 23699 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_StaticAll 105313 11518 ns/op 0 B/op 0 allocs/op
BenchmarkBunrouter_StaticAll 99674 12188 ns/op 0 B/op 0 allocs/op
```
</details>
## Quickstart
Install:
```shell
go get github.com/uptrace/bunrouter
```
Run the [example](/example/basic/):
```go
package main
import (
"html/template"
"log"
"net/http"
"github.com/uptrace/bunrouter"
"github.com/uptrace/bunrouter/extra/reqlog"
)
func main() {
router := bunrouter.New(
bunrouter.Use(reqlog.NewMiddleware()),
)
router.GET("/", indexHandler)
router.WithGroup("/api", func(g *bunrouter.Group) {
g.GET("/users/:id", debugHandler)
g.GET("/users/current", debugHandler)
g.GET("/users/*path", debugHandler)
})
log.Println("listening on http://localhost:9999")
log.Println(http.ListenAndServe(":9999", router))
}
func indexHandler(w http.ResponseWriter, req bunrouter.Request) error {
return indexTemplate().Execute(w, nil)
}
func debugHandler(w http.ResponseWriter, req bunrouter.Request) error {
return bunrouter.JSON(w, bunrouter.H{
"route": req.Route(),
"params": req.Params().Map(),
})
}
var indexTmpl = `
<html>
<h1>Welcome</h1>
<ul>
<li><a href="/api/users/123">/api/users/123</a></li>
<li><a href="/api/users/current">/api/users/current</a></li>
<li><a href="/api/users/foo/bar">/api/users/foo/bar</a></li>
</ul>
</html>
`
func indexTemplate() *template.Template {
return template.Must(template.New("index").Parse(indexTmpl))
}
```
See the [Golang Router documentation](https://bunrouter.uptrace.dev/) for details.
## See also
- [Golang ORM](https://bun.uptrace.dev/)
- [Golang msgpack](https://msgpack.uptrace.dev/)
- [Golang message task queue](https://github.com/vmihailenco/taskq)
- [Distributed tracing tools ](https://get.uptrace.dev/compare/distributed-tracing-tools.html)
+1
View File
@@ -0,0 +1 @@
module.exports = { extends: ["@commitlint/config-conventional"] };
+86
View File
@@ -0,0 +1,86 @@
package bunrouter
import "net/http"
type config struct {
notFoundHandler HandlerFunc
methodNotAllowedHandler HandlerFunc
group *Group
}
type Option interface {
apply(cfg *config)
}
type option func(cfg *config)
func (fn option) apply(cfg *config) {
fn(cfg)
}
// WithNotFoundHandler is called when there is no a matching pattern.
// The default NotFoundHandler is http.NotFound.
func WithNotFoundHandler(handler HandlerFunc) Option {
return option(func(c *config) {
c.notFoundHandler = c.group.wrap(handler)
})
}
// MethodNotAllowedHandler is called when a route matches, but that
// route does not have a handler for the requested method. The default
// handler just writes the status code http.StatusMethodNotAllowed.
func WithMethodNotAllowedHandler(handler HandlerFunc) Option {
return option(func(c *config) {
c.methodNotAllowedHandler = handler
})
}
//------------------------------------------------------------------------------
type GroupOption interface {
Option
groupOption()
}
type groupOption func(cfg *config)
func (fn groupOption) apply(cfg *config) {
fn(cfg)
}
func (fn groupOption) groupOption() {}
// WithGroup calls the fn with the current Group.
func WithGroup(fn func(g *Group)) GroupOption {
return groupOption(func(c *config) {
fn(c.group)
})
}
// WithMiddleware adds a middleware handler to the Group's middleware stack.
func WithMiddleware(fns ...MiddlewareFunc) GroupOption {
return groupOption(func(c *config) {
c.group.stack = append(c.group.stack, fns...)
})
}
// Use is an alias for WithMiddleware.
func Use(fns ...MiddlewareFunc) GroupOption {
return WithMiddleware(fns...)
}
// WithHandler is like WithMiddleware, but the handler can't modify the request.
func WithHandler(fn HandlerFunc) GroupOption {
return groupOption(func(c *config) {
middleware := func(next HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, req Request) error {
if err := fn(w, req); err != nil {
return err
}
return next(w, req)
}
}
c.group.stack = append(c.group.stack, middleware)
})
}
+256
View File
@@ -0,0 +1,256 @@
package bunrouter
import (
"fmt"
"net/http"
)
// Group is a group of routes and middlewares.
type Group struct {
router *Router
path string
stack []MiddlewareFunc
}
// NewGroup adds a sub-group to this group.
func (g *Group) NewGroup(path string, opts ...GroupOption) *Group {
group := &Group{
router: g.router,
path: joinPath(g.path, path),
stack: g.cloneStack(),
}
cfg := &config{
group: group,
}
for _, opt := range opts {
opt.apply(cfg)
}
return group
}
func (g *Group) cloneStack() []MiddlewareFunc {
return g.stack[:len(g.stack):len(g.stack)]
}
func (g *Group) Use(middlewares ...MiddlewareFunc) *Group {
return g.NewGroup("", Use(middlewares...))
}
func (g *Group) WithMiddleware(middleware MiddlewareFunc) *Group {
return g.NewGroup("", WithMiddleware(middleware))
}
func (g *Group) WithGroup(path string, fn func(g *Group)) {
fn(g.NewGroup(path))
}
func (g *Group) Handle(meth string, path string, handler HandlerFunc) {
g.router.mu.Lock()
defer g.router.mu.Unlock()
checkPath(path)
path = g.path + path
if path == "" {
panic("path can't be empty")
}
node, params := g.router.tree.addRoute(path)
if node.handlerMap != nil {
if h := node.handlerMap.Get(meth); h != nil {
if node.route == path {
panic(fmt.Errorf("route %q already handles %s", node.route, meth))
}
panic(fmt.Errorf("routes %q and %q can't both handle %s", node.route, path, meth))
}
}
node.setHandler(meth, &routeHandler{
fn: g.wrap(handler),
params: params,
})
if node.handlerMap.notAllowed == nil {
node.handlerMap.notAllowed = &routeHandler{
fn: g.wrap(g.router.methodNotAllowedHandler),
params: params,
}
}
}
// Syntactic sugar for Handle("GET", path, handler)
func (g *Group) GET(path string, handler HandlerFunc) {
g.Handle("GET", path, handler)
}
// Syntactic sugar for Handle("POST", path, handler)
func (g *Group) POST(path string, handler HandlerFunc) {
g.Handle("POST", path, handler)
}
// Syntactic sugar for Handle("PUT", path, handler)
func (g *Group) PUT(path string, handler HandlerFunc) {
g.Handle("PUT", path, handler)
}
// Syntactic sugar for Handle("DELETE", path, handler)
func (g *Group) DELETE(path string, handler HandlerFunc) {
g.Handle("DELETE", path, handler)
}
// Syntactic sugar for Handle("PATCH", path, handler)
func (g *Group) PATCH(path string, handler HandlerFunc) {
g.Handle("PATCH", path, handler)
}
// Syntactic sugar for Handle("HEAD", path, handler)
func (g *Group) HEAD(path string, handler HandlerFunc) {
g.Handle("HEAD", path, handler)
}
// Syntactic sugar for Handle("OPTIONS", path, handler)
func (g *Group) OPTIONS(path string, handler HandlerFunc) {
g.Handle("OPTIONS", path, handler)
}
func (g *Group) wrap(handler HandlerFunc) HandlerFunc {
for i := len(g.stack) - 1; i >= 0; i-- {
handler = g.stack[i](handler)
}
return handler
}
func (g *Group) Compat() *CompatGroup {
return &CompatGroup{group: g}
}
func (g *Group) Verbose() *VerboseGroup {
return &VerboseGroup{group: g}
}
//------------------------------------------------------------------------------
// CompatGroup is like Group, but it works with http.HandlerFunc instead of bunrouter handler.
type CompatGroup struct {
group *Group
}
func (g CompatGroup) NewGroup(path string, opts ...GroupOption) *CompatGroup {
return &CompatGroup{group: g.group.NewGroup(path, opts...)}
}
func (g CompatGroup) WithMiddleware(middleware MiddlewareFunc) *CompatGroup {
return &CompatGroup{group: g.group.WithMiddleware(middleware)}
}
func (g CompatGroup) WithGroup(path string, fn func(g *CompatGroup)) {
fn(g.NewGroup(path))
}
func (g CompatGroup) Handle(method string, path string, handler http.HandlerFunc) {
g.group.Handle(method, path, HTTPHandlerFunc(handler))
}
func (g CompatGroup) GET(path string, handler http.HandlerFunc) {
g.Handle(http.MethodGet, path, handler)
}
func (g CompatGroup) POST(path string, handler http.HandlerFunc) {
g.Handle("POST", path, handler)
}
func (g CompatGroup) PUT(path string, handler http.HandlerFunc) {
g.Handle("PUT", path, handler)
}
func (g CompatGroup) DELETE(path string, handler http.HandlerFunc) {
g.Handle("DELETE", path, handler)
}
func (g CompatGroup) PATCH(path string, handler http.HandlerFunc) {
g.Handle("PATCH", path, handler)
}
func (g CompatGroup) HEAD(path string, handler http.HandlerFunc) {
g.Handle("HEAD", path, handler)
}
func (g CompatGroup) OPTIONS(path string, handler http.HandlerFunc) {
g.Handle("OPTIONS", path, handler)
}
//------------------------------------------------------------------------------
type VerboseHandlerFunc func(w http.ResponseWriter, req *http.Request, ps Params)
// VerboseGroup is like Group, but it works with VerboseHandlerFunc instead of bunrouter handler.
type VerboseGroup struct {
group *Group
}
func (g VerboseGroup) NewGroup(path string, opts ...GroupOption) *VerboseGroup {
return &VerboseGroup{group: g.group.NewGroup(path, opts...)}
}
func (g VerboseGroup) WithMiddleware(middleware MiddlewareFunc) *VerboseGroup {
return &VerboseGroup{group: g.group.WithMiddleware(middleware)}
}
func (g VerboseGroup) WithGroup(path string, fn func(g *VerboseGroup)) {
fn(g.NewGroup(path))
}
func (g VerboseGroup) Handle(method string, path string, handler VerboseHandlerFunc) {
g.group.Handle(method, path, func(w http.ResponseWriter, req Request) error {
handler(w, req.Request, req.Params())
return nil
})
}
func (g VerboseGroup) GET(path string, handler VerboseHandlerFunc) {
g.Handle(http.MethodGet, path, handler)
}
func (g VerboseGroup) POST(path string, handler VerboseHandlerFunc) {
g.Handle("POST", path, handler)
}
func (g VerboseGroup) PUT(path string, handler VerboseHandlerFunc) {
g.Handle("PUT", path, handler)
}
func (g VerboseGroup) DELETE(path string, handler VerboseHandlerFunc) {
g.Handle("DELETE", path, handler)
}
func (g VerboseGroup) PATCH(path string, handler VerboseHandlerFunc) {
g.Handle("PATCH", path, handler)
}
func (g VerboseGroup) HEAD(path string, handler VerboseHandlerFunc) {
g.Handle("HEAD", path, handler)
}
func (g VerboseGroup) OPTIONS(path string, handler VerboseHandlerFunc) {
g.Handle("OPTIONS", path, handler)
}
//------------------------------------------------------------------------------
func joinPath(base, path string) string {
checkPath(path)
path = base + path
// Don't want trailing slash as all sub-paths start with slash
if len(path) > 1 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
return path
}
func checkPath(path string) {
// All non-empty paths must start with a slash
if len(path) > 0 && path[0] != '/' {
panic(fmt.Sprintf("path %s must start with a slash", path))
}
}
+396
View File
@@ -0,0 +1,396 @@
package bunrouter
import (
"fmt"
"net/http"
"sort"
"strings"
)
type node struct {
route string
part string
handlerMap *handlerMap
parent *node
colon *node
isWC bool
nodes []*node
index struct {
table []uint8 // index table for the nodes: firstChar-minChar => node position
minChar byte // min char in the table
maxChar byte // max char in the table
}
}
func (n *node) addRoute(route string) (*node, map[string]int) {
parts, params := splitRoute(route)
currNode := n
for _, part := range parts {
currNode = currNode.addPart(part)
}
if currNode.route == "" {
currNode.route = route
}
n.indexNodes()
return currNode, params
}
func (n *node) addPart(part string) *node {
if part == "*" {
n.isWC = true
return n
}
if part == ":" {
if n.colon == nil {
n.colon = &node{part: ":"}
}
return n.colon
}
for childNodeIndex, childNode := range n.nodes {
if childNode.part[0] != part[0] {
continue
}
// Check for a common prefix.
for i, c := range []byte(part) {
if i == len(childNode.part) {
break
}
if c == childNode.part[i] {
continue
}
// Create a node for the common prefix.
childNode.part = childNode.part[i:]
newNode := &node{part: part[i:]}
n.nodes[childNodeIndex] = &node{
part: part[:i], // common prefix
nodes: []*node{childNode, newNode},
}
return newNode
}
// Parts match completely.
switch {
case len(part) > len(childNode.part): // part is bigger
part = part[len(childNode.part):]
return childNode.addPart(part)
case len(part) < len(childNode.part): // part is smaller
childNode.part = childNode.part[len(part):]
newNode := &node{part: part}
newNode.nodes = []*node{childNode}
n.nodes[childNodeIndex] = newNode
return newNode
default:
return childNode // exact match
}
}
node := &node{part: part}
n.nodes = append(n.nodes, node)
return node
}
func (n *node) findRoute(meth, path string) (*node, *routeHandler, int) {
if path == "" {
return nil, nil, 0
}
path = path[1:] // strip leading "/"
if path == "" {
if n.handlerMap != nil {
return n, n.handlerMap.Get(meth), 0
}
return nil, nil, 0
}
return n._findRoute(meth, path)
}
func (n *node) _findRoute(meth, path string) (*node, *routeHandler, int) {
var found *node
if firstChar := path[0]; firstChar >= n.index.minChar && firstChar <= n.index.maxChar {
if i := n.index.table[firstChar-n.index.minChar]; i != 0 {
childNode := n.nodes[i-1]
if childNode.part == path {
if childNode.handlerMap != nil {
if handler := childNode.handlerMap.Get(meth); handler != nil {
return childNode, handler, 0
}
found = childNode
}
} else {
partLen := len(childNode.part)
if strings.HasPrefix(path, childNode.part) {
node, handler, wildcardLen := childNode._findRoute(meth, path[partLen:])
if handler != nil {
return node, handler, wildcardLen
}
if node != nil {
found = node
}
}
}
}
}
if n.colon != nil {
if i := strings.IndexByte(path, '/'); i > 0 {
node, handler, wildcardLen := n.colon._findRoute(meth, path[i:])
if handler != nil {
return node, handler, wildcardLen
}
} else if n.colon.handlerMap != nil {
if handler := n.colon.handlerMap.Get(meth); handler != nil {
return n.colon, handler, 0
}
if found == nil {
found = n.colon
}
}
}
if n.isWC && n.handlerMap != nil {
if handler := n.handlerMap.Get(meth); handler != nil {
return n, handler, len(path)
}
if found == nil {
found = n
}
}
return found, nil, 0
}
func (n *node) indexNodes() {
if len(n.nodes) > 0 {
n._indexNodes()
}
if n.colon != nil {
n.colon.parent = n
n.colon.indexNodes()
}
}
func (n *node) _indexNodes() {
sort.Slice(n.nodes, func(i, j int) bool {
return n.nodes[i].part[0] < n.nodes[j].part[0]
})
n.index.minChar = n.nodes[0].part[0]
n.index.maxChar = n.nodes[len(n.nodes)-1].part[0]
// Reset index.
if size := int(n.index.maxChar - n.index.minChar + 1); len(n.index.table) != size {
n.index.table = make([]uint8, size)
} else {
for i := range n.index.table {
n.index.table[i] = 0
}
}
// Index nodes by the first char in a part.
for childNodeIndex, childNode := range n.nodes {
childNode.parent = n
childNode.indexNodes()
firstChar := childNode.part[0] - n.index.minChar
n.index.table[firstChar] = uint8(childNodeIndex + 1)
}
}
func (n *node) setHandler(verb string, handler *routeHandler) {
if n.handlerMap == nil {
n.handlerMap = newHandlerMap()
}
n.handlerMap.Set(verb, handler)
}
//------------------------------------------------------------------------------
type routeParser struct {
segments []string
i int
acc []string
parts []string
}
func (p *routeParser) valid() bool {
return p.i < len(p.segments)
}
func (p *routeParser) next() string {
s := p.segments[p.i]
p.i++
return s
}
func (p *routeParser) accumulate(s string) {
p.acc = append(p.acc, s)
}
func (p *routeParser) finalizePart(withSlash bool) {
if part := join(p.acc, withSlash); part != "" {
p.parts = append(p.parts, part)
}
p.acc = p.acc[:0]
if p.valid() {
p.acc = append(p.acc, "")
}
}
func join(ss []string, withSlash bool) string {
if len(ss) == 0 {
return ""
}
s := strings.Join(ss, "/")
if withSlash {
return s + "/"
}
return s
}
func splitRoute(route string) (_ []string, _ map[string]int) {
if route == "" || route[0] != '/' {
panic(fmt.Errorf("invalid route: %q", route))
}
if route == "/" {
return []string{}, nil
}
route = route[1:] // trim first "/"
ss := strings.Split(route, "/")
if len(ss) == 0 {
panic(fmt.Errorf("invalid route: %q", route))
}
p := routeParser{
segments: ss,
}
var params []string
for p.valid() {
segment := p.next()
if segment == "" {
p.accumulate("")
continue
}
switch firstChar := segment[0]; firstChar {
case ':':
p.finalizePart(true)
p.parts = append(p.parts, ":")
params = append(params, segment[1:])
case '*':
p.finalizePart(true)
p.parts = append(p.parts, "*")
params = append(params, segment[1:])
default:
p.accumulate(segment)
}
}
p.finalizePart(false)
if len(params) > 0 {
return p.parts, paramMap(route, params)
}
return p.parts, nil
}
func paramMap(route string, params []string) map[string]int {
m := make(map[string]int, len(params))
for i, param := range params {
if param == "" {
panic(fmt.Errorf("param must have a name: %q", route))
}
m[param] = i
}
return m
}
//------------------------------------------------------------------------------
type handlerMap struct {
get *routeHandler
post *routeHandler
put *routeHandler
delete *routeHandler
head *routeHandler
options *routeHandler
patch *routeHandler
notAllowed *routeHandler
}
type routeHandler struct {
fn HandlerFunc
params map[string]int // param name => param position
}
func newHandlerMap() *handlerMap {
return new(handlerMap)
}
func (h *handlerMap) Get(meth string) *routeHandler {
switch meth {
case http.MethodGet:
return h.get
case http.MethodPost:
return h.post
case http.MethodPut:
return h.put
case http.MethodDelete:
return h.delete
case http.MethodHead:
return h.head
case http.MethodOptions:
return h.options
case http.MethodPatch:
return h.patch
default:
return nil
}
}
func (h *handlerMap) Set(meth string, handler *routeHandler) {
switch meth {
case http.MethodGet:
h.get = handler
case http.MethodPost:
h.post = handler
case http.MethodPut:
h.put = handler
case http.MethodDelete:
h.delete = handler
case http.MethodHead:
h.head = handler
case http.MethodOptions:
h.options = handler
case http.MethodPatch:
h.patch = handler
default:
panic(fmt.Errorf("unknown HTTP method: %s", meth))
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"name": "bunrouter",
"version": "1.0.23"
}
+150
View File
@@ -0,0 +1,150 @@
// Copyright 2013 Julien Schmidt. All rights reserved.
// Based on the path package, Copyright 2009 The Go Authors.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package bunrouter
// CleanPath is the URL version of path.Clean, it returns a canonical URL path
// for p, eliminating . and .. elements.
//
// The following rules are applied iteratively until no further processing can
// be done:
// 1. Replace multiple slashes with a single slash.
// 2. Eliminate each . path name element (the current directory).
// 3. Eliminate each inner .. path name element (the parent directory)
// along with the non-.. element that precedes it.
// 4. Eliminate .. elements that begin a rooted path:
// that is, replace "/.." by "/" at the beginning of a path.
//
// If the result of this process is an empty string, "/" is returned
func CleanPath(p string) string {
const stackBufSize = 128
// Turn empty string into "/"
if p == "" {
return "/"
}
// Reasonably sized buffer on stack to avoid allocations in the common case.
// If a larger buffer is required, it gets allocated dynamically.
buf := make([]byte, 0, stackBufSize)
n := len(p)
// Invariants:
// reading from path; r is index of next byte to process.
// writing to buf; w is index of next byte to write.
// path must start with '/'
r := 1
w := 1
if p[0] != '/' {
r = 0
if n+1 > stackBufSize {
buf = make([]byte, n+1)
} else {
buf = buf[:n+1]
}
buf[0] = '/'
}
trailing := n > 1 && p[n-1] == '/'
// A bit more clunky without a 'lazybuf' like the path package, but the loop
// gets completely inlined (bufApp calls).
// So in contrast to the path package this loop has no expensive function
// calls (except make, if needed).
for r < n {
switch {
case p[r] == '/':
// empty path element, trailing slash is added after the end
r++
case p[r] == '.' && r+1 == n:
trailing = true
r++
case p[r] == '.' && p[r+1] == '/':
// . element
r += 2
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
// .. element: remove to last /
r += 3
if w > 1 {
// can backtrack
w--
if len(buf) == 0 {
for w > 1 && p[w] != '/' {
w--
}
} else {
for w > 1 && buf[w] != '/' {
w--
}
}
}
default:
// Real path element.
// Add slash if needed
if w > 1 {
bufApp(&buf, p, w, '/')
w++
}
// Copy element
for r < n && p[r] != '/' {
bufApp(&buf, p, w, p[r])
w++
r++
}
}
}
// Re-append trailing slash
if trailing && w > 1 {
bufApp(&buf, p, w, '/')
w++
}
// If the original string was not modified (or only shortened at the end),
// return the respective substring of the original string.
// Otherwise return a new string from the buffer.
if len(buf) == 0 {
return p[:w]
}
return string(buf[:w])
}
// Internal helper to lazily create a buffer if necessary.
// Calls to this function get inlined.
func bufApp(buf *[]byte, s string, w int, c byte) {
b := *buf
if len(b) == 0 {
// No modification of the original string so far.
// If the next character is the same as in the original string, we do
// not yet have to allocate a buffer.
if s[w] == c {
return
}
// Otherwise use either the stack buffer, if it is large enough, or
// allocate a new buffer on the heap, and copy all previous characters.
if l := len(s); l > cap(b) {
*buf = make([]byte, len(s))
} else {
*buf = (*buf)[:l]
}
b = *buf
copy(b, s[:w])
}
b[w] = c
}
+377
View File
@@ -0,0 +1,377 @@
package bunrouter
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
)
type routeCtxKey struct{}
// ParamsFromContext retrieves route parameters from the given context.
// It returns an empty Params if no parameters are found.
func ParamsFromContext(ctx context.Context) Params {
if ctx == nil {
return Params{}
}
route, _ := ctx.Value(routeCtxKey{}).(Params)
return route
}
// contextWithParams stores route parameters in the context.
func contextWithParams(ctx context.Context, params Params) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, routeCtxKey{}, params)
}
//------------------------------------------------------------------------------
// HTTPHandler converts http.Handler to bunrouter.HandlerFunc.
func HTTPHandler(handler http.Handler) HandlerFunc {
if handler == nil {
panic("bunrouter: nil handler")
}
return HTTPHandlerFunc(handler.ServeHTTP)
}
// HTTPHandlerFunc converts http.HandlerFunc to bunrouter.HandlerFunc.
func HTTPHandlerFunc(handler http.HandlerFunc) HandlerFunc {
if handler == nil {
panic("bunrouter: nil handler")
}
return func(w http.ResponseWriter, req Request) (err error) {
if w == nil {
return fmt.Errorf("bunrouter: nil response writer")
}
ctx := contextWithParams(req.Context(), req.params)
defer func() {
if v := recover(); v != nil {
var ok bool
err, ok = v.(error)
if !ok {
err = fmt.Errorf("bunrouter: panic recovered: %v", v)
}
}
}()
handler.ServeHTTP(w, req.Request.WithContext(ctx))
return err
}
}
// HandlerFunc is a function that handles HTTP requests in bunrouter.
// It returns an error that will be handled by the router.
type HandlerFunc func(w http.ResponseWriter, req Request) error
var _ http.Handler = (*HandlerFunc)(nil)
func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if h == nil {
http.Error(w, "Handler not found", http.StatusInternalServerError)
return
}
if req == nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
if err := h(w, NewRequest(req)); err != nil {
code := http.StatusInternalServerError
if httpErr, ok := err.(HTTPError); ok {
code = httpErr.StatusCode()
}
http.Error(w, err.Error(), code)
}
}
// HTTPError represents an HTTP error with a status code
type HTTPError interface {
error
StatusCode() int
}
// MiddlewareFunc is a function that wraps a HandlerFunc to provide middleware functionality.
type MiddlewareFunc func(next HandlerFunc) HandlerFunc
//------------------------------------------------------------------------------
// Request extends http.Request with route parameters.
type Request struct {
*http.Request
params Params
}
// NewRequest creates a new Request instance from an http.Request.
func NewRequest(req *http.Request) Request {
if req == nil {
req = &http.Request{}
}
return Request{
Request: req,
params: ParamsFromContext(req.Context()),
}
}
func newRequestParams(req *http.Request, params Params) Request {
if req == nil {
req = &http.Request{}
}
return Request{
Request: req,
params: params,
}
}
// WithContext returns a new Request with the provided context.
func (req Request) WithContext(ctx context.Context) Request {
if ctx == nil {
ctx = context.Background()
}
return Request{
Request: req.Request.WithContext(ctx),
params: req.params,
}
}
// Params returns the route parameters associated with the request.
func (req Request) Params() Params {
return req.params
}
// Param returns the value of the named parameter or empty string if not found.
func (req Request) Param(key string) string {
return req.Params().ByName(key)
}
// Route returns the matched route pattern.
func (req Request) Route() string {
return req.Params().Route()
}
//------------------------------------------------------------------------------
// Params holds route parameters and route information.
type Params struct {
path string
node *node
handler *routeHandler
wildcardLen uint16
}
// IsZero returns true if Params has no associated route node.
func (ps Params) IsZero() bool {
return ps.node == nil
}
// Route returns the route pattern that matched the request.
func (ps Params) Route() string {
if ps.node != nil {
return ps.node.route
}
return ""
}
// Get returns the value of the named parameter and whether it was found.
func (ps Params) Get(name string) (string, bool) {
if ps.node == nil || ps.handler == nil {
return "", false
}
if i, ok := ps.handler.params[name]; ok {
return ps.findParam(i)
}
return "", false
}
func (ps *Params) findParam(paramIndex int) (string, bool) {
if ps.node == nil || ps.handler == nil {
return "", false
}
path := ps.path
pathLen := len(path)
if pathLen == 0 {
return "", false
}
currNode := ps.node
currParamIndex := len(ps.handler.params) - 1
if paramIndex < 0 || paramIndex > currParamIndex {
return "", false
}
// Wildcard can be only in the final node.
if ps.node.isWC {
if currParamIndex == paramIndex {
if int(ps.wildcardLen) > pathLen {
return "", false
}
pathLen -= int(ps.wildcardLen)
return path[pathLen:], true
}
currParamIndex--
if int(ps.wildcardLen) > pathLen {
return "", false
}
pathLen -= int(ps.wildcardLen)
path = path[:pathLen]
}
for currNode != nil {
if currNode.part[0] != ':' { // static node
partLen := len(currNode.part)
if partLen > pathLen {
return "", false
}
pathLen -= partLen
path = path[:pathLen]
currNode = currNode.parent
continue
}
i := strings.LastIndexByte(path, '/')
if i == -1 {
return "", false
}
pathLen = i + 1
if currParamIndex == paramIndex {
return path[pathLen:], true
}
currParamIndex--
path = path[:pathLen]
currNode = currNode.parent
}
return "", false
}
// ByName returns the value of the named parameter or empty string if not found.
func (ps Params) ByName(name string) string {
s, _ := ps.Get(name)
return s
}
// Int parses the named parameter as an integer.
func (ps Params) Int(name string) (int, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.Atoi(value)
}
// Uint32 parses the named parameter as an unsigned 32-bit integer.
func (ps Params) Uint32(name string) (uint32, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
n, err := strconv.ParseUint(value, 10, 32)
return uint32(n), err
}
// Uint64 parses the named parameter as an unsigned 64-bit integer.
func (ps Params) Uint64(name string) (uint64, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.ParseUint(value, 10, 64)
}
// Int32 parses the named parameter as a signed 32-bit integer.
func (ps Params) Int32(name string) (int32, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
n, err := strconv.ParseInt(value, 10, 32)
return int32(n), err
}
// Int64 parses the named parameter as a signed 64-bit integer.
func (ps Params) Int64(name string) (int64, error) {
value := ps.ByName(name)
if value == "" {
return 0, fmt.Errorf("bunrouter: param '%s' not found", name)
}
return strconv.ParseInt(value, 10, 64)
}
// Map returns route parameters as a map[string]string.
func (ps Params) Map() map[string]string {
if ps.handler == nil || len(ps.handler.params) == 0 {
return make(map[string]string)
}
m := make(map[string]string, len(ps.handler.params))
for param, index := range ps.handler.params {
if value, ok := ps.findParam(index); ok {
m[param] = value
}
}
return m
}
// Param represents a key-value pair of route parameters.
type Param struct {
Key string
Value string
}
// Slice returns route parameters as a slice of Param.
func (ps Params) Slice() []Param {
if ps.handler == nil || len(ps.handler.params) == 0 {
return []Param{}
}
slice := make([]Param, len(ps.handler.params))
for param, index := range ps.handler.params {
if value, ok := ps.findParam(index); ok {
slice[index] = Param{Key: param, Value: value}
}
}
return slice
}
//------------------------------------------------------------------------------
// H is a shorthand for map[string]interface{}.
type H map[string]interface{}
// JSON marshals the value as JSON and writes it to the response writer.
// It sets the Content-Type header to application/json.
//
// Don't hesitate to copy-paste this function to your project and customize it as necessary.
func JSON(w http.ResponseWriter, value interface{}) error {
if w == nil {
return fmt.Errorf("bunrouter: nil response writer")
}
w.Header().Set("Content-Type", "application/json")
if value == nil {
if _, err := w.Write([]byte("null")); err != nil {
return fmt.Errorf("bunrouter: failed to write null response: %w", err)
}
return nil
}
enc := json.NewEncoder(w)
if err := enc.Encode(value); err != nil {
return fmt.Errorf("bunrouter: JSON encoding error: %w", err)
}
return nil
}
+179
View File
@@ -0,0 +1,179 @@
package bunrouter
import (
"net/http"
"net/url"
"strings"
"sync"
)
// Router is the main router structure that implements HTTP request routing.
// It maintains a routing tree and handles incoming HTTP requests.
type Router struct {
config // embedded router configuration
Group // embedded route group
mu sync.Mutex // protects the routing tree
tree node // root node of the routing tree
}
// New creates and returns a new Router instance with the given options.
// Options can include middleware, custom handlers for 404 and 405 responses,
// and other router configurations.
func New(opts ...Option) *Router {
r := &Router{
tree: node{
part: "/",
},
}
r.Group.router = r
r.config.group = &r.Group
r.methodNotAllowedHandler = methodNotAllowedHandler
for _, opt := range opts {
opt.apply(&r.config)
}
// Do it after processing middlewares from the options.
if r.notFoundHandler == nil {
r.notFoundHandler = r.group.wrap(notFoundHandler)
}
return r
}
var _ http.Handler = (*Router)(nil)
// ServeHTTP implements the http.Handler interface.
// It processes the incoming HTTP request and routes it to the appropriate handler.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
_ = r.ServeHTTPError(w, req)
}
// ServeHTTPError is similar to ServeHTTP but also returns any error
// that occurred during request handling.
func (r *Router) ServeHTTPError(w http.ResponseWriter, req *http.Request) error {
handler, params := r.lookup(w, req)
return handler(w, newRequestParams(req, params))
}
// lookup finds the appropriate handler and parameters for the given HTTP request.
// It returns the handler function and parsed route parameters.
func (r *Router) lookup(w http.ResponseWriter, req *http.Request) (HandlerFunc, Params) {
path := req.URL.RawPath
if path == "" {
path = req.URL.Path
}
node, handler, wildcardLen := r.tree.findRoute(req.Method, path)
if node == nil {
if redir := r.redir(req.Method, path); redir != nil {
return redir, Params{}
}
return r.notFoundHandler, Params{}
}
if handler == nil {
if redir := r.redir(req.Method, path); redir != nil {
return redir, Params{}
}
handler = node.handlerMap.notAllowed
}
return handler.fn, Params{
node: node,
handler: handler,
path: path,
wildcardLen: uint16(wildcardLen),
}
}
// redir handles URL redirects for cleaned paths and trailing slash variations.
// It returns a redirect handler if a redirect is needed, nil otherwise.
func (r *Router) redir(method, path string) HandlerFunc {
if path == "/" {
return nil
}
// Path was not found. Try cleaning it up and search again.
if cleanPath := CleanPath(path); cleanPath != path {
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
return redirectHandler(cleanPath)
}
}
if strings.HasSuffix(path, "/") {
// Try path without a slash.
cleanPath := path[:len(path)-1]
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
return redirectHandler(cleanPath)
}
return nil
}
// Try path with a slash.
cleanPath := path + "/"
if _, handler, _ := r.tree.findRoute(method, cleanPath); handler != nil {
return redirectHandler(cleanPath)
}
return nil
}
//------------------------------------------------------------------------------
// CompatRouter provides compatibility layer for the router.
type CompatRouter struct {
*Router
*CompatGroup
}
// Compat returns a new CompatRouter instance that wraps the current router.
func (r *Router) Compat() *CompatRouter {
return &CompatRouter{
Router: r,
CompatGroup: r.Group.Compat(),
}
}
// VerboseRouter provides a verbose interface to the router.
type VerboseRouter struct {
*Router
*VerboseGroup
}
// Verbose returns a new VerboseRouter instance that wraps the current router.
func (r *Router) Verbose() *VerboseRouter {
return &VerboseRouter{
Router: r,
VerboseGroup: r.Group.Verbose(),
}
}
//------------------------------------------------------------------------------
// redirectHandler creates a handler function that performs HTTP redirects
// to the specified new path while preserving query parameters and fragments.
func redirectHandler(newPath string) HandlerFunc {
return func(w http.ResponseWriter, req Request) error {
newURL := url.URL{
Path: newPath,
RawQuery: req.URL.RawQuery,
Fragment: req.URL.Fragment,
}
http.Redirect(w, req.Request, newURL.String(), http.StatusMovedPermanently)
return nil
}
}
// methodNotAllowedHandler is the default handler for requests with methods
// that are not allowed for the matched route.
func methodNotAllowedHandler(w http.ResponseWriter, r Request) error {
w.WriteHeader(http.StatusMethodNotAllowed)
return nil
}
// notFoundHandler is the default handler for requests that don't match any route.
func notFoundHandler(w http.ResponseWriter, req Request) error {
http.NotFound(w, req.Request)
return nil
}
+6
View File
@@ -0,0 +1,6 @@
package bunrouter
// Version is the current release version.
func Version() string {
return "1.0.23"
}