fix(reverseproxy): align forwarding and tunnel semantics

This commit is contained in:
wjqserver 2026-04-02 03:18:49 +08:00
parent c019f24e99
commit ed44c592d3
6 changed files with 864 additions and 26 deletions

View file

@ -475,21 +475,12 @@ func PutTempSkippedNodes(skippedNodes *[]skippedNode) {
func MethodNotAllowed() HandlerFunc {
return func(c *Context) {
httpMethod := c.Request.Method
requestPath := c.Request.URL.Path
requestPath := routeLookupPath(c.Request)
engine := c.engine
// 是否是OPTIONS方式
if httpMethod == http.MethodOptions {
// 如果是 OPTIONS 请求,尝试查找所有允许的方法
allowedMethods := []string{}
for _, treeIter := range engine.methodTrees {
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
tempSkippedNodes := GetTempSkippedNodes()
value := treeIter.root.getValue(requestPath, nil, tempSkippedNodes, false)
PutTempSkippedNodes(tempSkippedNodes)
if value.handlers != nil {
allowedMethods = append(allowedMethods, treeIter.method)
}
}
allowedMethods := engine.allowedMethodsForPath(requestPath)
if len(allowedMethods) > 0 {
// 如果找到了允许的方法,返回 200 OK 并设置 Allow 头部
c.Writer.Header().Set("Allow", strings.Join(allowedMethods, ", "))
@ -705,7 +696,7 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// 这是路由查找和执行的核心逻辑
func (engine *Engine) handleRequest(c *Context) {
httpMethod := c.Request.Method
requestPath := c.Request.URL.Path
requestPath := routeLookupPath(c.Request)
// 查找对应的路由树的根节点
rootNode := engine.methodTrees.get(httpMethod) // 这里获取到的 rootNode 已经是 *node 类型
@ -725,7 +716,7 @@ func (engine *Engine) handleRequest(c *Context) {
}
// 如果没有找到处理函数,检查是否需要重定向(尾部斜杠或大小写修复)
if httpMethod != http.MethodConnect && requestPath != "/" { // CONNECT 方法和根路径不进行重定向
if httpMethod != http.MethodConnect && requestPath != "/" && !isGeneralOptionsRequest(c.Request) { // CONNECT 方法、服务器级 OPTIONS 和根路径不进行重定向
if value.tsr && engine.RedirectTrailingSlash {
// 尾部斜杠重定向:/foo/ -> /foo 或 /foo -> /foo/
redirectPath := requestPath
@ -782,6 +773,41 @@ func (engine *Engine) handleRequest(c *Context) {
//c.Writer.Flush() // 确保所有缓冲的响应数据被发送
}
func routeLookupPath(req *http.Request) string {
if req == nil {
return ""
}
if req.Method == http.MethodConnect && req.RequestURI != "" && req.RequestURI != "*" && !strings.HasPrefix(req.RequestURI, "/") && !strings.Contains(req.RequestURI, "://") {
return "/" + req.RequestURI
}
if isGeneralOptionsRequest(req) {
return ""
}
if req.URL == nil {
return ""
}
return req.URL.Path
}
func isGeneralOptionsRequest(req *http.Request) bool {
return req != nil && req.Method == http.MethodOptions && req.RequestURI == "*"
}
func (engine *Engine) allowedMethodsForPath(requestPath string) []string {
allowedMethods := make([]string, 0, len(engine.methodTrees))
for _, treeIter := range engine.methodTrees {
// 注意这里 treeIter.root 才是正确的,因为 treeIter 是 methodTree 类型
tempSkippedNodes := GetTempSkippedNodes()
value := treeIter.root.getValue(requestPath, nil, tempSkippedNodes, false)
PutTempSkippedNodes(tempSkippedNodes)
if value.handlers != nil {
allowedMethods = append(allowedMethods, treeIter.method)
}
}
return allowedMethods
}
// Context 返回 Engine 的根上下文, 该上下文在服务器优雅关闭时会被取消.
// 它可以用于在长连接 (如 SSE) 中监听关闭信号.
func (engine *Engine) Context() context.Context {