Mengapa permintaan PILIHAN dikirim dan bisakah saya menonaktifkannya?

415

Saya sedang membangun API web. Saya temukan setiap kali saya menggunakan Chrome untuk POST, DAPATKAN ke API saya, selalu ada permintaan PILIHAN yang dikirim sebelum permintaan sebenarnya, yang cukup mengganggu. Saat ini saya membuat server mengabaikan permintaan PILIHAN. Sekarang pertanyaan saya adalah apa yang baik untuk mengirim permintaan OPSI untuk menggandakan beban server? Apakah ada cara untuk sepenuhnya menghentikan browser dari mengirim permintaan OPTIONS?

Qian Chen
sumber

Jawaban:

376

sunting 2018-09-13 : menambahkan beberapa ketentuan tentang permintaan pra-penerbangan ini dan cara menghindarinya di akhir tanggapan ini.

OPTIONSpermintaan adalah apa yang kami sebut pre-flightpermintaan Cross-origin resource sharing (CORS).

Mereka diperlukan ketika Anda membuat permintaan di berbagai sumber yang berbeda dalam situasi tertentu.

Permintaan pra-penerbangan ini dibuat oleh beberapa browser sebagai langkah keamanan untuk memastikan bahwa permintaan yang dilakukan dipercaya oleh server. Artinya server memahami bahwa metode, asal, dan tajuk yang dikirim berdasarkan permintaan aman untuk ditindaklanjuti.

Server Anda tidak boleh mengabaikan tetapi menangani permintaan ini setiap kali Anda mencoba melakukan permintaan lintas asal.

Sumber daya yang bagus dapat ditemukan di sini http://enable-cors.org/

Cara untuk menangani ini agar nyaman adalah dengan memastikan bahwa untuk setiap jalur dengan OPTIONSmetode server mengirim respons dengan header ini

Access-Control-Allow-Origin: *

Ini akan memberi tahu browser bahwa server bersedia menjawab permintaan dari asal mana pun.

Untuk informasi lebih lanjut tentang cara menambahkan dukungan CORS ke server Anda, lihat bagan alur berikut

http://www.html5rocks.com/static/images/cors_server_flowchart.png

Diagram Alir CORS


sunting 2018-09-13

OPTIONSPermintaan CORS dipicu hanya dalam beberapa kasus, seperti yang dijelaskan dalam dokumen MDN :

Beberapa permintaan tidak memicu preflight CORS. Itu disebut "permintaan sederhana" dalam artikel ini, meskipun spec Fetch (yang mendefinisikan CORS) tidak menggunakan istilah itu. Permintaan yang tidak memicu preflight CORS — yang disebut “permintaan sederhana” —adalah permintaan yang memenuhi semua kondisi berikut:

Satu-satunya metode yang diizinkan adalah:

  • DAPATKAN
  • KEPALA
  • POS

Terlepas dari tajuk yang ditetapkan secara otomatis oleh agen pengguna (misalnya, Koneksi, Agen-Pengguna, atau tajuk lainnya dengan nama yang ditentukan dalam spesifikasi Ambil sebagai "nama tajuk terlarang"), tajuk satu-satunya yang diizinkan untuk menjadi set manual adalah yang ditentukan oleh spec Fetch sebagai “header-permintaan safelisted-CORS”, yaitu:

  • Menerima
  • Bahasa Terima
  • Konten-Bahasa
  • Jenis-Konten (tetapi perhatikan persyaratan tambahan di bawah)
  • DPR
  • Downlink
  • Simpan-Data
  • Lebar-viewport
  • Lebar

Satu-satunya nilai yang diizinkan untuk header Jenis Konten adalah:

  • application / x-www-form-urlencoded
  • multipart / formulir-data
  • teks / polos

Tidak ada pendengar acara yang terdaftar pada objek XMLHttpRequestUpload apa pun yang digunakan dalam permintaan; ini diakses menggunakan properti XMLHttpRequest.upload.

Tidak ada objek ReadableStream yang digunakan dalam permintaan.

