Skip to content

Commit 56c8d44

Browse files
authored
Merge pull request #293 from devfeel/aicode
feat(group): add SetNotFoundHandle support for router groups
2 parents ab8c805 + 0abb9b7 commit 56c8d44

11 files changed

Lines changed: 380 additions & 41 deletions

File tree

config/config_yaml.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package config
22

33
import (
4-
"gopkg.in/yaml.v2"
4+
"gopkg.in/yaml.v3"
55
)
66

77
// UnmarshalYaml decodes the first document found within the in byte slice

dotweb.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,10 @@ func (app *DotWeb) initBindMiddleware() {
563563

564564
// bind group middlewares
565565
for _, g := range app.HttpServer.groups {
566-
xg := g.(*xGroup)
567-
if len(xg.middlewares) <= 0 {
566+
if len(g.middlewares) <= 0 {
568567
continue
569568
}
570-
for fullExpress, _ := range xg.allRouterExpress {
569+
for fullExpress, _ := range g.allRouterExpress {
571570
expresses := strings.Split(fullExpress, routerExpressSplit)
572571
if len(expresses) < 2 {
573572
continue
@@ -576,7 +575,7 @@ func (app *DotWeb) initBindMiddleware() {
576575
if node == nil {
577576
continue
578577
}
579-
node.groupMiddlewares = xg.middlewares
578+
node.groupMiddlewares = g.middlewares
580579
for _, m := range node.groupMiddlewares {
581580
if m.HasExclude() && m.ExistsExcludeRouter(node.fullPath) {
582581
app.Logger().Debug("DotWeb initBindMiddleware [group] "+fullExpress+" "+reflect.TypeOf(m).String()+" exclude", LogTarget_HttpServer)

example/group/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Group SetNotFoundHandle Example
2+
3+
This example demonstrates how to use `Group.SetNotFoundHandle` to set custom 404 handlers for router groups.
4+
5+
## Features
6+
7+
- **Group-level 404 handler**: Set custom 404 response for specific route groups
8+
- **Priority**: Group-level handler takes priority over app-level handler
9+
- **Flexible**: Different groups can have different 404 handlers
10+
11+
## Usage
12+
13+
```bash
14+
# Run the example
15+
go run main.go
16+
17+
# Test routes
18+
curl http://localhost:8080/ # Welcome page
19+
curl http://localhost:8080/api/users # API: Users list
20+
curl http://localhost:8080/api/health # API: Health check
21+
curl http://localhost:8080/api/unknown # API: 404 (group handler)
22+
curl http://localhost:8080/web/index # Web: Index page
23+
curl http://localhost:8080/web/unknown # Web: 404 (global handler)
24+
curl http://localhost:8080/unknown # Global: 404 (global handler)
25+
```
26+
27+
## Expected Responses
28+
29+
### API Group (custom 404)
30+
```bash
31+
$ curl http://localhost:8080/api/unknown
32+
{"code": 404, "message": "API 404 - Resource not found", "hint": "Check API documentation for available endpoints"}
33+
```
34+
35+
### Web Group (uses global 404)
36+
```bash
37+
$ curl http://localhost:8080/web/unknown
38+
{"code": 404, "message": "Global 404 - Page not found"}
39+
```
40+
41+
### Global 404
42+
```bash
43+
$ curl http://localhost:8080/unknown
44+
{"code": 404, "message": "Global 404 - Page not found"}
45+
```
46+
47+
## Code Explanation
48+
49+
```go
50+
// Set global 404 handler (fallback)
51+
app.SetNotFoundHandle(func(ctx dotweb.Context) error {
52+
return ctx.WriteString(`{"code": 404, "message": "Global 404"}`)
53+
})
54+
55+
// Create API group with custom 404 handler
56+
apiGroup := app.HttpServer.Group("/api")
57+
apiGroup.SetNotFoundHandle(func(ctx dotweb.Context) error {
58+
return ctx.WriteString(`{"code": 404, "message": "API 404"}`)
59+
})
60+
61+
// Web group uses global 404 (no SetNotFoundHandle)
62+
webGroup := app.HttpServer.Group("/web")
63+
```
64+
65+
## Use Cases
66+
67+
1. **API vs Web**: Return JSON for API 404s, HTML for Web 404s
68+
2. **Versioned APIs**: Different 404 messages for v1 vs v2 APIs
69+
3. **Multi-tenant**: Custom 404 per tenant group
70+
4. **Internationalization**: Different language 404 messages per group

example/group/group_test

10.4 MB
Binary file not shown.

example/group/main.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/devfeel/dotweb"
6+
)
7+
8+
func main() {
9+
// Create DotWeb app
10+
app := dotweb.New()
11+
12+
// Set global 404 handler
13+
app.SetNotFoundHandle(func(ctx dotweb.Context) {
14+
ctx.Response().Header().Set("Content-Type", "application/json")
15+
ctx.WriteString(`{"code": 404, "message": "Global 404 - Page not found"}`)
16+
})
17+
18+
// Create API group
19+
apiGroup := app.HttpServer.Group("/api")
20+
21+
// Set group-level 404 handler
22+
apiGroup.SetNotFoundHandle(func(ctx dotweb.Context) {
23+
ctx.Response().Header().Set("Content-Type", "application/json")
24+
ctx.WriteString(`{"code": 404, "message": "API 404 - Resource not found", "hint": "Check API documentation for available endpoints"}`)
25+
})
26+
27+
// Register API routes
28+
apiGroup.GET("/users", func(ctx dotweb.Context) error {
29+
return ctx.WriteString(`{"users": ["Alice", "Bob", "Charlie"]}`)
30+
})
31+
32+
apiGroup.GET("/health", func(ctx dotweb.Context) error {
33+
return ctx.WriteString(`{"status": "ok"}`)
34+
})
35+
36+
// Create Web group (no custom 404 handler, will use global)
37+
webGroup := app.HttpServer.Group("/web")
38+
39+
webGroup.GET("/index", func(ctx dotweb.Context) error {
40+
return ctx.WriteString("<h1>Welcome to Web</h1>")
41+
})
42+
43+
// Root route
44+
app.HttpServer.GET("/", func(ctx dotweb.Context) error {
45+
return ctx.WriteString("Welcome to DotWeb! Try:\n" +
46+
"- GET /api/users (exists)\n" +
47+
"- GET /api/unknown (API 404)\n" +
48+
"- GET /web/index (exists)\n" +
49+
"- GET /web/unknown (Global 404)\n" +
50+
"- GET /unknown (Global 404)")
51+
})
52+
53+
fmt.Println("Server starting on :8080...")
54+
fmt.Println("\nTest routes:")
55+
fmt.Println(" curl http://localhost:8080/ - Welcome page")
56+
fmt.Println(" curl http://localhost:8080/api/users - API: Users list")
57+
fmt.Println(" curl http://localhost:8080/api/health - API: Health check")
58+
fmt.Println(" curl http://localhost:8080/api/unknown - API: 404 (group handler)")
59+
fmt.Println(" curl http://localhost:8080/web/index - Web: Index page")
60+
fmt.Println(" curl http://localhost:8080/web/unknown - Web: 404 (global handler)")
61+
fmt.Println(" curl http://localhost:8080/unknown - Global: 404 (global handler)")
62+
63+
// Start server
64+
err := app.StartServer(8080)
65+
if err != nil {
66+
fmt.Println("Server error:", err)
67+
}
68+
}

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ go 1.21
44

55
require (
66
github.com/garyburd/redigo v1.6.0
7-
golang.org/x/net v0.23.0
8-
gopkg.in/yaml.v2 v2.2.2
7+
golang.org/x/net v0.33.0
8+
gopkg.in/yaml.v3 v3.0.1
99
)
10-
11-
require gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,8 @@
11
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
22
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
3-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4-
golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0=
5-
golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
6-
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
7-
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
8-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
9-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
3+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
4+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
105
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
116
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12-
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
13-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
147
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
158
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

group.go

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,43 @@ package dotweb
22

33
import "reflect"
44

5-
type (
6-
Group interface {
7-
Use(m ...Middleware) Group
8-
Group(prefix string, m ...Middleware) Group
9-
DELETE(path string, h HttpHandle) RouterNode
10-
GET(path string, h HttpHandle) RouterNode
11-
HEAD(path string, h HttpHandle) RouterNode
12-
OPTIONS(path string, h HttpHandle) RouterNode
13-
PATCH(path string, h HttpHandle) RouterNode
14-
POST(path string, h HttpHandle) RouterNode
15-
PUT(path string, h HttpHandle) RouterNode
16-
ServerFile(path string, fileroot string) RouterNode
17-
RegisterRoute(method, path string, h HttpHandle) RouterNode
18-
}
19-
xGroup struct {
20-
prefix string
21-
middlewares []Middleware
22-
allRouterExpress map[string]struct{}
23-
server *HttpServer
24-
}
25-
)
5+
// Group is the interface that wraps the group router methods.
6+
// A Group allows you to create routes with a common prefix and middleware chain.
7+
type Group interface {
8+
// Use registers middleware(s) to the group.
9+
Use(m ...Middleware) Group
10+
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
11+
Group(prefix string, m ...Middleware) Group
12+
// DELETE registers a new DELETE route with the given path and handler.
13+
DELETE(path string, h HttpHandle) RouterNode
14+
// GET registers a new GET route with the given path and handler.
15+
GET(path string, h HttpHandle) RouterNode
16+
// HEAD registers a new HEAD route with the given path and handler.
17+
HEAD(path string, h HttpHandle) RouterNode
18+
// OPTIONS registers a new OPTIONS route with the given path and handler.
19+
OPTIONS(path string, h HttpHandle) RouterNode
20+
// PATCH registers a new PATCH route with the given path and handler.
21+
PATCH(path string, h HttpHandle) RouterNode
22+
// POST registers a new POST route with the given path and handler.
23+
POST(path string, h HttpHandle) RouterNode
24+
// PUT registers a new PUT route with the given path and handler.
25+
PUT(path string, h HttpHandle) RouterNode
26+
// ServerFile registers a file server route with the given path and file root.
27+
ServerFile(path string, fileroot string) RouterNode
28+
// RegisterRoute registers a new route with the given HTTP method, path and handler.
29+
RegisterRoute(method, path string, h HttpHandle) RouterNode
30+
// SetNotFoundHandle sets a custom 404 handler for this group.
31+
SetNotFoundHandle(handler StandardHandle) Group
32+
}
33+
34+
// xGroup is the implementation of Group interface.
35+
type xGroup struct {
36+
prefix string
37+
middlewares []Middleware
38+
allRouterExpress map[string]struct{}
39+
server *HttpServer
40+
notFoundHandler StandardHandle
41+
}
2642

2743
func NewGroup(prefix string, server *HttpServer) Group {
2844
g := &xGroup{prefix: prefix, server: server, allRouterExpress: make(map[string]struct{})}
@@ -119,3 +135,14 @@ func (g *xGroup) add(method, path string, handler HttpHandle) RouterNode {
119135
node.Node().groupMiddlewares = g.middlewares
120136
return node
121137
}
138+
139+
// SetNotFoundHandle sets a custom 404 handler for this group.
140+
// This handler takes priority over the app-level NotFoundHandler.
141+
// If a request path starts with the group's prefix but no route matches,
142+
// this handler will be called instead of the global NotFoundHandler.
143+
// SetNotFoundHandle sets custom 404 handler for this group.
144+
// This handler takes priority over the app-level NotFoundHandler.
145+
func (g *xGroup) SetNotFoundHandle(handler StandardHandle) Group {
146+
g.notFoundHandler = handler
147+
return g
148+
}

0 commit comments

Comments
 (0)