Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions application.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,22 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx := app.createContext(w, req)

var middlewares []HandlerFunc
var bestMatch *RouterGroup

// Find the longest matching group prefix
for _, group := range app.groups {
if ok := group.matchPath(ctx.Path); ok {
middlewares = append(middlewares, group.middlewares...)
if group.matchPath(ctx.Path) {
if bestMatch == nil || len(group.prefix) > len(bestMatch.prefix) {
bestMatch = group
}
}
}

// Use the best matching group's middlewares
if bestMatch != nil {
middlewares = bestMatch.getAllMiddlewares()
}

ctx.handlers = middlewares
app.router.handle(ctx)
}
Expand Down Expand Up @@ -665,7 +674,7 @@ func (app *Application) serveHTTP(ctx context.Context) error {
}

go func() {
<-ctx.Done() // 当上下文被取消时,停止服务器
<-ctx.Done() // When context is canceled, stop the server
server.Close()
}()

Expand All @@ -675,7 +684,7 @@ func (app *Application) serveHTTP(ctx context.Context) error {
logger.Info("Server started at http://%s", app.AddressForLog())
}

// 等待所有 goroutine 完成
// Wait for all goroutines to complete
return server.Serve(listener)
}

Expand All @@ -702,7 +711,7 @@ func (app *Application) serveHTTPS(ctx context.Context) error {
}

go func() {
<-ctx.Done() // 当上下文被取消时,停止服务器
<-ctx.Done() // When context is canceled, stop the server
server.Close()
}()

Expand Down
17 changes: 5 additions & 12 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,9 @@ func (ctx *Context) Context() context.Context {
// Next runs the next handler in the middleware stack
func (ctx *Context) Next() {
ctx.index++
s := len(ctx.handlers)
// for ; ctx.index < s; ctx.index ++ {
// ctx.handlers[ctx.index](ctx)
// }

if ctx.index >= s {
panic("Handler cannot call ctx.Next")
if ctx.index < len(ctx.handlers) {
ctx.handlers[ctx.index](ctx)
}

ctx.handlers[ctx.index](ctx)
}

// Query returns the query string parameter with the given name.
Expand Down Expand Up @@ -770,7 +763,7 @@ func (ctx *Context) BindJSON(obj interface{}) (err error) {
}

if ctx.Env().Get("DEBUG_ZOOX_REQUEST_BODY") != "" {
// refernece: golang复用http.request.body - https://zhuanlan.zhihu.com/p/47313038
// reference: golang reuse http.request.body - https://zhuanlan.zhihu.com/p/47313038
_, err = ctx.CloneBody()
if err != nil {
return fmt.Errorf("failed to read request body: %v", err)
Expand Down Expand Up @@ -803,7 +796,7 @@ func (ctx *Context) BindYAML(obj interface{}) (err error) {
}

if ctx.Debug().IsDebugMode() {
// refernece: golang复用http.request.body - https://zhuanlan.zhihu.com/p/47313038
// reference: golang reuse http.request.body - https://zhuanlan.zhihu.com/p/47313038
_, err = ctx.CloneBody()
if err != nil {
return fmt.Errorf("failed to read request body: %v", err)
Expand Down Expand Up @@ -1060,7 +1053,7 @@ func (ctx *Context) Proxy(target string, cfg ...*proxy.SingleHostConfig) {
// CloneBody clones the body of the request, should be used carefully.
func (ctx *Context) CloneBody() (body io.ReadCloser, err error) {
if ctx.bodyBytes == nil {
// refernece: golang复用http.request.body - https://zhuanlan.zhihu.com/p/47313038
// reference: golang reuse http.request.body - https://zhuanlan.zhihu.com/p/47313038
ctx.bodyBytes, err = ioutil.ReadAll(ctx.Request.Body)
if err != nil {
return nil, fmt.Errorf("failed to read request body: %v", err)
Expand Down
106 changes: 84 additions & 22 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ import (
"mime"
"net/http"
"path"
"strings"
"time"

"github.com/go-zoox/core-utils/regexp"
"github.com/go-zoox/core-utils/strings"
"github.com/go-zoox/fs"
"github.com/go-zoox/headers"
"github.com/go-zoox/proxy"
Expand Down Expand Up @@ -40,6 +39,8 @@ func newRouterGroup(app *Application, prefix string) *RouterGroup {
func (g *RouterGroup) Group(prefix string, cb ...GroupFunc) *RouterGroup {
newGroup := newRouterGroup(g.app, g.prefix+prefix)
newGroup.parent = g

// Simply append to groups list - no need for complex sorting
g.app.groups = append(g.app.groups, newGroup)

for _, fn := range cb {
Expand All @@ -49,30 +50,91 @@ func (g *RouterGroup) Group(prefix string, cb ...GroupFunc) *RouterGroup {
return newGroup
}

func (g *RouterGroup) matchPath(path string) (ok bool) {
// /v1 => /v1
// /v1/ => /v1
if ok := strings.HasPrefix(path, g.prefix); ok {
return ok
// matchPath handles both static and dynamic prefix matching
func (g *RouterGroup) matchPath(path string) bool {
// Empty prefix matches all paths
if g.prefix == "" || g.prefix == "/" {
return true
}

// If prefix has no dynamic parts, use simple prefix matching
if !strings.Contains(g.prefix, ":") && !strings.Contains(g.prefix, "{") && !strings.Contains(g.prefix, "*") {
return strings.HasPrefix(path, g.prefix)
}

// For dynamic prefixes, use simple pattern matching
return g.matchDynamicPrefix(path, g.prefix)
}

// matchDynamicPrefix handles dynamic path matching with a simplified approach
func (g *RouterGroup) matchDynamicPrefix(path, prefix string) bool {
pathParts := strings.Split(strings.Trim(path, "/"), "/")
prefixParts := strings.Split(strings.Trim(prefix, "/"), "/")

// If prefix has more parts than path, it cannot match
if len(prefixParts) > len(pathParts) {
return false
}

// Check each part
for i, prefixPart := range prefixParts {
pathPart := pathParts[i]

// Skip dynamic parameters
if strings.HasPrefix(prefixPart, ":") ||
(strings.HasPrefix(prefixPart, "{") && strings.HasSuffix(prefixPart, "}")) ||
strings.HasPrefix(prefixPart, "*") {
continue
}

// Static part must match exactly
if prefixPart != pathPart {
return false
}
}

// @TODO /v1/containers/123456/terminal => /v1/containers/:id
re := g.prefix
if strings.Contains(re, ":") {
re = strings.ReplaceAllFunc(re, ":\\w+", func(b []byte) []byte {
return []byte("\\w+")
})
} else if strings.Contains(re, "{") {
re = strings.ReplaceAllFunc(re, "{.*}", func(b []byte) []byte {
return []byte("\\w+")
})
return true
}

// getAllMiddlewares gets all middlewares (including parent)
func (g *RouterGroup) getAllMiddlewares() []HandlerFunc {
var middlewares []HandlerFunc

// Recursively collect parent middlewares
if g.parent != nil {
middlewares = append(middlewares, g.parent.getAllMiddlewares()...)
}

// Add current level middlewares
middlewares = append(middlewares, g.middlewares...)

return middlewares
}

// joinPath correctly joins URL paths
func (g *RouterGroup) joinPath(path string) string {
if g.prefix == "" {
return path
}

// Handle root path special case
if g.prefix == "/" && path == "/" {
return "/"
}

// Simple path joining
prefix := strings.TrimSuffix(g.prefix, "/")
path = strings.TrimPrefix(path, "/")

if path == "" {
return prefix
}

return regexp.Match(re, path)
return prefix + "/" + path
}

func (g *RouterGroup) addRoute(method string, path string, handler ...HandlerFunc) {
pathX := fs.JoinPath(g.prefix, path)
pathX := g.joinPath(path)
g.app.router.addRoute(method, pathX, handler...)
}

Expand Down Expand Up @@ -164,7 +226,7 @@ func (g *RouterGroup) Proxy(path, target string, options ...func(cfg *ProxyConfi
handler := WrapH(proxy.NewSingleHost(target, &cfg.SingleHostConfig))

g.Use(func(ctx *Context) {
if strings.StartsWith(ctx.Path, path) {
if strings.HasPrefix(ctx.Path, path) {
if cfg.OnRequestWithContext != nil {
if err := cfg.OnRequestWithContext(ctx); err != nil {
ctx.Logger.Errorf("proxy error: %s", err)
Expand Down Expand Up @@ -349,7 +411,7 @@ func (g *RouterGroup) Static(basePath string, rootDir string, options ...*Static
opts = options[0]
}

if !strings.StartsWith(basePath, "/") {
if !strings.HasPrefix(basePath, "/") {
rootDir = fs.JoinCurrentDir(basePath)
}

Expand All @@ -363,7 +425,7 @@ func (g *RouterGroup) Static(basePath string, rootDir string, options ...*Static
return
}

if !strings.StartsWith(ctx.Path, absolutePath) {
if !strings.HasPrefix(ctx.Path, absolutePath) {
ctx.Next()
return
}
Expand Down
Loading
Loading