CORS Boilerplate in Go
Edit: Since this writing, rs/cors has been identified as the easiest and best way to do CORS in Go.
/*
* Boilerplate for handling CORS preflighted requests.
* https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
*/
package main
import (
"fmt"
"log"
"net/http"
"strings"
)
// Define length of time results of preflight
// can be cached
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Max-Age
var MaxAge = "60"
// Define all allowed methods here.
var AllowedMethods = map[string]bool{
"GET": true,
"POST": true,
}
// Define all allowed headers here.
var AllowedHeaders = map[string]bool{
"XPING": true,
}
// Define all allowed origins here.
// Can be wildcard ("*") or exactly ONE URI (e.g. "www.nhatqbui.com")
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
var AllowedOrigins = "*"
func getKeys(m map[string]bool) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// Example method handling preflight requests and normal requests.
func handleAjaxHello(w http.ResponseWriter, r *http.Request) {
if r.Method == "OPTIONS" {
// Handle pre-flight here.
// Note: We begin by manually checking the headers of the preflight.
// We can actually respond with the information communicated
// in the response headers and the browser should handle
// whether the subsequent is carried out. Here, we are being
// very explicit and checking the headers ourselves.
// Pre-flight will indicate the method of the actual request.
// Check that the method is a member of the set of allowed methods.
reqMethod, ok := r.Header["Access-Control-Request-Method"]
if !ok {
// Handle unspecified methods here
// In this example, we don't do anything and return.
return
}
hasMethod := false
for _, method := range reqMethod {
if AllowedMethods[method] {
hasMethod = true
break
}
}
if !hasMethod {
// Handle incorrect methods here
// In this example, we don't do anything and return.
return
}
// Check that subsequent request will have appropriate headers.
// This one is more nuanced. You should consider:
// - should the request contain ALL allowed headers?
// - or should the request headers be allowed headers
// (but not necessarily containing all allowed headers)
// Here, we just check that the request headers are allowed headers.
// Whether it has-all-necessary-headers is not checked.
reqHeaders, ok := r.Header["Access-Control-Request-Headers"]
if !ok {
// Handle unspecified methods here.
// In this example, we don't do anything and return.
return
}
for _, headers := range reqHeaders {
for _, header := range strings.Split(headers, ",") {
if !AllowedHeaders[header] {
// Handle incorrect headers here
// In this example, we don't do anything and return.
return
}
}
}
// We arrived here, the preflight has passed our checks
// And the subsequent request will be valid.
w.Header().Set("Access-Control-Allow-Origin", AllowedOrigins)
w.Header().Set("Access-Control-Allow-Methods", strings.Join(getKeys(AllowedMethods), ","))
w.Header().Set("Access-Control-Allow-Headers", strings.Join(getKeys(AllowedHeaders), ","))
w.Header().Set("Access-Control-Max-Age", MaxAge)
} else if r.Method == "POST" {
var jsonStr = []byte(fmt.Sprintf(`{"data":"PONG"}`))
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Write(jsonStr)
}
}
func main() {
http.HandleFunc("/ajax/hello", handleAjaxHello)
err := http.ListenAndServeTLS(":8080", "./certs/testing.crt", "./certs/testing.key", nil)
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}