Skip to content

Instantly share code, notes, and snippets.

@justinas
Last active November 29, 2023 11:41
Show Gist options
  • Star 62 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • 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)
}
@troyk
Copy link

troyk commented Oct 21, 2013

Hi, came here from your blog and I'm thinking about writing my next app in go and really appreciate your post about middlewares. It would be really cool if you could share the rest of your stack.

@stevan
Copy link

stevan commented Oct 30, 2013

@justinas - Just read your blog as well, I recently pushed a simple "add on" to net/http that does allows middleware style programming, you can take a look here: https://github.com/stevan/httpapp

@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