Définition des en-têtes HTTP

165

J'essaie de définir un en-tête dans mon serveur Web Go. J'utilise gorilla/muxet des net/httppackages.

Je voudrais configurer Access-Control-Allow-Origin: *pour autoriser le cross domain AJAX.

Voici mon code 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)
}

Le net/httppackage contient une documentation décrivant l'envoi des en-têtes de demande http comme s'il s'agissait d'un client - je ne sais pas exactement comment définir les en-têtes de réponse?

Zen
la source

Réponses:

227

Qu'à cela ne tienne, je l'ai compris - j'ai utilisé la Set()méthode sur Header()(doh!)

Mon gestionnaire ressemble à ceci maintenant:

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

Peut-être que cela aidera quelqu'un d'aussi privé de caféine que moi un jour :)

Zen
la source
2
J'ai eu le même problème, il peut également être utile d'ajouter: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray
1
Cela ne fonctionnera pas dans le cas où les ensembles de clients AJAX withCredentials:true(la valeur "*" n'est pas autorisée lorsque les informations d'identification sont envoyées, ce qui est un cas d'utilisation courant). Vous devez définir l'origine sur le demandeur (voir la réponse de Matt Bucci ci-dessous pour savoir comment).
orcaman
98

Toutes les réponses ci-dessus sont fausses car elles ne parviennent pas à gérer la demande de contrôle en amont OPTIONS, la solution est de remplacer l'interface du routeur multiplexeur. Voir 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)
}
Matt Bucci
la source
19
«Tout ce qui précède»… les réponses peuvent être triées de plusieurs façons, donc cette phrase ne signifie pas ce que vous voulez.
Dave C
Les requêtes CORS simples n'ont pas de contrôle en amont, tout dépend de ce que vous essayez de servir.
laike9m
N'oubliez pas les Access-Control-Allow-Credentials": "true"demandes avec httpOnly Cookies.
Federico
23

N'utilisez pas '*' pour Origin, jusqu'à ce que vous ayez vraiment besoin d'un comportement complètement public.
Comme le dit Wikipedia :

"La valeur de" * "est spéciale en ce sens qu'elle n'autorise pas les demandes de fourniture d'informations d'identification, c'est-à-dire l'authentification HTTP, les certificats SSL côté client, et n'autorise pas l'envoi de cookies."

Cela signifie que vous obtiendrez beaucoup d'erreurs, en particulier dans Chrome lorsque vous tenterez de mettre en œuvre par exemple une authentification simple.

Voici un wrapper corrigé:

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

Et n'oubliez pas de répondre à tous ces en-têtes à la requête OPTIONS de contrôle en amont.

tacobot
la source
1
Je ne comprends pas tout à fait l'utilisation de ce wrapper, pouvez-vous donner un exemple de la façon dont vous envelopperiez votre handle http avec ce code? J'utilise gorilla mux donc mon utilisation actuelle est router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci
2
J'emballe maintenant mes appels de poignée avec addDefaultHeaders comme router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) cependant, comme j'ai environ 16 routes, ce n'est pas idéal, y a-t-il un moyen de le spécifier comme wrapper au package http ou à la couche de routeur mux
Matt Bucci
14

Définissez un middleware golang approprié pour pouvoir le réutiliser sur n'importe quel point de terminaison.

Type et fonction d'assistance

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
}

Intergiciel réel

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

Point final

RAPPELEZ-VOUS! Les middlewares sont appliqués dans l'ordre inverse (ExpectGET () obtient les incendies en premier)

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

Si vous ne souhaitez pas remplacer votre routeur (si votre application n'est pas configurée d'une manière qui prend en charge cela, ou si vous souhaitez configurer CORS route par route), ajoutez un gestionnaire OPTIONS pour gérer la demande de pré-vol .

C'est-à-dire qu'avec Gorilla Mux, vos itinéraires ressembleraient à:

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

Notez ci-dessus qu'en plus de notre gestionnaire POST, nous définissons un gestionnaire de méthode OPTIONS spécifique .

Et puis pour gérer réellement la méthode de contrôle en amont OPTIONS, vous pouvez définir AccountsCreatePreFlight comme suit:

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

Ce qui a vraiment rendu tout cela cliqué pour moi (en plus de comprendre comment fonctionne CORS), c'est que la méthode HTTP d'une demande de contrôle en amont est différente de la méthode HTTP de la demande réelle. Pour lancer CORS, le navigateur envoie une requête de contrôle en amont avec OPTIONS de méthode HTTP, que vous devez gérer explicitement dans votre routeur, puis, s'il reçoit la réponse appropriée "Access-Control-Allow-Origin": origin(ou "*" pour tous) de votre application, il lance le demande.

Je crois aussi que vous ne pouvez faire "*" que pour les types de requêtes standard (par exemple: GET), mais pour d'autres, vous devrez définir explicitement l'origine comme je le fais ci-dessus.

Kyle Chadha
la source
12

Je crée un wrapper pour ce cas:

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
la source
1

J'ai eu le même problème que celui décrit ci-dessus, les solutions données ci-dessus sont correctes, la configuration que j'ai est la suivante 1) Angularjs pour le client 2) Framework Beego pour le serveur GO

Veuillez suivre ces points 1) Les paramètres CORS doivent être activés uniquement sur le serveur GO 2) N'ajoutez AUCUN type d'en-tête dans angularJS sauf pour cela

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

Dans votre serveur GO, ajoutez les paramètres CORS avant que la demande ne commence à être traitée afin que la demande de contrôle en amont reçoive un 200 OK, après quoi la méthode OPTIONS sera convertie en GET, POST, PUT ou quel que soit votre type de demande.

Prostil Hardi
la source
-7

Je sais que la réponse est différente, mais n'est-ce pas plus une préoccupation pour un serveur Web? Par exemple, nginx pourrait vous aider.

Le module ngx_http_headers_module permet d'ajouter les champs d'en-tête « Expires » et «Cache-Control», ainsi que des champs arbitraires, à un en-tête de réponse

...

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

Ajouter nginx devant votre service go en production semble judicieux. Il fournit beaucoup plus de fonctionnalités pour l'autorisation, la journalisation et la modification des demandes. En outre, cela donne la possibilité de contrôler qui a accès à votre service et pas seulement cela, mais on peut spécifier un comportement différent pour des emplacements spécifiques de votre application, comme démontré ci-dessus.

Je pourrais expliquer pourquoi utiliser un serveur Web avec votre api go, mais je pense que c'est un sujet pour une autre discussion.

shwoodard
la source