Establecer encabezados HTTP

165

Estoy tratando de establecer un encabezado en mi servidor web Go. Estoy usando gorilla/muxy net/httppaquetes.

Me gustaría configurar Access-Control-Allow-Origin: *para permitir el dominio cruzado AJAX.

Aquí está mi código Go:

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)
}

El net/httppaquete tiene documentación que describe el envío de encabezados de solicitud http como si fuera un cliente. ¿No estoy exactamente seguro de cómo configurar encabezados de respuesta?

zen
fuente

Respuestas:

227

No importa, lo descubrí, utilicé el Set()método en Header()(¡doh!)

Mi controlador se ve así ahora:

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

Tal vez esto ayude a alguien tan privado de cafeína como yo alguna vez :)

zen
fuente
2
He tenido el mismo problema, también puede ser útil agregar: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray
1
Esto no funcionará en caso de que el cliente AJAX establezca withCredentials:true(el valor "*" no está permitido cuando se envían credenciales, que es un caso de uso común). Debe establecer el origen del solicitante (consulte la respuesta de Matt Bucci a continuación para saber cómo).
orcaman
98

Todas las respuestas anteriores son incorrectas porque no pueden manejar la solicitud de verificación previa de OPTIONS, la solución es anular la interfaz del enrutador mux. Consulte AngularJS $ http solicitud fallida con encabezado personalizado (permitido en 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)
}
Matt Bucci
fuente
19
"Todo lo anterior" ... las respuestas se pueden ordenar de muchas maneras para que esta frase no signifique lo que usted quiere.
Dave C
Las solicitudes CORS simples no tienen verificación previa, todo depende de lo que intente servir.
laike9m
No olvide las Access-Control-Allow-Credentials": "true"solicitudes con httpOnly Cookies.
Federico
23

No use '*' para Origin, hasta que realmente necesite un comportamiento completamente público.
Como dice Wikipedia :

"El valor de" * "es especial porque no permite que las solicitudes proporcionen credenciales, es decir, autenticación HTTP, certificados SSL del lado del cliente, ni permite el envío de cookies".

Eso significa que obtendrá muchos errores, especialmente en Chrome cuando intente implementar, por ejemplo, una autenticación simple.

Aquí hay un contenedor corregido:

// 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)
    }
}

Y no olvide responder todos estos encabezados a la solicitud OPTIONS de verificación previa.

tacobot
fuente
1
No entiendo bien el uso de este contenedor, ¿puede dar un ejemplo de cómo envolvería su identificador http con este código? Estoy usando gorilla mux, así que mi uso actual es router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci
2
Ahora estoy envolviendo mis llamadas de manejador con addDefaultHeaders como, router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) sin embargo, como tengo alrededor de 16 rutas, esto no es ideal, ¿hay alguna manera de especificarlo como un envoltorio en el paquete http o la capa de enrutador mux
Matt Bucci
14

Establezca un middleware golang adecuado, para que pueda reutilizar en cualquier punto final.

Tipo de ayuda y función

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
}

Middleware real

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)
        })
    }
}

Punto final

¡RECUERDA! Los middlewares se aplican en orden inverso (ExpectGET () se activa primero)

mux.Handle("/watcher/{action}/{device}",Adapt(api.SerialHandler(mux),
    api.EnableCORS(),
    api.ExpectGET(),
))
CESCO
fuente
14

Si no desea anular su enrutador (si no tiene su aplicación configurada de una manera que lo admita, o si desea configurar CORS ruta por ruta), agregue un controlador de OPCIONES para manejar la solicitud previa al vuelo .

Es decir, con Gorilla Mux tus rutas se verían así:

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

Tenga en cuenta que además de nuestro controlador POST, estamos definiendo un controlador de método OPTIONS específico .

Y luego, para manejar el método de verificación previa OPTIONS, puede definir CuentasCreatePreFlight de la siguiente manera:

// 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")
}

Lo que realmente hizo que todo esto fuera un clic para mí (además de comprender realmente cómo funciona CORS) es que el Método HTTP de una solicitud de verificación previa es diferente del Método HTTP de la solicitud real. Para iniciar CORS, el navegador envía una solicitud de verificación previa con las OPCIONES del Método HTTP, que debe manejar explícitamente en su enrutador, y luego, si recibe la respuesta adecuada"Access-Control-Allow-Origin": origin (o "*" para todos) de su aplicación, inicia la aplicación real solicitud.

También creo que solo puede hacer "*" para los tipos estándar de solicitudes (es decir: GET), pero para otros tendrá que establecer explícitamente el origen como lo hice anteriormente.

Kyle Chadha
fuente
12

Creo envoltura para este caso:

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)
    }
}
obyknovenius
fuente
1

Tuve el mismo problema que el descrito anteriormente, las soluciones dadas anteriormente son correctas, la configuración que tengo es la siguiente 1) Angularjs para el Cliente 2) Marco de Beego para el servidor GO

Siga estos puntos 1) La configuración de CORS debe estar habilitada solo en el servidor GO 2) NO agregue ningún tipo de encabezados en angularJS excepto esto

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

En su servidor GO, agregue la configuración CORS antes de que la solicitud comience a procesarse para que la solicitud de verificación previa reciba un 200 OK luego de lo cual el método OPTIONS se convertirá en GET, POST, PUT o el tipo de solicitud que sea.

Prostil Hardi
fuente
-7

Sé que este es un giro diferente en la respuesta, pero ¿no es esto más una preocupación para un servidor web? Por ejemplo, nginx , podría ayudar.

El módulo ngx_http_headers_module permite agregar los campos de encabezado " Expires " y "Cache-Control", y campos arbitrarios, a un encabezado de respuesta

...

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

Agregar nginx frente a su servicio go en producción parece una buena idea. Proporciona muchas más funciones para autorizar, registrar y modificar solicitudes. Además, le da la capacidad de controlar quién tiene acceso a su servicio y no solo eso, sino que también puede especificar un comportamiento diferente para ubicaciones específicas en su aplicación, como se demostró anteriormente.

Podría seguir explicando por qué usar un servidor web con su api go, pero creo que ese es un tema para otra discusión.

shwoodard
fuente