fix(go.sum): update ResolveSpec dependency to v1.0.87
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
trailingComma: all
|
||||
tabWidth: 2
|
||||
semi: false
|
||||
singleQuote: true
|
||||
proseWrap: always
|
||||
printWidth: 100
|
||||
+240
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,195 @@
|
||||
# Fast and flexible HTTP router for Go
|
||||
|
||||
[](https://github.com/uptrace/bunrouter/actions)
|
||||
[](https://pkg.go.dev/github.com/uptrace/bunrouter)
|
||||
[](https://bunrouter.uptrace.dev/)
|
||||
[](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
@@ -0,0 +1 @@
|
||||
module.exports = { extends: ["@commitlint/config-conventional"] };
|
||||
+86
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "bunrouter",
|
||||
"version": "1.0.23"
|
||||
}
|
||||
+150
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
|
||||
package bunrouter
|
||||
|
||||
// Version is the current release version.
|
||||
func Version() string {
|
||||
return "1.0.23"
|
||||
}
|
||||
Reference in New Issue
Block a user