Impostazione delle intestazioni HTTP


165

Sto cercando di impostare un'intestazione nel mio server web Go. Sto usando gorilla/muxe net/httppacchetti.

Vorrei impostare Access-Control-Allow-Origin: *per consentire AJAX tra domini.

Ecco il mio codice 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)
}

Il net/httppacchetto contiene documentazione che descrive l'invio di intestazioni di richiesta http come se fosse un client. Non sono sicuro di come impostare le intestazioni di risposta?

Risposte:


227

Non importa, l'ho capito - ho usato il Set()metodo su Header()(doh!)

Il mio gestore ora assomiglia a questo:

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

Forse questo aiuterà qualcuno come caffeina privato come me stesso a volte :)


2
Ho avuto lo stesso problema, potrebbe anche essere utile aggiungere: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray

1
Questo non funzionerà nel caso in cui il client AJAX withCredentials:truevenga impostato (il valore "*" non è consentito quando vengono inviate le credenziali, che è un caso d'uso comune). È necessario impostare l'origine sul richiedente (vedere la risposta di Matt Bucci di seguito per come).
Orcaman,

98

Tutte le risposte di cui sopra sono errate perché non riescono a gestire la richiesta di preflight OPTIONS, la soluzione è quella di sostituire l'interfaccia del router mux. Vedi AngularJS $ http richiesta non riuscita con intestazione personalizzata (concessa 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)
}

19
"Tutte le precedenti" ... le risposte possono essere ordinate in molti modi, quindi questa frase non significa quello che vuoi.
Dave C

Le semplici richieste CORS non hanno preflight, tutto dipende da cosa stai cercando di servire.
laike9m,

Non dimenticare le Access-Control-Allow-Credentials": "true"richieste con i cookie solo http.
Federico

23

Non usare '*' per Origin, fino a quando non hai davvero bisogno di un comportamento completamente pubblico.
Come dice Wikipedia :

"Il valore di" * "è speciale in quanto non consente alle richieste di fornire credenziali, ovvero autenticazione HTTP, certificati SSL lato client, né consente l'invio di cookie."

Ciò significa che otterrai molti errori, soprattutto in Chrome quando proverai ad implementare una semplice autenticazione.

Ecco un wrapper corretto:

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

E non dimenticare di rispondere a tutte queste intestazioni alla richiesta OPZIONI preflight.


1
Non capisco bene l'uso di questo wrapper, puoi fare un esempio di come avvolgeresti il ​​tuo handle http con questo codice? Sto usando gorilla mux, quindi il mio attuale utilizzo è router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci,

2
Ora sto avvolgendo le mie chiamate handle con addDefaultHeaders come router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) tuttavia dato che ho circa 16 route, questo non è l'ideale c'è un modo per specificarlo come wrapper nel pacchetto http o nel livello router mux
Matt Bucci

14

Impostare un middleware Golang adeguato, in modo da poterlo riutilizzare su qualsiasi endpoint.

Tipo e funzione di supporto

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 reale

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

Ricorda! I middleware vengono applicati in ordine inverso (ExpectGET () ottiene prima gli incendi)

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

14

Se non si desidera sovrascrivere il router (se l'app non è configurata in modo tale da supportarlo o se si desidera configurare CORS in base al percorso per percorso), aggiungere un gestore OPTIONS per gestire la richiesta pre-volo .

Cioè, con Gorilla Mux i tuoi percorsi sembrerebbero:

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

Nota sopra che oltre al nostro gestore POST, stiamo definendo un gestore del metodo OPTIONS specifico .

E quindi per gestire effettivamente il metodo di preflight OPTIONS, è possibile definire AccountsCreatePreFlight in questo modo:

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

Ciò che ha reso davvero tutto questo clic per me (oltre a comprendere effettivamente come funziona CORS) è che il Metodo HTTP di una richiesta di verifica preliminare è diverso dal Metodo HTTP della richiesta effettiva. Per avviare CORS, il browser invia una richiesta di verifica preliminare con OPTIONS del metodo HTTP, che è necessario gestire esplicitamente nel router, quindi, se riceve la risposta appropriata "Access-Control-Allow-Origin": origin(o "*" per tutti) dall'applicazione, avvia l'effettivo richiesta.

Credo anche che tu possa fare "*" solo per tipi standard di richieste (es: GET), ma per altri dovrai impostare esplicitamente l'origine come faccio sopra.


12

Creo wrapper per questo 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)
    }
}

1

Ho avuto lo stesso problema descritto sopra, le soluzioni fornite sopra sono corrette, l'impostazione che ho è la seguente 1) Angularjs per il client 2) Beego framework per il server GO

Seguire questi punti 1) Le impostazioni CORS devono essere abilitate solo sul server GO 2) NON aggiungere alcun tipo di intestazioni in angularJS eccetto questo

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

Nel tuo server GO aggiungi le impostazioni CORS prima che la richiesta inizi a essere elaborata in modo che la richiesta di verifica preliminare riceva 200 OK, dopodiché il metodo OPTIONS verrà convertito in GET, POST, PUT o qualunque sia il tuo tipo di richiesta.


-7

So che questa è una svolta diversa nella risposta, ma non è più una preoccupazione per un web server? Ad esempio, nginx , potrebbe aiutare.

Il modulo ngx_http_headers_module consente di aggiungere i campi di intestazione "Scadenza" e "Controllo cache" e i campi arbitrari a un'intestazione di risposta

...

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

L'aggiunta di nginx davanti al tuo servizio go in produzione sembra saggia. Fornisce molte più funzioni per l'autorizzazione, la registrazione e la modifica delle richieste. Inoltre, ti dà la possibilità di controllare chi ha accesso al tuo servizio e non solo, ma puoi specificare comportamenti diversi per posizioni specifiche nella tua app, come dimostrato sopra.

Potrei continuare sul perché usare un web server con il tuo go api, ma penso che sia un argomento per un'altra discussione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.