Golang中如何自定义405 Method Not Allowed的JSON响应

Golang中如何自定义405 Method Not Allowed的JSON响应 你好,

正如此处所述,Go 1.22 的 http.NewServeMux 附带了一个默认的 405 Method Not Allowed 纯文本响应,但我希望覆盖此行为,改为返回自定义的 JSON 响应。是否可以不编写中间件来实现?

谢谢

mux := http.NewServeMux()
mux.HandleFunc("GET /api/info", handler.Info)
4 回复

甚至不确定该怎么做。

更多关于Golang中如何自定义405 Method Not Allowed的JSON响应的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


也许你可以将所有无效路径都交给一个特定的处理器来处理,在那里你可以自定义你的消息。

简而言之:我认为如果不使用中间件或类似的东西,这是不可能实现的。以下是相关的标准库代码:

https://cs.opensource.google/go/go/+/master:src/net/http/server.go;l=2553

看起来他们在严格执行 HTTP 语义。换句话说:返回 JSON 意味着该方法是受支持的。基于这一点,我认为你可以这样做:

mux := http.NewServeMux()
// 处理 GET 请求
mux.HandleFunc("GET /api/info", handler.Info)
// 向客户端发送友好的 JSON 而不是 405 错误
mux.HandleFunc("/api/info", handler.InfoNotSupported)

这并不完全理想,但代码易于阅读和理解其功能;并且避免了使用中间件。免责声明:我尚未使用 Go 1.22 的路由增强功能,因此不能 100% 确定这将如何表现。

在Go 1.22中,可以通过实现自定义的http.Handler来覆盖405响应,无需中间件。以下是示例代码:

package main

import (
    "encoding/json"
    "net/http"
)

type CustomMux struct {
    *http.ServeMux
}

func (m *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 先尝试标准路由匹配
    handler, pattern := m.Handler(r)
    if pattern == "" {
        // 没有匹配的路由,返回404
        http.NotFound(w, r)
        return
    }
    
    // 检查方法是否允许
    if handler != nil {
        // 这里需要检查方法是否匹配
        // 由于标准库不直接暴露方法检查,我们可以通过尝试调用handler来判断
        // 但更简单的方式是使用自定义路由逻辑
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusMethodNotAllowed)
        json.NewEncoder(w).Encode(map[string]string{
            "error": "Method Not Allowed",
            "message": "The requested method is not supported for this endpoint",
        })
        return
    }
    
    handler.ServeHTTP(w, r)
}

func main() {
    mux := &CustomMux{ServeMux: http.NewServeMux()}
    mux.HandleFunc("GET /api/info", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{
            "message": "Info endpoint",
        })
    })
    
    http.ListenAndServe(":8080", mux)
}

更精确的实现需要检查具体的方法:

package main

import (
    "encoding/json"
    "net/http"
    "strings"
)

type routeInfo struct {
    pattern string
    methods []string
    handler http.Handler
}

type JSONMethodMux struct {
    routes []routeInfo
}

func (m *JSONMethodMux) Handle(method, pattern string, handler http.Handler) {
    m.routes = append(m.routes, routeInfo{
        pattern: pattern,
        methods: []string{method},
        handler: handler,
    })
}

func (m *JSONMethodMux) HandleFunc(method, pattern string, handler func(http.ResponseWriter, *http.Request)) {
    m.Handle(method, pattern, http.HandlerFunc(handler))
}

func (m *JSONMethodMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    var allowedMethods []string
    var matchedHandler http.Handler
    
    for _, route := range m.routes {
        if route.pattern == r.URL.Path || strings.HasSuffix(route.pattern, "/") && strings.HasPrefix(r.URL.Path, route.pattern) {
            for _, method := range route.methods {
                if method == r.Method {
                    matchedHandler = route.handler
                    break
                }
                allowedMethods = append(allowedMethods, method)
            }
        }
    }
    
    if matchedHandler != nil {
        matchedHandler.ServeHTTP(w, r)
        return
    }
    
    if len(allowedMethods) > 0 {
        w.Header().Set("Content-Type", "application/json")
        w.Header().Set("Allow", strings.Join(allowedMethods, ", "))
        w.WriteHeader(http.StatusMethodNotAllowed)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "error": "Method Not Allowed",
            "allowed_methods": allowedMethods,
            "path": r.URL.Path,
        })
        return
    }
    
    http.NotFound(w, r)
}

func main() {
    mux := &JSONMethodMux{}
    
    mux.HandleFunc("GET", "/api/info", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{
            "message": "Info endpoint",
        })
    })
    
    mux.HandleFunc("POST", "/api/create", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{
            "message": "Create endpoint",
        })
    })
    
    http.ListenAndServe(":8080", mux)
}

第一个示例展示了包装标准ServeMux的基本方法,第二个示例提供了更完整的自定义路由实现,能准确返回允许的方法列表。

回到顶部