nytimes / gziphandler

Go middleware to gzip HTTP responses
https://godoc.org/github.com/NYTimes/gziphandler
Apache License 2.0
868 stars 129 forks source link

http: multiple response.WriteHeader calls #18

Closed wayneashleyberry closed 7 years ago

wayneashleyberry commented 8 years ago

How would one use the gzip handler with non-200 responses? The following example seems like it should work but logs http: multiple response.WriteHeader calls and the response isn't compressed.

package main

import (
    "fmt"
    "net/http"
    "os"

    "github.com/gorilla/mux"
    "github.com/nytimes/gziphandler"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", homeHandler)
    r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
    http.Handle("/", gziphandler.GzipHandler(r))
    http.ListenAndServe(":"+os.Getenv("PORT"), r)
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!\n")
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusNotFound)
    fmt.Fprintf(w, "404 - Not Found!\n")
}

This might be related to #5 but I'm mentioning it because I thought #16 might have fixed it. This also seems to be related to https://github.com/gorilla/handlers/issues/83. Although this handler just logs the error but still returns the response successfully (although uncompressed).

Here's some debug information if this helps:

$GOPATH/src/github.com/nytimes/gziphandler master
❯ git rev-parse HEAD
44668d75e46f05932cf7c1c7a375d0765b324a0b
❯ go version
go version go1.7.1 darwin/amd64
tmthrgd commented 7 years ago

Caveat emptor: I have nothing to do with this library. That being said, the following example works perfectly fine.

The first bug in the above example is that you're not actually using the gzip handler because you are registering it with http.DefaultServeMux but then explicitly passing a handler into http.ListenAndServe (which is very good practice IMO).

The second bug in the above example is that the notFoundHandler doesn't set the Content-Type before it writes the error message. \<speculation> This leaves it too late for the GzipHandler to set the Content-Type header but the http.Server does it's own sniffing and infers the incorrect mime-type application/x-gzip. \</speculation> The built-in http.Error method does set the Content-Type header before it calls WriteHeader and thus avoids this problem.

Working example:

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/NYTimes/gziphandler"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", homeHandler)
    r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
    http.ListenAndServe(":3333", gziphandler.GzipHandler(r))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!\n")
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
    http.Error(w, "404 - Not Found!", http.StatusNotFound)
}
MarufSaidov6 commented 6 years ago

hihihi