Method spoofing in Go

My Go-based todo app marches on. Today I implemented functionality to delete a todo.

Before I move on to what I worked on, it just occurred to me that I should really consider naming within my app.

I currently have a struct called Todo. But a “todo” isn’t really a thing. It’s lazy naming. These are “tasks.” Tasks belong to a “todo list,” which could really be a called a “task list” but we decided not to go that way as a society. Anyway.

I wanted to be able to delete a todo task at a route like the one registered below:

r.HandleFunc("/tasks/{id}", handlers.Delete).Methods("DELETE")

The problem is that this is a boring server-rendered app with boring html forms. HTML forms only support two methods: GET and POST. I also want to reuse this same path for editing and updating the task, and I don’t want to imperatively suss out which action I’m supposed to take within the handler. After all, this isn’t Django: things should be better. I wanted to use a hidden input field to a form to force the request method like Laravel does.

<input type="hidden" name="_method" value="DELETE">

Two minutes of searching on Google didn’t immediately turn up a way to do this, and I was eager to learn about how global middleware might work in Go so I decided to implement it myself.

I found the following code in the Go For Web book I’ve been reading from O’Reilly:

func log(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
fmt.Println("Handler function called - " + name)
h(w, r)
}
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", log(hello))
server.ListenAndServe()
}

That was enough for me to get started: you could wrap the server. Unfortunately, the API for mux seems a little bit different, but I came up with something that seemed sensible enough:

func useHttpFormMethod(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
m := r.PostFormValue("_method")
if m != "" {
r.Method = m
}
}

next.ServeHTTP(w, r)
})
}

func main() {
r := mux.NewRouter()

r.HandleFunc("/", handlers.RedirectToIndex).Methods("GET")
r.HandleFunc("/todos", handlers.Index).Methods("GET")
r.HandleFunc("/todos", handlers.Store).Methods("POST")
r.HandleFunc("/todos/{id}", handlers.Delete).Methods("DELETE")

http.ListenAndServe(":3000", useHttpFormMethod(r))
}

I didn’t notice in Mux’s middlware documentation that you can call r.Use(middlewareFunc) to add middleware, but I think I’m going to leave it as-is for now so I can remember this when I’m using Go’s standard mux.

I was pretty proud of myself after implementing this, so I searched Google a little harder to see an example of someone doing something similar. I mean someone had to have done something like this, it’s a very popular pattern in the batteries-included framework world.

Sure enough, if I had looked for three minutes instead of two I would have found Alex Edwards’s blog post on HTTP Method Spoofing in Go. Alex came up with a solution a bit more elegant than mine:

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)
})
}

Alex’s Middleware validates that the server is receiving a valid HTTP method; mine does not. I’m not really sure what the X-HTTP-Method-Override header is about, so I should probably read his whole blog post at some point. I also think it might be nice to make the method uppercase before doing any comparisons, but maybe that’s too much magic.

I’m pretty pumped with my progress. More good things to come.

Leave a comment