Leo Correa
sumber
8
Tapi itu tidak realistis untuk mengatur bendera Chrome ini untuk semua pengguna umum.
Qian Chen
37
Tidak benar untuk mengatakan permintaan preflight diperlukan saat membuat permintaan lintas asal. Permintaan Preflight hanya diperlukan dalam situasi tertentu, seperti jika Anda mengatur header khusus, atau membuat permintaan selain dari mendapatkan, kepala dan posting.
Robin Clowers
4
Lucunya, ketika membuat permintaan CORS menggunakan jQuery, pustaka JavaScript secara khusus menghindari pengaturan tajuk khusus, bersama dengan kata peringatan untuk pengembang: Untuk permintaan lintas-domain, mengingat kondisi untuk preflight sama dengan jigsaw puzzle, kami hanya tidak pernah mengaturnya untuk memastikan.
Di bawah Radar
3
Bagaimana jika saya melakukan curlke api berfungsi, tetapi ketika menjalankan dari chrome saya mendapatkan kesalahan?
SuperUberDuper
5
@SuperUberDuper karena CORS dan permintaan preflight adalah masalah terkait browser. Anda dapat mensimulasikan CORS dengan menambahkan Originheader ke permintaan Anda untuk mensimulasikan seolah-olah permintaan tersebut berasal dari host tertentu (misalnya situs web Anda). Anda juga dapat mensimulasikan permintaan preflight dengan mengatur metode HTTP permintaan ke OPTIONSdan Access-Control-*Header
Leo Correa
234

Telah melewati masalah ini, di bawah ini adalah kesimpulan saya untuk masalah ini dan solusi saya.

Menurut strategi CORS (sangat disarankan Anda membacanya) Anda tidak bisa memaksa browser untuk berhenti mengirim permintaan OPSI jika menurutnya perlu.

Ada dua cara untuk mengatasinya:

  1. Pastikan permintaan Anda adalah "permintaan sederhana"
  2. Setel Access-Control-Max-Ageuntuk permintaan PILIHAN

Permintaan sederhana

Permintaan lintas situs sederhana adalah yang memenuhi semua kondisi berikut:

Satu-satunya metode yang diizinkan adalah:

  • DAPATKAN
  • KEPALA
  • POS

Terlepas dari tajuk yang diatur secara otomatis oleh agen pengguna (mis. Koneksi, Agen-Pengguna, dll.), Satu-satunya tajuk yang diizinkan untuk disetel secara manual adalah:

  • Menerima
  • Bahasa Terima
  • Konten-Bahasa
  • Jenis konten

Satu-satunya nilai yang diizinkan untuk header Jenis Konten adalah:

  • application / x-www-form-urlencoded
  • multipart / formulir-data
  • teks / polos

Permintaan sederhana tidak akan menyebabkan permintaan OPSI pra-penerbangan.

Tetapkan cache untuk pemeriksaan OPSI

Anda dapat menetapkan Access-Control-Max-Ageuntuk permintaan OPSI, sehingga tidak akan memeriksa izin lagi sampai habis.

Access-Control-Max-Age memberikan nilai dalam hitungan detik untuk berapa lama respons terhadap permintaan preflight dapat di-cache tanpa mengirim permintaan preflight lain.

Batasan Tercatat

  • Untuk Chrome, detik-detik maksimum Access-Control-Max-Ageyaitu 600yang 10 menit, menurut chrome kode sumber
  • Access-Control-Max-Agehanya berfungsi untuk satu sumber daya setiap kali, misalnya, GETpermintaan dengan jalur URL yang sama tetapi kueri yang berbeda akan diperlakukan sebagai sumber daya yang berbeda. Jadi permintaan ke sumber daya kedua masih akan memicu permintaan preflight.
