Установка заголовков HTTP

165

Я пытаюсь установить заголовок на своем веб-сервере Go. Я использую gorilla/muxи net/httpпакеты.

Я хотел бы Access-Control-Allow-Origin: *разрешить междоменный AJAX.

Вот мой код 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/httpпакете есть документация, описывающая отправку заголовков HTTP-запроса, как если бы это был клиент - я не совсем уверен, как установить заголовки ответа?

Zen
источник

Ответы:

227

Неважно, я понял это - я использовал Set()метод Header()(до!)

Мой обработчик теперь выглядит так:

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

Может быть, это поможет кому-то, лишенному кофеина, так же как и мне :)

Zen
источник
2
У меня возникла та же проблема, может быть полезно добавить: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Рэй
1
Это не будет работать в случае, если клиент AJAX устанавливает withCredentials:true(значение «*» не допускается при отправке учетных данных, что является распространенным случаем использования). Вы должны установить источник запроса (см. Ответ Мэтта Буччи ниже).
Оркамен
98

Все вышеприведенные ответы неверны, поскольку они не могут обработать предварительный запрос OPTIONS, решение состоит в том, чтобы переопределить интерфейс мультиплексорного маршрутизатора. Смотрите AngularJS $ http: запрос на получение не выполнен с пользовательским заголовком (разрешено в 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
«Все вышеперечисленное»… ответы можно сортировать разными способами, поэтому эта фраза не означает, что вы хотите.
Дейв С
Простые запросы CORS не имеют предварительной проверки, все зависит от того, что вы пытаетесь обслуживать.
laike9m
Не забывайте Access-Control-Allow-Credentials": "true"о запросах с httpOnly Cookies.
Федерико
23

Не используйте '*' для Происхождения, пока Вам действительно не нужно абсолютно публичное поведение.
Как говорит Википедия :

«Значение« * »является особенным в том смысле, что оно не позволяет запросам предоставлять учетные данные, то есть HTTP-аутентификацию, клиентские SSL-сертификаты, и не разрешает отправку файлов cookie»

Это означает, что вы получите много ошибок, особенно в Chrome, когда вы попытаетесь реализовать, например, простую аутентификацию.

Вот исправленная обертка:

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

И не забудьте ответить на все эти заголовки на предварительный запрос OPTIONS.

tacobot
источник
1
Я не совсем понимаю, как использовать эту обертку. Можете ли вы привести пример того, как вы обернули бы этот http-дескриптор этим кодом? Я использую гориллу мукс, так что мое текущее использование router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Мэтт Буччи
2
Теперь я обертываю свои вызовы дескрипторов с помощью addDefaultHeaders, как, тем не router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) менее, поскольку у меня есть около 16 маршрутов, это не идеально, есть ли способ указать его в качестве оболочки на уровне пакета http или mux-маршрутизатора
Matt Bucci
14

Установите правильное промежуточное ПО golang, чтобы вы могли использовать его на любой конечной точке.

Тип и функция помощника

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
}

Актуальное промежуточное ПО

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

Конечная точка

Remeber! Промежуточные программы применяются в обратном порядке (ExpectGET () сначала срабатывает)

mux.Handle("/watcher/{action}/{device}",Adapt(api.SerialHandler(mux),
    api.EnableCORS(),
    api.ExpectGET(),
))
СЕСКО
источник
14

Если вы не хотите переопределять свой маршрутизатор (если ваше приложение не настроено так, чтобы это поддерживалось, или вы хотите настроить CORS на маршруте по маршруту), добавьте обработчик OPTIONS для обработки запроса перед полетом ,

То есть с Gorilla Mux ваши маршруты будут выглядеть так:

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

Обратите внимание, что в дополнение к нашему обработчику POST мы определяем определенный обработчик метода OPTIONS .

А затем для фактической обработки метода предварительной проверки OPTIONS вы можете определить AccountsCreatePreFlight следующим образом:

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

Что действительно заставило меня все это щелкнуть (в дополнение к собственному пониманию того, как работает CORS), так это то, что HTTP-метод предварительного запроса отличается от HTTP-метода реального запроса. Чтобы инициировать CORS, браузер отправляет предварительный запрос с опциями метода HTTP, который вы должны явно обработать в своем маршрутизаторе, а затем, если он получает соответствующий ответ "Access-Control-Allow-Origin": origin(или «*» для всех) от вашего приложения, он инициирует фактический запрос.

Я также считаю, что вы можете делать «*» только для стандартных типов запросов (например, GET), но для других вам придется явно указать источник, как я делал выше.

Кайл Чадха
источник
12

Я создаю обертку для этого случая:

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
источник
1

У меня была та же проблема, что и описанная выше, приведенные выше решения верны, у меня есть следующие настройки: 1) Angularjs для клиента 2) Beego Framework для GO-сервера

Пожалуйста, следуйте этим пунктам 1) Настройки CORS должны быть включены только на сервере GO 2) НЕ добавляйте заголовки любого типа в angularJS, за исключением этого

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

На вашем GO-сервере добавьте параметры CORS до начала обработки запроса, чтобы предварительный запрос получил 200 OK, после чего метод OPTIONS будет преобразован в GET, POST, PUT или любой другой тип запроса.

Простил Харди
источник
-7

Я знаю, что это другой поворот в ответе, но разве это не проблема для веб-сервера? Например, nginx , может помочь.

Модуль ngx_http_headers_module позволяет добавлять поля заголовка «Expires» и «Cache-Control» и произвольные поля в заголовок ответа

...

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

Добавление nginx перед вашим сервисом go в производстве кажется разумным. Он предоставляет гораздо больше возможностей для авторизации, регистрации и изменения запросов. Кроме того, это дает возможность контролировать, кто имеет доступ к вашему сервису, и не только это, но и можно указать другое поведение для определенных местоположений в вашем приложении, как показано выше.

Я мог бы продолжить о том, почему использовать веб-сервер с вашим go api, но я думаю, что это тема для другого обсуждения.

shwoodard
источник