Setting HTTP headers

138,263

Solution 1

Never mind, I figured it out - I used the Set() method on Header() (doh!)

My handler looks like this now:

func saveHandler(w http.ResponseWriter, r *http.Request) {
    // allow cross domain AJAX requests
    w.Header().Set("Access-Control-Allow-Origin", "*")
}

Maybe this will help someone as caffeine deprived as myself sometime :)

Solution 2

All of the above answers are wrong because they fail to handle the OPTIONS preflight request, the solution is to override the mux router's interface. See AngularJS $http get request failed with custom header (alllowed in CORS)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/save", saveHandler)
    http.Handle("/", &MyServer{r})
    http.ListenAndServe(":8080", nil);

}

type MyServer struct {
    r *mux.Router
}

func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    if origin := req.Header.Get("Origin"); origin != "" {
        rw.Header().Set("Access-Control-Allow-Origin", origin)
        rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        rw.Header().Set("Access-Control-Allow-Headers",
            "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
    }
    // Stop here if its Preflighted OPTIONS request
    if req.Method == "OPTIONS" {
        return
    }
    // Lets Gorilla work
    s.r.ServeHTTP(rw, req)
}

Solution 3

Do not use '*' for Origin, until You really need a completely public behavior.
As Wikipedia says:

"The value of "*" is special in that it does not allow requests to supply credentials, meaning HTTP authentication, client-side SSL certificates, nor does it allow cookies to be sent."

That means, you'll get a lot of errors, especially in Chrome when you'll try to implement for example a simple authentication.

Here is a corrected wrapper:

// Code has not been tested.
func addDefaultHeaders(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if origin := r.Header.Get("Origin"); origin != "" {
            w.Header().Set("Access-Control-Allow-Origin", origin)
        }
        w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token")
        w.Header().Set("Access-Control-Allow-Credentials", "true")
        fn(w, r)
    }
}

And don't forget to reply all these headers to the preflight OPTIONS request.

Solution 4

If you don't want to override your router (if you don't have your app configured in a way that supports this, or want to configure CORS on a route by route basis), add an OPTIONS handler to handle the pre flight request.

Ie, with Gorilla Mux your routes would look like:

accounts := router.Path("/accounts").Subrouter()
accounts.Methods("POST").Handler(AccountsCreate)
accounts.Methods("OPTIONS").Handler(AccountsCreatePreFlight)

Note above that in addition to our POST handler, we're defining a specific OPTIONS method handler.

And then to actual handle the OPTIONS preflight method, you could define AccountsCreatePreFlight like so:

// Check the origin is valid.
origin := r.Header.Get("Origin")
validOrigin, err := validateOrigin(origin)
if err != nil {
    return err
}

// If it is, allow CORS.
if validOrigin {
    w.Header().Set("Access-Control-Allow-Origin", origin)
    w.Header().Set("Access-Control-Allow-Methods", "POST")
    w.Header().Set("Access-Control-Allow-Headers",
        "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
}

What really made this all click for me (in addition to actually understanding how CORS works) is that the HTTP Method of a preflight request is different from the HTTP Method of the actual request. To initiate CORS, the browser sends a preflight request with HTTP Method OPTIONS, which you have to handle explicitly in your router, and then, if it receives the appropriate response "Access-Control-Allow-Origin": origin (or "*" for all) from your application, it initiates the actual request.

I also believe that you can only do "*" for standard types of requests (ie: GET), but for others you'll have to explicitly set the origin like I do above.

Solution 5

Set a proper golang middleware, so you can reuse on any endpoint.

Helper Type and Function

type Adapter func(http.Handler) http.Handler
// Adapt h with all specified adapters.
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
    for _, adapter := range adapters {
        h = adapter(h)
    }
    return h
}

Actual middleware

func EnableCORS() Adapter {
    return func(h http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

            if origin := r.Header.Get("Origin"); origin != "" {
                w.Header().Set("Access-Control-Allow-Origin", origin)
                w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
                w.Header().Set("Access-Control-Allow-Headers",
                    "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
            }
            // Stop here if its Preflighted OPTIONS request
            if r.Method == "OPTIONS" {
                return
            }
            h.ServeHTTP(w, r)
        })
    }
}

Endpoint

REMEBER! Middlewares get applyed on reverse order( ExpectGET() gets fires first)

mux.Handle("/watcher/{action}/{device}",Adapt(api.SerialHandler(mux),
    api.EnableCORS(),
    api.ExpectGET(),
))
Share:
138,263

Related videos on Youtube

Zen
Author by

Zen

Code is love, code is life. I'm pretty into functional programming, I try to take those ideas with me into my JS code whenever practical. My day to day is JavaScript and Elixir mostly right now. I also love music and make a lot of it, so I've been delving into C++/DSP and have made a few little VST plugins. I'd love to one day make a real time collaborative DAW (if you're working on this, hit me up!). In a previous life I used to write a lot of Ruby and PHP.

Updated on July 08, 2022

Comments

  • Zen
    Zen almost 2 years

    I'm trying to set a header in my Go web server. I'm using gorilla/mux and net/http packages.

    I'd like to set Access-Control-Allow-Origin: * to allow cross domain AJAX.

    Here's my Go code:

    func saveHandler(w http.ResponseWriter, r *http.Request) {
    // do some stuff with the request data
    }
    
    func main() {
        r := mux.NewRouter()
        r.HandleFunc("/save", saveHandler)
        http.Handle("/", r)
        http.ListenAndServe(":"+port, nil)
    }
    

    The net/http package has documentation describing sending http request headers as if it were a client - I'm not exactly sure how to set response headers?

  • Ray
    Ray over 11 years
    I've been having the same issue, it may also be helpful to add: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
  • Matt Bucci
    Matt Bucci almost 10 years
    I don't quite understand the usage of this wrapper, can you give an example of how you would wrap your http handle with this code? I'm using gorilla mux so my current usage is router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
  • Matt Bucci
    Matt Bucci almost 10 years
    I'm now wrapping my handle calls with addDefaultHeaders like router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) however as I have about 16 routes this isn't ideal is there any way to specify it as a wrapper at the http package or mux router layer
  • Dave C
    Dave C about 9 years
    "All of the above" … answers can be sorted in many ways so this phrase doesn't mean what you want it to.
  • orcaman
    orcaman over 8 years
    This won't work in case the AJAX client sets withCredentials:true (the "*" value is not allowed when credentials are sent, which is a common use case). You must set the origin to the requester (see Matt Bucci's answer below for how).
  • laike9m
    laike9m almost 8 years
    Simple CORS requests have no preflight, it all depends on what you're trying to serve.
  • Federico
    Federico about 5 years
    Don't forget Access-Control-Allow-Credentials": "true" for requests with httpOnly Cookies.