HTTP Method Spoofing in Go
As a web developer you probably already know that HTML forms only support the GET
and POST
HTTP methods.
If you want to send a PUT
, PATCH
or DELETE
request from your web application, you need to either send a XMLHttpRequest
from JavaScript (where they are supported by most major browsers) or implement a workaround in your server-side application code to support 'spoofed' or 'overridden' HTTP methods.
The de-facto workaround — which you might be familiar with if you've used frameworks like Ruby on Rails, Laravel or Express — is to include a hidden _method
input in your form containing the spoofed HTTP method. A bit like this:
<form method="POST" action="/">
<input type="hidden" name="_method" value="PUT">
<button type="submit">Submit</button>
</form>
Another common workaround is to send a spoofed HTTP method in a X-HTTP-Method-Override
header.
So how can we support these things in a Go application?
MethodOverride Middleware
Intercepting and dealing with spoofed HTTP methods is the perfect task for some custom middleware. We want the middleware to:
- Intercept
POST
requests before they reach any application handlers. - Check for a spoofed HTTP method, either in a
_method
parameter of the request body or aX-HTTP-Method-Override
header. - If a spoofed method exists — and is equal to
"PUT"
,"PATCH"
or"DELETE"
— the currenthttp.Request.Method
value should be updated accordingly.
It's pretty quick to implement:
package main
import (
"net/http"
)
func MethodOverride(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only act on POST requests.
if r.Method == "POST" {
// Look in the request body and headers for a spoofed method.
// Prefer the value in the request body if they conflict.
method := r.PostFormValue("_method")
if method == "" {
method = r.Header.Get("X-HTTP-Method-Override")
}
// Check that the spoofed method is a valid HTTP method and
// update the request object accordingly.
if method == "PUT" || method == "PATCH" || method == "DELETE" {
r.Method = method
}
}
// Call the next handler in the chain.
next.ServeHTTP(w, r)
})
}
You can then use the middleware in your application like so:
package main
import (
"html/template"
"io"
"log"
"net/http"
)
const form = `
<!DOCTYPE HTML>
<html>
<body>
<form method="POST" action="/">
<input type="hidden" name="_method" value="PUT">
<label>Example field</label>
<input type="text" name="example">
<button type="submit">Submit</button>
</form>
</body>
</html>
`
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", formHandler)
// Wrap the servemux with the MethodOverride middleware.
err := http.ListenAndServe(":4000", MethodOverride(mux))
log.Print(err)
}
func formHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
t, err := template.New("form").Parse(form)
if err != nil {
http.Error(w, err.Error(), 500)
}
t.Execute(w, nil)
case "PUT":
io.WriteString(w, "This is a PUT request")
default:
http.Error(w, http.StatusText(405), 405)
}
}