Pengaturan tajuk HTTP

165

Saya mencoba mengatur header di server web Go saya. Saya menggunakan gorilla/muxdan net/httppaket.

Saya ingin mengatur Access-Control-Allow-Origin: *untuk mengizinkan lintas domain AJAX.

Ini kode Go saya:

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

The net/httppaket memiliki dokumentasi yang menggambarkan mengirimkan header permintaan http seolah-olah klien - Saya tidak tahu pasti bagaimana set header respon?

Zen
sumber

Jawaban:

227

Sudahlah, saya menemukan jawabannya - saya menggunakan Set()metode ini Header()(doh!)

Pawang saya terlihat seperti ini sekarang:

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

Mungkin ini akan membantu seseorang karena kafein seperti saya kadang-kadang :)

Zen
sumber
2
Saya telah mengalami masalah yang sama, mungkin juga bermanfaat untuk menambahkan: w.Header().Add("Access-Control-Allow-Methods", "PUT") w.Header().Add("Access-Control-Allow-Headers", "Content-Type")
Ray
1
Ini tidak akan berfungsi jika klien AJAX menetapkan withCredentials:true(nilai "*" tidak diizinkan ketika kredensial dikirim, yang merupakan kasus penggunaan umum). Anda harus mengatur asal ke pemohon (lihat jawaban Matt Bucci di bawah ini untuk caranya).
orcaman
98

Semua jawaban di atas salah karena mereka gagal menangani permintaan preflight OPSI, solusinya adalah menimpa antarmuka router mux. Lihat AngularJS $ http dapatkan permintaan gagal dengan tajuk khusus (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
sumber
19
"Semua hal di atas" ... jawaban dapat diurutkan dalam banyak cara sehingga frasa ini tidak berarti apa yang Anda inginkan.
Dave C
Permintaan CORS sederhana tidak memiliki preflight, semuanya tergantung pada apa yang Anda coba layani.
laike9m
Jangan lupa Access-Control-Allow-Credentials": "true"untuk permintaan dengan hanya Cookie.
Federico
23

Jangan gunakan '*' untuk Origin, sampai Anda benar-benar membutuhkan perilaku publik sepenuhnya.
Seperti yang dikatakan Wikipedia :

"Nilai" * "adalah spesial karena tidak memungkinkan permintaan untuk memasok kredensial, yang berarti otentikasi HTTP, sertifikat SSL sisi klien, juga tidak mengizinkan cookie untuk dikirim."

Itu berarti, Anda akan mendapatkan banyak kesalahan, terutama di Chrome ketika Anda akan mencoba menerapkan misalnya otentikasi sederhana.

Berikut ini adalah pembungkus yang diperbaiki:

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

Dan jangan lupa untuk membalas semua header ini dengan permintaan PILIHAN prelight.

tacobot
sumber
1
Saya tidak begitu mengerti penggunaan bungkus ini, dapatkah Anda memberikan contoh bagaimana Anda akan membungkus gagang http Anda dengan kode ini? Saya menggunakan gorilla mux sehingga penggunaan saya saat ini router.HandleFunc("/user/action", user.UserAction) http.Handle("/", router) http.ListenAndServe(":8080", nil).Set("Access-Control-Allow-Origin", "*")
Matt Bucci
2
Saya sekarang membungkus panggilan pegangan saya dengan addDefaultHeaders seperti router.HandleFunc("/user/action", addDefaultHeaders(user.UserAction)) namun karena saya memiliki sekitar 16 rute ini tidak ideal apakah ada cara untuk menentukannya sebagai pembungkus pada paket http atau lapisan router mux
Matt Bucci
14

Tetapkan middleware golang yang tepat, sehingga Anda dapat menggunakan kembali pada titik akhir apa pun.

Jenis dan Fungsi Helper

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 yang sebenarnya

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

Titik akhir

REMEBER! Middlewares diaplikasikan dengan urutan terbalik (ExpectGET () mendapatkan api terlebih dahulu)

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

Jika Anda tidak ingin mengganti router Anda (jika Anda tidak memiliki aplikasi yang dikonfigurasi dengan cara yang mendukung ini, atau ingin mengkonfigurasi CORS pada rute dengan basis rute), tambahkan penangan OPSI untuk menangani permintaan pra penerbangan .

Yaitu, dengan Gorilla Mux rute Anda akan terlihat seperti:

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

Perhatikan di atas bahwa selain penangan POST kami, kami mendefinisikan penangan metode OPTIONS tertentu .

Dan kemudian untuk menangani metode preflight OPSI yang sebenarnya, Anda dapat mendefinisikan AccountsCreatePreFlight seperti:

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

Apa yang benar-benar membuat ini semua klik untuk saya (selain benar-benar memahami cara kerja CORS) adalah bahwa Metode HTTP dari permintaan preflight berbeda dari Metode HTTP dari permintaan aktual. Untuk memulai CORS, browser mengirimkan permintaan preflight dengan HTTP Method OPTIONS, yang harus Anda tangani secara eksplisit di router Anda, dan kemudian, jika ia menerima respons yang sesuai "Access-Control-Allow-Origin": origin(atau "*" untuk semua) dari aplikasi Anda, browser akan memulai aktual permintaan.

Saya juga percaya bahwa Anda hanya dapat melakukan "*" untuk jenis permintaan standar (yaitu: GET), tetapi untuk yang lain Anda harus menetapkan secara eksplisit asal seperti yang saya lakukan di atas.

Kyle Chadha
sumber
12

Saya membuat bungkus untuk kasus ini:

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
sumber
1

Saya memiliki masalah yang sama seperti yang dijelaskan di atas, solusi yang diberikan di atas benar, pengaturan yang saya miliki adalah sebagai berikut 1) Angularjs untuk Klien 2) kerangka Beego untuk server GO

Harap ikuti poin-poin ini 1) Pengaturan CORS harus diaktifkan hanya pada server GO 2) JANGAN menambahkan semua jenis header di angularJS kecuali untuk ini

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

Di server GO Anda menambahkan pengaturan CORS sebelum permintaan mulai diproses sehingga permintaan preflight menerima 200 OK setelah metode OPTIONS akan dikonversi menjadi GET, POST, PUT atau apa pun jenis permintaan Anda.

Prostil Hardi
sumber
-7

Saya tahu ini adalah twist yang berbeda pada jawabannya, tetapi bukankah ini lebih menjadi perhatian untuk server web? Misalnya, nginx , bisa membantu.

The ngx_http_headers_module modul memungkinkan menambahkan “Berakhir” dan “Cache-Control” field header, dan bidang sewenang-wenang, untuk header respon

...

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

Menambahkan nginx di depan layanan go Anda dalam produksi tampaknya bijaksana. Ini memberikan lebih banyak fitur untuk mengotorisasi, masuk, dan memodifikasi permintaan. Selain itu, ini memberikan kemampuan untuk mengontrol siapa yang memiliki akses ke layanan Anda dan tidak hanya itu tetapi seseorang dapat menentukan perilaku yang berbeda untuk lokasi tertentu di aplikasi Anda, seperti yang ditunjukkan di atas.

Saya bisa terus tentang mengapa menggunakan server web dengan api go Anda, tapi saya pikir itu topik untuk diskusi lain.

semak-semak
sumber