Setting HTTP headers

HttpGoCorsHttp Headers

Http Problem Overview


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?

Http Solutions


Solution 1 - Http

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 - Http

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 https://stackoverflow.com/questions/22452804/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 - Http

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 - Http

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 - Http

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(),
))

Solution 6 - Http

I create wrapper for this case:

func addDefaultHeaders(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		fn(w, r)
	}
}

Solution 7 - Http

I had the same issue as described above the solutions given above are correct, the set up I have is as follows

  1. Angularjs for the Client
  2. Beego framework for GO server

Please following these points

  1. CORS settings must be enabled only on GO server

  2. Do NOT add any type of headers in angularJS except for this

    .config(['$httpProvider', function($httpProvider) { $httpProvider.defaults.useXDomain = true; delete $httpProvider.defaults.headers.common['X-Requested-With']; }])

In you GO server add the CORS settings before the request starts to get processed so that the preflight request receives a 200 OK after which the the OPTIONS method will get converted to GET,POST,PUT or what ever is your request type.

Solution 8 - Http

I know this is a different twist on the answer, but isn't this more of a concern for a web server? For example, nginx, could help.

> The ngx_http_headers_module module allows adding the “Expires” and “Cache-Control” header fields, and arbitrary fields, to a response header

...

location ~ ^<REGXP MATCHING CORS ROUTES> {
    add_header Access-Control-Allow-Methods POST
    ...
}
...

Adding nginx in front of your go service in production seems wise. It provides a lot more feature for authorizing, logging,and modifying requests. Also, it gives the ability to control who has access to your service and not only that but one can specify different behavior for specific locations in your app, as demonstrated above.

I could go on about why to use a web server with your go api, but I think that's a topic for another discussion.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZenView Question on Stackoverflow
Solution 1 - HttpZenView Answer on Stackoverflow
Solution 2 - HttpMatt BucciView Answer on Stackoverflow
Solution 3 - HttptacobotView Answer on Stackoverflow
Solution 4 - HttpKyle ChadhaView Answer on Stackoverflow
Solution 5 - HttpCESCOView Answer on Stackoverflow
Solution 6 - HttpobyknoveniusView Answer on Stackoverflow
Solution 7 - HttpProstil HardiView Answer on Stackoverflow
Solution 8 - HttpshwoodardView Answer on Stackoverflow