Ustawianie nagłówków HTTP

165

Próbuję ustawić nagłówek na moim serwerze internetowym Go. Używam gorilla/muxi net/httppakietów.

Chcę Access-Control-Allow-Origin: *zezwolić na obsługę AJAX w wielu domenach.

Oto mój kod 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)
}

net/httpPakiet zawiera dokumentację opisującą wysyłanie nagłówków HTTP, jak gdyby był to klient - Nie jestem do końca pewien, jak do nagłówków odpowiedzi ustawić?

Zen
źródło

Odpowiedzi:

227

Nieważne, rozgryzłem to - użyłem Set()metody na Header()(doh!)

Mój program obsługi wygląda teraz tak:

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

Może to kiedyś pomoże komuś tak samo pozbawionemu kofeiny jak ja :)

Zen
źródło
2
Miałem ten sam problem, pomocne może być również dodanie: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray,
1
Nie zadziała to w przypadku ustawienia klienta AJAX withCredentials:true(wartość „*” jest niedozwolona, ​​gdy wysyłane są poświadczenia, co jest częstym przypadkiem użycia). Musisz ustawić źródło na żądającym (zobacz odpowiedź Matta Bucci poniżej, aby dowiedzieć się, jak to zrobić).
orcaman
98

Wszystkie powyższe odpowiedzi są błędne, ponieważ nie obsługują one żądania wstępnego OPTIONS, rozwiązaniem jest zastąpienie interfejsu routera mux. Zobacz żądanie pobrania AngularJS $ http nie powiodło się z niestandardowym nagłówkiem (dozwolone w 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
źródło
19
„Wszystkie powyższe”… odpowiedzi można sortować na wiele sposobów, więc to wyrażenie nie oznacza tego, czego chcesz.
Dave C
Proste żądania CORS nie mają inspekcji wstępnej, wszystko zależy od tego, co próbujesz obsłużyć.
laike9m
Nie zapomnij Access-Control-Allow-Credentials": "true"o żądaniach z plikami cookie httpOnly.
Federico
23

Nie używaj znaku „*” dla Origin, dopóki naprawdę nie potrzebujesz całkowicie publicznego zachowania.
Jak mówi Wikipedia :

Wartość „*” jest wyjątkowa, ponieważ nie zezwala na żądania dostarczania danych uwierzytelniających, co oznacza uwierzytelnianie HTTP, certyfikaty SSL po stronie klienta, ani nie zezwala na wysyłanie plików cookie. "

Oznacza to, że otrzymasz wiele błędów, zwłaszcza w Chrome, gdy spróbujesz zaimplementować na przykład proste uwierzytelnianie.

Oto poprawione opakowanie:

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

I nie zapomnij odpowiedzieć na wszystkie te nagłówki na żądanie OPCJI inspekcji wstępnej.

tacobot
źródło
1
Nie do końca rozumiem użycie tego opakowania, czy możesz podać przykład, w jaki sposób można opakować uchwyt http tym kodem? Używam mux goryla, więc moje obecne użycie to router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci,
2
Teraz zawijam wywołania obsługi za pomocą addDefaultHeaders, jak router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) jednak mam około 16 tras, co nie jest idealne, czy istnieje sposób, aby określić je jako opakowanie w pakiecie http lub warstwie routera mux
Matt Bucci,
14

Ustaw odpowiednie oprogramowanie pośredniczące golang, aby można było użyć go ponownie w dowolnym punkcie końcowym.

Typ i funkcja pomocnika

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
}

Rzeczywiste oprogramowanie pośredniczące

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

Punkt końcowy

PAMIĘTAJ! Oprogramowanie pośredniczące jest stosowane w odwrotnej kolejności (ExpectGET () uruchamia się jako pierwsza)

mux.Handle("/watcher/{action}/{device}",Adapt(api.SerialHandler(mux),
    api.EnableCORS(),
    api.ExpectGET(),
))
CESCO
źródło
14

Jeśli nie chcesz zastąpić routera (jeśli nie masz aplikacji skonfigurowanej w sposób, który to obsługuje lub chcesz skonfigurować CORS na podstawie trasy po trasie), dodaj obsługę OPTIONS do obsługi żądania przed lotem .

To znaczy, z Gorilla Mux Twoje trasy wyglądałyby tak:

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

Zauważ powyżej, że oprócz naszej obsługi POST, definiujemy konkretną procedurę obsługi metody OPTIONS .

A następnie, aby faktycznie obsłużyć metodę preflight OPTIONS, możesz zdefiniować AccountsCreatePreFlight w następujący sposób:

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

To, co naprawdę sprawiło, że to wszystko kliknęło dla mnie (oprócz faktycznego zrozumienia, jak działa CORS), to fakt, że metoda HTTP żądania inspekcji wstępnej różni się od metody HTTP rzeczywistego żądania. Aby zainicjować CORS, przeglądarka wysyła żądanie inspekcji wstępnej z opcjami metody HTTP, które należy obsługiwać jawnie na routerze, a następnie, jeśli otrzyma odpowiednią odpowiedź "Access-Control-Allow-Origin": origin(lub „*” dla wszystkich) z aplikacji, inicjuje rzeczywistą żądanie.

Uważam również, że możesz zrobić "*" tylko dla standardowych typów żądań (np.: GET), ale dla innych będziesz musiał jawnie ustawić źródło, tak jak ja powyżej.

Kyle Chadha
źródło
12

Tworzę opakowanie dla tego przypadku:

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
źródło
1

Miałem ten sam problem co powyżej, powyższe rozwiązania są poprawne, konfiguracja jest następująca 1) Angularjs dla Klienta 2) Framework Beego dla serwera GO

Postępuj zgodnie z poniższymi punktami 1) Ustawienia CORS muszą być włączone tylko na serwerze GO 2) NIE dodawaj żadnego typu nagłówków w angularJS z wyjątkiem tego

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

W serwerze you GO dodaj ustawienia CORS, zanim żądanie zacznie być przetwarzane, aby żądanie preflight otrzymało 200 OK, po czym metoda OPTIONS zostanie przekonwertowana na GET, POST, PUT lub jakikolwiek inny typ żądania.

Prostil Hardi
źródło
-7

Wiem, że to inna zmiana w odpowiedzi, ale czy nie dotyczy to bardziej serwera WWW? Na przykład może pomóc nginx .

Ngx_http_headers_module Moduł umożliwia dodanie „wygasa” i „Cache-Control” nagłówki oraz dowolnych pól z nagłówka odpowiedzi

...

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

Dodanie nginx przed usługą go w produkcji wydaje się rozsądne. Zapewnia o wiele więcej funkcji autoryzacji, rejestrowania i modyfikowania żądań. Daje również możliwość kontrolowania, kto ma dostęp do Twojej usługi, i nie tylko, ale można określić różne zachowania dla określonych lokalizacji w Twojej aplikacji, jak pokazano powyżej.

Mógłbym dalej mówić o tym, dlaczego warto używać serwera WWW z interfejsem go api, ale myślę, że to temat na inną dyskusję.

shwoodard
źródło