Description

How to change the http request method?

for example: https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_method-or-x-http-method-override-header

A POST to /wp-json/wp/v2/posts/42?_method=DELETE would be translated to a DELETE to the wp/v2/posts/42 route.

How to reproduce

package api

import (
    "net/http"

    "github.com/gin-gonic/gin"

    "snowdream.tech/press-backend/pkg/i18n"
    ghttp "snowdream.tech/press-backend/pkg/net/http"
)

func loadMethodAPI(router *gin.Engine) {
    router.GET(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! GET"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.POST(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! POST"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.PUT(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! PUT"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.DELETE(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! DELETE"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.PATCH(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! PATCH"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.HEAD(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! HEAD"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

    router.OPTIONS(Namespace, func(c *gin.Context) {
        i18 := i18n.Default(c)

        data := gin.H{
            "message": i18.T(c, "Hello Snowdream Server! OPTIONS"),
        }

        ghttp.NegotiateResponse(c, http.StatusOK, ghttp.ResponseSuccessWithData(c, data))
    })

}

package middlewares

import (
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
    "snowdream.tech/press-backend/pkg/tools"
)

// Method Method
func Method() gin.HandlerFunc {
    tools.DebugPrintF("[INFO] Starting Middleware %s", "Method")

    return func(c *gin.Context) {
        if _method, ok := c.GetQuery("_method"); ok && strings.ToUpper(_method) != c.Request.Method {
            c.Request.Method = strings.ToUpper(_method)
            c.Redirect(http.StatusSeeOther, c.Request.URL.Path)
            c.Abort()
        } else if _header := c.GetHeader("X-HTTP-Method-Override"); _header != "" && strings.ToUpper(_header) != c.Request.Method {
            c.Request.Method = strings.ToUpper(_header)
            c.Redirect(http.StatusSeeOther, c.Request.URL.Path)
            c.Abort()
        } else {
            c.Next()
        }
    }
}

http://localhost:8080/wp/v2?_method=put

Expectations

change the http request method

Actual result

not work

Environment

  • go version:
  • gin version (or commit ref):
  • operating system: macOS 12.7.2 go env GO111MODULE='on' GOARCH='amd64' GOBIN='' GOCACHE='/Users/snowdream/Library/Caches/go-build' GOENV='/Users/snowdream/Library/Application Support/go/env' GOEXE='' GOEXPERIMENT='' GOFLAGS='' GOHOSTARCH='amd64' GOHOSTOS='darwin' GOINSECURE='' GOMODCACHE='/Users/snowdream/go/pkg/mod' GONOPROXY='' GONOSUMDB='' GOOS='darwin' GOPATH='/Users/snowdream/go' GOPRIVATE='' GOPROXY='https://goproxy.cn' GOROOT='/usr/local/go' GOSUMDB='sum.golang.org' GOTMPDIR='' GOTOOLCHAIN='auto' GOTOOLDIR='/usr/local/go/pkg/tool/darwin_amd64' GOVCS='' GOVERSION='go1.21.4' GCCGO='gccgo' GOAMD64='v1' AR='ar' CC='clang' CXX='clang++' CGO_ENABLED='1' GOMOD='/dev/null' GOWORK='' CGO_CFLAGS='-O2 -g' CGO_CPPFLAGS='' CGO_CXXFLAGS='-O2 -g' CGO_FFLAGS='-O2 -g' CGO_LDFLAGS='-O2 -g' PKG_CONFIG='pkg-config' GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/pn/sl_vbqyx74jf365f5cgdbyv40000gn/T/go-build1723272133=/tmp/go-build -gno-record-gcc-switches -fno-common'

Comment From: snowdream

    c.Request.Method = strings.ToUpper(_method)
            engine.HandleContext(c)
            c.Abort()

Comment From: RedCrazyGhost

When you use HTTP methods, consider the security aspects. For tighter security, some firewalls do not allow HTTP PUT or DELETE traffic.

f you're using a server that can't handle PUT or DELETE traffic, you should use the query or head argument logic in func(c *gin.Context).

The following code has been uploaded to the code repository

// HandleContext re-enters a context that has been rewritten.
// This can be done by setting c.Request.URL.Path to your new target.
// Disclaimer: You can loop yourself to deal with this, use wisely.
func (engine *Engine) HandleContext(c *Context) {
    oldIndexValue := c.index
    c.reset()
    engine.handleHTTPRequest(c)

    c.index = oldIndexValue
}
package main

import (
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"

)

func Method(r *gin.Engine) gin.HandlerFunc {
    return func(c *gin.Context) {
        if _method, ok := c.GetQuery("_method"); ok && strings.ToUpper(_method) != c.Request.Method {
            c.Request.Method = strings.ToUpper(_method)
            r.HandleContext(c)
            c.Abort()
        } else if _header := c.GetHeader("X-HTTP-Method-Override"); _header != "" && strings.ToUpper(_header) != c.Request.Method {
            c.Request.Method = strings.ToUpper(_header)
            r.HandleContext(c)
            c.Abort()
        }else{
            c.Next()
        }
    }
}


func main() {
    r := gin.Default()
    r.Use(Method(r))

    r.GET("/test", func(c *gin.Context) {
        c.JSON(http.StatusOK,gin.H{"message":"GET"})
    })

    r.POST("/test", func(c *gin.Context) {
        c.JSON(http.StatusOK,gin.H{"message":"POST"})
    })
    r.PUT("/test", func(c *gin.Context) {
        c.JSON(http.StatusOK,gin.H{"message":"PUT"})
    })

    if err := r.Run(":8080"); err != nil {
        panic(err)
    }
}