Skip to content

Instantly share code, notes, and snippets.

@justinas
Last active November 29, 2023 11:41
Show Gist options
  • Save justinas/7059324 to your computer and use it in GitHub Desktop.
Save justinas/7059324 to your computer and use it in GitHub Desktop.
Go middleware samples for my blog post. http://justinas.org/writing-http-middleware-in-go/
package main
import (
"net/http"
)
type SingleHost struct {
handler http.Handler
allowedHost string
}
func NewSingleHost(handler http.Handler, allowedHost string) *SingleHost {
return &SingleHost{handler: handler, allowedHost: allowedHost}
}
func (s *SingleHost) ServeHTTP(w http.ResponseWriter, r *http.Request) {
host := r.Host
if host == s.allowedHost {
s.handler.ServeHTTP(w, r)
} else {
w.WriteHeader(403)
}
}
func myHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Success!"))
}
func main() {
single := NewSingleHost(http.HandlerFunc(myHandler), "example.com")
println("Listening on port 8080")
http.ListenAndServe(":8080", single)
}
package main
import (
"net/http"
)
func SingleHost(handler http.Handler, allowedHost string) http.Handler {
ourFunc := func(w http.ResponseWriter, r *http.Request) {
host := r.Host
if host == allowedHost {
handler.ServeHTTP(w, r)
} else {
w.WriteHeader(403)
}
}
return http.HandlerFunc(ourFunc)
}
func myHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Success!"))
}
func main() {
single := SingleHost(http.HandlerFunc(myHandler), "example.com")
println("Listening on port 8080")
http.ListenAndServe(":8080", single)
}
package main
import (
"net/http"
)
type AppendMiddleware struct {
handler http.Handler
}
func (a *AppendMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.handler.ServeHTTP(w, r)
w.Write([]byte("<!-- Middleware says hello! -->"))
}
func myHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Success!"))
}
func main() {
mid := &AppendMiddleware{http.HandlerFunc(myHandler)}
println("Listening on port 8080")
http.ListenAndServe(":8080", mid)
}
package main
import (
"net/http"
"net/http/httptest"
)
type ModifierMiddleware struct {
handler http.Handler
}
func (m *ModifierMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rec := httptest.NewRecorder()
// passing a ResponseRecorder instead of the original RW
m.handler.ServeHTTP(rec, r)
// after this finishes, we have the response recorded
// and can modify it before copying it to the original RW
// we copy the original headers first
for k, v := range rec.Header() {
w.Header()[k] = v
}
// and set an additional one
w.Header().Set("X-We-Modified-This", "Yup")
// only then the status code, as this call writes the headers as well
w.WriteHeader(418)
// The body hasn't been written (to the real RW) yet,
// so we can prepend some data.
data := []byte("Middleware says hello again. ")
// But the Content-Length might have been set already,
// we should modify it by adding the length
// of our own data.
// Ignoring the error is fine here:
// if Content-Length is empty or otherwise invalid,
// Atoi() will return zero,
// which is just what we'd want in that case.
clen, _ := strconv.Atoi(r.Header.Get("Content-Length"))
clen += len(data)
w.Header.Set("Content-Length", strconv.Itoa(clen))
// finally, write out our data
w.Write(data)
// then write out the original body
w.Write(rec.Body.Bytes())
}
func myHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Success!"))
}
func main() {
mid := &ModifierMiddleware{http.HandlerFunc(myHandler)}
println("Listening on port 8080")
http.ListenAndServe(":8080", mid)
}
@coldfire-x
Copy link

since you have modified the content, why there is no code updating 'content-length' ?

@unknwon
Copy link

unknwon commented Jan 3, 2014

Very good resources! I just made one lecture based on your blog about middleware, but that's in Chinese in case you might be interested in https://github.com/Unknwon/go-web-foundation/blob/master/lectures/lecture10/lecture10.md

@regorov
Copy link

regorov commented Jul 19, 2014

Hi justinas !
Great job.
Noob question. Do you see any chance to make alice and https://github.com/julienschmidt/httprouter working together without rewriting code? Issue I see, httprouter has additional in handler.
Thank you in advance

@hartzell
Copy link

There are two problems with 4_modifier.go.

  1. You should not ignore the error return values of the calls to w.Write. Ignoring them makes it much harder to track down the issues caused by problem #2.
  2. If the Handler that you are wrapping sets a Content-Length header and you write that header as-is to your new ResponseWriter and you make a change to the response's content that increases its length then Write will fail and return an error. If you do not check for that error (see #1 above) and just silently proceed you'll be left to scratch your head, wondering where you content went. See bradfitz's explanation in this thread in the golang-nuts forum.

@justinas
Copy link
Author

@hartzell, thanks so much. I think @pengfei-xue referred to the same issue, though I had trouble understanding it.

I have updated the gist (and the blog post) for #2, though I am not sure about #1. It would make sense to add it everywhere, instead of just that one example, as weird stuff might happen anywhere and in theory, every Write() could fail.

@andrewwatson
Copy link

What would be a good way for your modifier middleware to alter the HTTP Status Code of the response? I found some old discussion on the GoNuts list from 30 months ago but none of what they suggested seems to work anymore!

DOH! Nevermind. ResponseRecorder has it. I should have looked closer. Silly Me!

@Komosa
Copy link

Komosa commented Feb 8, 2015

@justinas,
I think, that you should use w instead of r while updating Content-Length (and read it from rec). I'm right?

@justinas
Copy link
Author

@Komosa, yes, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment