Definindo cabeçalhos HTTP

165

Estou tentando definir um cabeçalho no meu servidor web Go. Estou usando gorilla/muxe net/httppacotes.

Eu gostaria de definir Access-Control-Allow-Origin: *para permitir AJAX entre domínios.

Aqui está o meu 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)
}

O net/httppacote possui documentação descrevendo o envio de cabeçalhos de solicitação http como se fosse um cliente - não sei exatamente como definir cabeçalhos de resposta?

zen
fonte

Respostas:

227

Não importa, eu descobri - usei o Set()método emHeader() (doh!)

Meu manipulador fica assim agora:

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

Talvez isso ajude alguém tão carente de cafeína quanto eu algum dia :)

zen
fonte
2
Eu tenho tido o mesmo problema, pode também ser útil para acrescentar: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray
1
Isso não funcionará no caso de o cliente AJAX definir withCredentials:true(o valor "*" não é permitido quando credenciais são enviadas, que é um caso de uso comum). Você deve definir a origem para o solicitante (consulte a resposta de Matt Bucci abaixo para saber como).
Orcaman
98

Todas as respostas acima estão erradas porque não conseguem lidar com a solicitação de comprovação de OPTIONS, a solução é substituir a interface do roteador mux. Consulte Falha na solicitação de obtenção do AngularJS $ http com cabeçalho personalizado (permitido no 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
fonte
19
"Todas as opções acima" ... as respostas podem ser classificadas de várias maneiras, portanto esta frase não significa o que você deseja.
Dave C
Solicitações simples do CORS não têm preflight, tudo depende do que você está tentando servir.
laike9m
Não se esqueça Access-Control-Allow-Credentials": "true"de solicitações com httpOnly Cookies.
Federico
23

Não use '*' para o Origin até que você realmente precise de um comportamento completamente público.
Como a Wikipedia diz :

"O valor de" * "é especial, pois não permite que solicitações forneçam credenciais, significando autenticação HTTP, certificados SSL no lado do cliente, nem permite o envio de cookies."

Isso significa que você receberá muitos erros, especialmente no Chrome, quando tentar implementar, por exemplo, uma autenticação simples.

Aqui está um wrapper corrigido:

// 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 não se esqueça de responder a todos esses cabeçalhos à solicitação OPTIONS de comprovação.

tacobot
fonte
1
Não entendo muito bem o uso desse wrapper, você pode dar um exemplo de como envolver seu manipulador http com esse código? Estou usando gorila mux então meu uso atual é router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci
2
Agora estou passando meus lidar com as chamadas com addDefaultHeaders como router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) , porém, como eu tenho cerca de 16 rotas este não é o ideal, há alguma maneira de especificar isso como um wrapper para o pacote http ou camada router mux
Matt Bucci
14

Defina um middleware golang adequado para poder reutilizar em qualquer terminal.

Tipo e Função do Auxiliar

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

Ponto final

LEMBRE-SE! Middlewares são aplicados em ordem inversa (ExpectGET () é acionado primeiro)

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

Se você não deseja substituir seu roteador (se você não tiver seu aplicativo configurado de uma maneira que ofereça suporte a isso, ou desejar configurar o CORS em uma rota por rota), adicione um manipulador de OPTIONS para lidar com a solicitação de pré-vôo .

Ou seja, com o Gorilla Mux, suas rotas se pareceriam com:

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

Observe acima que, além de nosso manipulador POST, estamos definindo um manipulador de método OPTIONS específico .

E, para lidar com o método de comprovação OPTIONS, você pode definir AccountsCreatePreFlight da seguinte maneira:

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

O que realmente fez tudo isso clicar para mim (além de realmente entender como o CORS funciona) é que o método HTTP de uma solicitação de comprovação é diferente do método HTTP da solicitação real. Para iniciar o CORS, o navegador envia uma solicitação de comprovação com o método HTTP OPTIONS, que você precisa manipular explicitamente no roteador e, em seguida, se receber a resposta apropriada"Access-Control-Allow-Origin": origin (ou "*" para todos) do seu aplicativo, inicia a real solicitação.

Eu também acredito que você só pode fazer "*" para tipos padrão de solicitações (por exemplo: GET), mas para outros você precisará definir explicitamente a origem, como eu faço acima.

Kyle Chadha
fonte
12

Eu crio o wrapper 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
fonte
1

Eu tive o mesmo problema, conforme descrito acima, as soluções fornecidas acima estão corretas, a configuração que tenho é a seguinte 1) Angularjs para o cliente 2) Estrutura do Beego para servidor GO

Por favor, siga estes pontos 1) As configurações do CORS devem ser ativadas apenas no servidor GO 2) NÃO adicione nenhum tipo de cabeçalho no angularJS, exceto este

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

No servidor GO, adicione as configurações do CORS antes que a solicitação comece a ser processada, para que a solicitação de comprovação receba 200 OK, após o que o método OPTIONS será convertido em GET, POST, PUT ou qualquer que seja o seu tipo de solicitação.

Prostil Hardi
fonte
-7

Sei que essa é uma reviravolta diferente na resposta, mas isso não é mais uma preocupação para um servidor web? Por exemplo, nginx , poderia ajudar.

O módulo ngx_http_headers_module permite adicionar os campos de cabeçalho "Expira" e "Controle de cache" e campos arbitrários a um cabeçalho de resposta

...

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

Adicionar nginx na frente do seu serviço go na produção parece sábio. Ele fornece muito mais recursos para autorizar, registrar e modificar solicitações. Além disso, permite controlar quem tem acesso ao seu serviço e não apenas isso, mas é possível especificar um comportamento diferente para locais específicos no seu aplicativo, como demonstrado acima.

Eu poderia continuar falando sobre o porquê de usar um servidor da Web com sua API go, mas acho que esse é um tópico para outra discussão.

shwoodard
fonte