Neekey
sumber
3
Ya ... ini harus menjadi jawaban yang diterima dan paling relevan dengan pertanyaan ..!
Rajesh Mbm
7
Terima kasih telah menyebutkan Access-Control-Max-Age. Itulah kuncinya di sini. Ini membantu Anda menghindari permintaan preflight yang berlebihan.
Idris Mokhtarzada
Saya menggunakan aksioma untuk menelepon mendapatkan permintaan. Di mana saya dapat mengatur Access-Control-Max-Age dalam permintaan aksioma?
Mohit Shah
+1 Header Access-Control-Max-Age adalah kuncinya di sini. Ini harus menjadi jawaban yang diterima! Saya mengatur 86400 detik (24 jam) pada header dan permintaan prefligth hilang!
revobtz
1
@VitalyZdanevich no! Jangan menghindari application/jsonhanya karena itu membuat permintaan Anda menjadi tidak "sederhana" (dan karenanya memicu CORS). Browser sedang melakukan tugasnya. Tetapkan server Anda untuk mengembalikan sesuatu seperti header Access-Control-Max-Age: 86400dan browser tidak akan mengirim ulang permintaan OPSI selama 24 jam.
colm.anseo
139

Silakan merujuk jawaban ini pada kebutuhan aktual untuk permintaan OPSI pra-penerbangan: CORS - Apa motivasi di balik memperkenalkan permintaan preflight?

Untuk menonaktifkan permintaan OPSI, kondisi di bawah ini harus dipenuhi untuk permintaan ajax:

  1. Permintaan tidak menetapkan tajuk HTTP khusus seperti 'aplikasi / xml' atau 'aplikasi / json' dll
  2. Metode permintaan harus berupa GET, HEAD atau POST. Jika POST, jenis konten harus menjadi salah satu application/x-www-form-urlencoded, multipart/form-dataatautext/plain

Referensi: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

device_exec
sumber
14
+1 untuk "header HTTP khusus"! Dalam kasus saya, mereka menyebabkan permintaan pra-penerbangan terpicu. Saya refactored permintaan untuk mengirim apa pun yang saya kirim di header sebagai badan permintaan dan permintaan OPSI berhenti dikirim.
Andre
21
application/xmlatau application/jsonbukan "Tajuk HTTP khusus". Header itu sendiri Content-Typedan memanggil header itu "custom" akan menyesatkan.
Leo Correa
1
Header HTTP khusus yang dihapus dan ini berfungsi seperti pesona!
Tim D
47

Ketika Anda memiliki konsol debug terbuka dan Disable Cacheopsi dihidupkan, permintaan preflight akan selalu dikirim (yaitu sebelum setiap permintaan). jika Anda tidak menonaktifkan cache, permintaan pra-penerbangan hanya akan dikirimkan satu kali (per server)

Nir
sumber
3
oh apa yang aku pikirkan. memiliki debugging selama berjam-jam ini adalah solusi saya. cache dinonaktifkan karena konsol debugging.
mauris
1
Bahkan jika konsol debug ditutup, permintaan preflight dikirim
Luca Perico
2
Luca: itu benar tetapi intinya adalah bahwa "disable cache" tidak berpengaruh ketika alat dev ditutup. permintaan preflight dikirim hanya sekali (per server tentu saja) jika cache tidak dinonaktifkan dan dikirim sebelum masing-masing dan setiap permintaan jika cache dinonaktifkan.
Nir
Itu sangat membantu.
Anuraag Patil
38

Ya itu mungkin untuk menghindari permintaan opsi. Permintaan opsi adalah permintaan preflight ketika Anda mengirim (memposting) data apa pun ke domain lain. Ini masalah keamanan browser. Tapi kita bisa menggunakan teknologi lain: lapisan transport iframe. Saya sangat menyarankan Anda melupakan konfigurasi CORS dan menggunakan solusi readymade dan itu akan berfungsi di mana saja.

Lihatlah di sini: https://github.com/jpillora/xdomain

Dan contoh kerja: http://jpillora.com/xdomain/

XTRUST.ORG
sumber
apakah ini sebenarnya semacam proxy drop-in?
matanster
15
"Permintaan opsi adalah permintaan preflight ketika Anda mengirim (mengirim) data ke domain lain." - Itu tidak benar. Anda dapat menggunakan XHR untuk mengirim permintaan POST apa pun yang dapat Anda kirim dengan formulir HTML normal tanpa memicu permintaan preflight. Hanya ketika Anda mulai melakukan hal-hal yang tidak bisa dilakukan oleh suatu formulir (seperti jenis konten khusus atau header permintaan tambahan), preflight dikirim.
Quentin
Solusi di sini tampaknya bergantung pada iframe shim yang berfungsi dalam beberapa kasus, tetapi memiliki beberapa batasan utama. Apa yang terjadi jika Anda ingin mengetahui kode status HTTP dari respons atau nilai header respons HTTP lain?
Stephen Crosby
5
iFrames tidak dibuat untuk itu.
Romko
1
ini adalah implementasi perangkat lunak dan tidak menjawab pertanyaan terakhir yaitu: "Apakah ada cara untuk sepenuhnya menghentikan browser mengirim permintaan OPSI?". Singkatnya, tidak ada cara untuk menonaktifkannya di Mozilla atau Chromium, itu diterapkan dalam kode dan satu-satunya "bekerja" pilihan hanya menghindari perilaku.
pemulung
15

Untuk pengembang yang memahami alasan keberadaannya tetapi perlu mengakses API yang tidak menangani panggilan OPSI tanpa auth, saya memerlukan jawaban sementara sehingga saya dapat mengembangkan secara lokal hingga pemilik API menambahkan dukungan SPA CORS yang tepat atau saya mendapatkan API proxy berdiri dan berjalan.

Saya menemukan Anda dapat menonaktifkan CORS di Safari dan Chrome di Mac.

Nonaktifkan kebijakan asal yang sama di Chrome

Chrome: Keluar dari Chrome, buka terminal dan rekatkan perintah ini: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

Safari: Menonaktifkan kebijakan sumber asli yang sama di Safari

Jika Anda ingin menonaktifkan kebijakan yang sama-asal pada Safari (saya punya 9.1.1), maka Anda hanya perlu mengaktifkan menu pengembang, dan pilih "Nonaktifkan Pembatasan Lintas-Asal" dari menu kembangkan.

Joseph Juhnke
sumber
4
Anda harus lebih menonjolkan bagian yang mengatakan "ini TIDAK PERNAH menjadi solusi permament !!!!" . Kebijakan asal yang sama adalah langkah pengamanan browser yang sangat penting dan tidak boleh dinonaktifkan ketika biasanya menjelajah internet.
jannis
Saya berharap web hanya akan bekerja dengan cara ini, jadi kami hanya dapat meminta data yang kami butuhkan dari server tanpa kerumitan tambahan.
jemiloii
14

Seperti yang disebutkan dalam posting sebelumnya, OPTIONSpermintaan ada karena suatu alasan. Jika Anda memiliki masalah dengan waktu respons yang besar dari server Anda (mis. Koneksi ke luar negeri), Anda juga dapat meminta cache browser Anda terlebih dahulu meminta permintaan.

Mintalah server Anda membalas dengan Access-Control-Max-Agetajuk dan untuk permintaan yang pergi ke titik akhir yang sama permintaan preflight akan di-cache dan tidak muncul lagi.

enpenax
sumber
1
Terima kasih untuk ini! Fakta bahwa OPTIONSpermintaan akan di-cache dengan header ini cukup buram di semua dokumentasi CORS yang saya baca.
joshperry
Dan cache hanya berlaku dengan url yang sama persis. Saya ingin cache preflight tingkat domain yang benar-benar dapat mengurangi perjalanan bolak-balik. (CORS konyol!)
heran
8

Saya telah memecahkan masalah ini seperti.

if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
    header("HTTP/1.1 200 OK");
    die();
}

Ini hanya untuk pembangunan. Dengan ini saya menunggu 9ms dan 500ms, bukan 8s dan 500ms. Saya bisa melakukan itu karena aplikasi JS produksi akan berada di mesin yang sama dengan produksi sehingga tidak akan ada OPTIONStetapi pengembangan adalah lokal saya.

AntiCZ
sumber
4

Anda tidak bisa tetapi Anda bisa menghindari CORS menggunakan JSONP.

Jose Mato
sumber
2
Anda hanya mendapatkan permintaan PILIHAN jika Anda melakukan sesuatu yang tidak sederhana . Anda hanya dapat membuat permintaan sederhana (DAPATKAN, tidak ada header kustom, tidak ada data otentikasi) dengan JSONP, jadi JSONP tidak dapat menggantikan di sini.
Quentin
Ya, saya tahu itu tetapi saya tidak tahu persis persyaratan proyek. Saya tahu ini bukan penghindaran yang mudah tetapi tergantung pada proyeknya. Dalam skenario terburuk untuk menghindari CORS Anda harus meneruskan data menggunakan parameter get. Jadi, JSONP dapat menggantikan kor tergantung pada persyaratan proyek (seperti yang Anda katakan, menggunakan permintaan sederhana)
Jose Mato
Saya tidak mengerti desain yang disebut pre-flight. Apa yang bisa menyebabkannya tidak aman, jika klien memutuskan untuk mengirim data ke server? Saya tidak melihat masuk akal untuk menggandakan beban pada kawat.
Qian Chen
@ ElgsQianChen, ini mungkin bisa menjawab pertanyaan Anda stackoverflow.com/questions/15381105/…
Leo Correa
0

Setelah menghabiskan satu setengah hari mencoba untuk mengatasi masalah yang sama saya menemukan itu ada hubungannya dengan IIS .

Proyek API Web saya disiapkan sebagai berikut:

// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
    var cors = new EnableCorsAttribute("*", "*", "*");
    config.EnableCors(cors);
    //...
}

Saya tidak memiliki opsi konfigurasi CORS spesifik di web.config> system.webServer node seperti yang saya lihat di banyak posting

Tidak ada kode spesifik CORS di global.asax atau di controller sebagai dekorator

Masalahnya adalah pengaturan kumpulan aplikasi .

Itu modus pipa berhasil ditetapkan untuk klasik ( berubah ke terintegrasi ) dan Identitas ditetapkan untuk Network Service ( berubah ke ApplicationPoolIdentity )

Mengubah pengaturan tersebut (dan menyegarkan kumpulan aplikasi) memperbaikinya untuk saya.

Ju66ernaut
sumber
-2

Apa yang berhasil bagi saya adalah mengimpor "github.com/gorilla/handlers" dan kemudian menggunakannya dengan cara ini:

router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")

headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})

log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))

Segera setelah saya menjalankan permintaan Ajax POST dan melampirkan data JSON ke dalamnya, Chrome akan selalu menambahkan header Tipe Konten yang tidak ada dalam konfigurasi AllowHeaders saya sebelumnya.

Kurt
sumber
-2

Salah satu solusi yang saya gunakan di masa lalu - katakanlah situs Anda ada di mydomain.com, dan Anda perlu membuat permintaan ajax ke foreigndomain.com

Konfigurasikan penulisan ulang IIS dari domain Anda ke domain asing - mis

<rewrite>
  <rules>
    <rule name="ForeignRewrite" stopProcessing="true">
        <match url="^api/v1/(.*)$" />
        <action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
    </rule>
  </rules>
</rewrite>

di situs mydomain.com Anda - Anda kemudian dapat membuat permintaan asal yang sama, dan tidak perlu untuk permintaan opsi :)

David McEleney
sumber
-2

Ini bisa diselesaikan jika menggunakan proxy yang memotong permintaan dan menulis header yang sesuai. Dalam kasus Varnish khusus ini akan menjadi aturan:

if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
   set resp.http.Access-Control-Max-Age = "1728000";
   set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
   set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
   set resp.http.Content-Length = "0";
   set resp.http.Content-Type = "text/plain charset=UTF-8";
   set resp.status = 204;
}

}

Rafa Cuestas
sumber
-5

Mungkin ada solusi (tetapi saya tidak mengujinya): Anda dapat menggunakan CSP (Kebijakan Keamanan Konten) untuk mengaktifkan domain jarak jauh Anda dan browser mungkin akan melewatkan verifikasi permintaan PILIHAN CORS.

Saya jika menemukan waktu, saya akan mengujinya dan memperbarui posting ini!

CSP: https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy

Spesifikasi CSP: https://www.w3.org/TR/CSP/

Tournier Arnaud
sumber
Saya baru saja menguji dan tidak berhasil, CORS masih diperlukan setelah CSP untuk penerimaan permintaan xhr ...
Arnaud Tournier