AngularJS melakukan permintaan HTTP OPSI untuk sumber daya lintas asal

264

Saya mencoba mengatur AngularJS untuk berkomunikasi dengan sumber daya asal-silang di mana host aset yang memberikan file templat saya ada di domain yang berbeda dan oleh karena itu permintaan XHR yang berkinerja angular harus berupa cross-domain. Saya telah menambahkan header CORS yang sesuai ke server saya untuk permintaan HTTP untuk membuat ini berfungsi, tetapi tampaknya tidak berfungsi. Masalahnya adalah ketika saya memeriksa permintaan HTTP di browser saya (chrome), permintaan yang dikirim ke file aset adalah permintaan OPTIONS (seharusnya permintaan GET).

Saya tidak yakin apakah ini bug di AngularJS atau jika saya perlu mengkonfigurasi sesuatu. Dari apa yang saya pahami pembungkus XHR tidak dapat membuat permintaan HTTP OPSI sehingga sepertinya browser sedang mencari tahu apakah "diizinkan" untuk mengunduh aset terlebih dahulu sebelum melakukan permintaan GET. Jika demikian, apakah saya perlu mengatur header CORS (Access-Control-Allow-Origin: http://asset.host ... ) dengan host aset juga?

matsko
sumber

Jawaban:

226

Permintaan PILIHAN sama sekali bukan bug AngularJS, beginilah standar Cross-Origin Resource Sharing memberi mandat browser untuk berperilaku. Silakan merujuk ke dokumen ini: https://developer.mozilla.org/en-US/docs/HTTP_access_control , di mana di bagian "Ikhtisar" disebutkan:

Standar Cross-Origin Resource Sharing bekerja dengan menambahkan header HTTP baru yang memungkinkan server untuk menggambarkan set asal yang diizinkan untuk membaca informasi tersebut menggunakan browser web. Selain itu, untuk metode permintaan HTTP yang dapat menyebabkan efek samping pada data pengguna (khususnya; untuk metode HTTP selain GET, atau untuk penggunaan POST dengan jenis MIME tertentu). Spesifikasi mengamanatkan bahwa browser "preflight" permintaan, meminta metode yang didukung dari server dengan header permintaan HTTP OPTIONS, dan kemudian, setelah "persetujuan" dari server, mengirimkan permintaan aktual dengan metode permintaan HTTP yang sebenarnya. Server juga dapat memberi tahu klien apakah "kredensial" (termasuk cookie dan data Otentikasi HTTP) harus dikirim bersama permintaan.

Sangat sulit untuk memberikan solusi umum yang akan bekerja untuk semua server WWW karena pengaturan akan bervariasi tergantung pada server itu sendiri dan kata kerja HTTP yang ingin Anda dukung. Saya akan mendorong Anda untuk melupakan artikel yang sangat bagus ini ( http://www.html5rocks.com/en/tutorials/cors/ ) yang memiliki lebih banyak detail tentang header yang tepat yang perlu dikirim oleh server.

pkozlowski.opensource
sumber
23
@matsko Bisakah Anda menguraikan apa yang Anda lakukan untuk menyelesaikan ini? Saya menghadapi masalah yang sama dengan mana permintaan AngularJS $resource POST menghasilkan permintaan OPSI ke server backJS Expressend saya (pada host yang sama; tetapi port yang berbeda).
dbau
6
Untuk semua pemilih bawah - hampir tidak mungkin untuk menyediakan pengaturan konfigurasi yang tepat untuk semua server web di luar sana - jawabannya akan membutuhkan 10 halaman dalam kasus ini. Alih-alih, saya menautkan ke artikel yang memberikan lebih banyak detail.
pkozlowski.opensource
5
Anda benar bahwa Anda tidak dapat meresepkan jawaban - Anda harus menambahkan tajuk ke respons OPSI Anda yang mencakup semua tajuk yang diminta browser, dalam kasus saya, menggunakan chrome itu tajuk 'terima' dan 'x -requested-with '. Di chrome, saya mencari tahu apa yang perlu saya tambahkan dengan melihat permintaan jaringan dan melihat apa yang diminta chrome. Saya menggunakan nodejs / expressjs sebagai yang didukung sehingga saya membuat layanan yang mengembalikan respons terhadap permintaan OPSI yang mencakup semua header yang diperlukan. -1 karena saya tidak bisa menggunakan jawaban ini untuk mencari tahu apa yang harus dilakukan, harus mencari tahu sendiri.
Ed Sykes
2
Saya tahu ini 2+ tahun kemudian, tapi ... OP merujuk pada permintaan GET beberapa kali (penekanan ditambahkan oleh saya): «[...] permintaan yang dikirim ke file aset adalah permintaan PILIHAN ( seharusnya GET permintaan ). » dan «browser mencoba mencari tahu apakah" diizinkan "untuk mengunduh aset terlebih dahulu sebelum melakukan permintaan GET ». Bagaimana mungkin itu bukan bug AngularJS? Tidakkah permintaan preflight dilarang untuk DAPATKAN?
polettix
4
@polettix bahkan permintaan GET dapat memicu permintaan pra-penerbangan di browser jika header khusus digunakan. Periksa "permintaan yang tidak terlalu sederhana" di artikel yang saya tautkan : html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Sekali lagi, ini adalah mekanisme browser , bukan AngularJS.
pkozlowski.opensource
70

Untuk Angular 1.2.0rc1 + Anda perlu menambahkan resourceUrlWhitelist.

1.2: versi rilis mereka menambahkan fungsi escapeForRegexp sehingga Anda tidak perlu lagi melarikan diri dari string. Anda bisa langsung menambahkan url

'http://sub*.assets.example.com/**' 

pastikan untuk menambahkan ** untuk sub folder. Berikut adalah jsbin yang berfungsi untuk 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Jika Anda masih pada versi rc, Solusinya 1.2.0rc1 solusinya terlihat seperti:

.config(['$sceDelegateProvider', function($sceDelegateProvider) {
     $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?yourdomain.com/]);
 }])

Berikut adalah contoh jsbin di mana ia bekerja untuk 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pra 1.2: Untuk versi yang lebih lama (ref http://better-inter.net/enabling-cors-in-angular-js/ ) Anda perlu menambahkan 2 baris berikut ke konfigurasi Anda:

$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Berikut adalah contoh jsbin di mana ia berfungsi untuk versi pra 1.2: http://jsbin.com/olavok/11/edit

JStark
sumber
Ini berhasil bagi saya. Berikut ini adalah keajaiban yang sesuai untuk sisi server dengan nodejs / express: gist.github.com/dirkk0/5967221
dirkk0
14
Ini hanya berfungsi untuk permintaan GET, masih tidak dapat menemukan solusi untuk permintaan POST pada domain silang.
Pnct
2
Menggabungkan jawaban ini dengan user2304582 jawaban di atas akan berfungsi untuk permintaan POST. Anda harus memberi tahu server Anda untuk menerima permintaan POST dari server eksternal yang mengirimkannya
Charlie Martin
Ini sepertinya tidak akan bekerja sendiri untuk saya ... jadi a -1. Anda memerlukan sesuatu pada URL yang sama yang akan menanggapi permintaan OPSI sebagai bagian dari pemeriksaan pra-penerbangan. Bisakah Anda jelaskan mengapa ini bisa berhasil?
Ed Sykes
1
@ EdSykes, saya telah memperbarui jawaban untuk 1.2 dan menambahkan contoh jsbin yang berfungsi. Semoga itu bisa menyelesaikannya untuk Anda. Pastikan Anda menekan tombol "Run with JS".
JStark
62

CATATAN: Tidak yakin itu berfungsi dengan versi terbaru Angular.

ASLI:

Dimungkinkan juga untuk mengabaikan permintaan OPSI (hanya diuji di Chrome):

app.config(['$httpProvider', function ($httpProvider) {
  //Reset headers to avoid OPTIONS request (aka preflight)
  $httpProvider.defaults.headers.common = {};
  $httpProvider.defaults.headers.post = {};
  $httpProvider.defaults.headers.put = {};
  $httpProvider.defaults.headers.patch = {};
}]);
pengguna2899845
sumber
1
Bekerja dengan baik untuk saya dengan Chrome, FF & IE 10. IE 9 dan di bawahnya harus diuji secara native pada mesin Windows 7 atau XP.
DOM
3
Atau orang dapat mengatur ini pada metode $ resource itu sendiri dan tidak secara global:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r
3
Apakah ada masalah dengan ini? Tampaknya sangat sederhana, namun jawabannya sangat dalam di utasnya? sunting: Tidak berfungsi sama sekali.
Sebastialonso
Kemana kode ini pergi? di app.js, controller.js atau di mana tepatnya.
Murlidhar Fichadia
Kode ini tidak melakukan apa pun untuk mendapatkan permintaan. Saya menambahkan aturan terpisah untuk mendapatkan juga di config
kushalvm
34

Layanan Anda harus menjawab OPTIONSpermintaan dengan tajuk seperti ini:

Access-Control-Allow-Origin: [the same origin from the request]
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: [the same ACCESS-CONTROL-REQUEST-HEADERS from request]

Berikut ini adalah dokumen yang bagus: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

pengguna2304582
sumber
1
Untuk menguraikan bagian [kepala ACCESS-CONTROL-REQUEST-HEADER dari permintaan] yang sama, seperti yang saya sebutkan pada jawaban lain, Anda perlu melihat permintaan OPSI yang ditambahkan oleh browser Anda. Lalu, tambahkan tajuk tersebut pada layanan yang Anda buat (atau server web). Dalam expressjs yang tampak seperti: ejs.options (' ', function (request, response) {response.header ('Access-Control-Allow-Origin', ' '); response.header ('Access-Control-Allow-Methods ',' GET, PUT, POST, DELETE '); response.header (' Access-Control-Allow-Headers ',' Jenis-Konten, Otorisasi, terima, x-diminta-dengan '); response.send (); });
Ed Sykes
1
Komentar Ed Sykes sangat akurat, perlu diketahui bahwa tajuk yang dikirim pada respons PILIHAN dan yang dikirim pada respons POST harus persis sama, misalnya: Access-Control-Allow-Origin: * tidak sama dengan Access- Control-Allow-Origin: * karena spasi.
Lucia
20

Dokumen yang sama mengatakan

Tidak seperti permintaan sederhana (dibahas di atas), permintaan "preflighted" pertama-tama mengirim tajuk permintaan HTTP OPTIONS ke sumber daya di domain lain, untuk menentukan apakah permintaan sebenarnya aman untuk dikirim. Permintaan lintas-situs telah ditetapkan sebelumnya seperti ini karena mungkin memiliki implikasi pada data pengguna. Secara khusus, permintaan sudah dicetak sebelumnya jika:

Ini menggunakan metode selain GET atau POST. Juga, jika POST digunakan untuk mengirim data permintaan dengan Tipe-Konten selain dari aplikasi / x-www-form-urlencoded, multipart / form-data, atau teks / plain, misalnya jika permintaan POST mengirimkan payload XML ke server menggunakan aplikasi / xml atau teks / xml, maka permintaannya sudah disorot.

Itu mengatur header kustom dalam permintaan (mis. Permintaan menggunakan header seperti X-PINGOTHER)

Ketika permintaan asli Dapatkan tanpa header khusus, browser tidak boleh membuat permintaan Pilihan yang sekarang. Masalahnya adalah ia menghasilkan header X-Diminta-Dengan yang memaksa permintaan Opsi. Lihat https://github.com/angular/angular.js/pull/1454 tentang cara menghapus tajuk ini

Grum Ketema
sumber
10

Ini memperbaiki masalah saya:

$http.defaults.headers.post["Content-Type"] = "text/plain";
Cem Arguvanlı
sumber
10

Jika Anda menggunakan server nodeJS, Anda dapat menggunakan perpustakaan ini, itu berfungsi dengan baik untuk saya https://github.com/expressjs/cors

var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

dan setelah itu kamu bisa melakukan npm update.

Zakaria.dem
sumber
4

Inilah cara saya memperbaiki masalah ini pada ASP.NET

  • Pertama, Anda harus menambahkan paket nuget Microsoft.AspNet.WebApi.Cors

  • Kemudian modifikasi file App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Tambahkan atribut ini pada kelas controller Anda

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Saya bisa mengirim json ke aksi dengan cara ini

    $http({
            method: 'POST',
            data: JSON.stringify(data),
            url: 'actionurl',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8'
            }
        }).then(...)

Referensi: Mengaktifkan Permintaan Lintas-Asal di ASP.NET Web API 2

asfez
sumber
2

Entah bagaimana saya memperbaikinya dengan mengubah

<add name="Access-Control-Allow-Headers" 
     value="Origin, X-Requested-With, Content-Type, Accept, Authorization" 
     />

untuk

<add name="Access-Control-Allow-Headers" 
     value="Origin, Content-Type, Accept, Authorization" 
     />
Ved Prakash
sumber
0

Dijelaskan dengan sempurna dalam komentar pkozlowski. Saya punya solusi yang berfungsi dengan AngularJS 1.2.6 dan ASP.NET Web Api tetapi ketika saya telah memutakhirkan AngularJS ke 1.3.3 maka permintaan gagal.

  • Solusi untuk server Web Api adalah menambahkan penanganan permintaan OPSI di awal metode konfigurasi (info lebih lanjut di posting blog ini ):

    app.Use(async (context, next) =>
    {
        IOwinRequest req = context.Request;
        IOwinResponse res = context.Response;
        if (req.Path.StartsWithSegments(new PathString("/Token")))
        {
            var origin = req.Headers.Get("Origin");
            if (!string.IsNullOrEmpty(origin))
            {
                res.Headers.Set("Access-Control-Allow-Origin", origin);
            }
            if (req.Method == "OPTIONS")
            {
                res.StatusCode = 200;
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Methods", "GET", "POST");
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Headers", "authorization", "content-type");
                return;
            }
        }
        await next();
    });
Jiří Kavulák
sumber
Di atas tidak bekerja untuk saya. Ada solusi yang lebih sederhana untuk mengaktifkan CORS untuk digunakan dengan AngularJS dengan ASP.NET WEB API 2.2 dan yang lebih baru. Dapatkan paket Microsoft WebAPI CORS dari Nuget kemudian di file konfigurasi WebAPI Anda ... var cors = new EnableCorsAttribute ("www.example.com", " ", " "); config.EnableCors (cors); Detail ada di situs Microsoft di sini asp.net/web-api/overview/security/…
kmcnamee
0

Jika Anda menggunakan Jersey untuk API REST, Anda dapat melakukannya seperti di bawah ini

Anda tidak perlu mengubah implementasi layanan web Anda.

Saya akan menjelaskan untuk Jersey 2.x

1) Pertama-tama tambahkan ResponseFilter seperti yang ditunjukkan di bawah ini

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  }
}

2) kemudian di web.xml, dalam deklarasi servlet jersey tambahkan di bawah ini

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>
tidak mencolok
sumber
0

Saya menyerah berusaha memperbaiki masalah ini.

Web.config IIS saya memiliki " Access-Control-Allow-Methods" yang relevan di dalamnya, saya bereksperimen menambahkan pengaturan konfigurasi ke kode Angular saya, tetapi setelah membakar beberapa jam mencoba membuat Chrome memanggil layanan web JSON lintas-domain, saya menyerah dengan sedih.

Pada akhirnya, saya menambahkan bodoh ASP.Net handler halaman web, mendapat bahwa untuk memanggil layanan JSON web saya, dan mengembalikan hasil. Sudah berjalan dan dalam 2 menit.

Berikut kode yang saya gunakan:

public class LoadJSONData : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        string URL = "......";

        using (var client = new HttpClient())
        {
            // New code:
            client.BaseAddress = new Uri(URL);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("Authorization", "Basic AUTHORIZATION_STRING");

            HttpResponseMessage response = client.GetAsync(URL).Result;
            if (response.IsSuccessStatusCode)
            {
                var content = response.Content.ReadAsStringAsync().Result;
                context.Response.Write("Success: " + content);
            }
            else
            {
                context.Response.Write(response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

Dan di pengontrol sudut saya ...

$http.get("/Handlers/LoadJSONData.ashx")
   .success(function (data) {
      ....
   });

Saya yakin ada cara yang lebih sederhana / lebih umum untuk melakukan ini, tetapi hidup ini terlalu singkat ...

Ini bekerja untuk saya, dan saya bisa melanjutkan pekerjaan normal sekarang !!

Mike Gledhill
sumber
0

Untuk IIS MVC 5 / Angular CLI (Ya, saya sangat menyadari masalah Anda dengan Angular JS) proyek dengan API Saya melakukan hal berikut:

web.config di bawah <system.webServer>simpul

    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, atv2" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
    </httpProtocol>

global.asax.cs

protected void Application_BeginRequest() {
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") {
    Response.Flush();
    Response.End();
  }
}

Itu akan memperbaiki masalah Anda untuk MVC dan WebAPI tanpa harus melakukan yang lainnya. Saya kemudian membuat HttpInterceptor di proyek Angular CLI yang secara otomatis ditambahkan dalam informasi header yang relevan. Semoga ini bisa membantu seseorang keluar dalam situasi serupa.

Damon Drake
sumber
0

Sedikit terlambat ke pesta,

Jika Anda menggunakan Angular 7 (atau 5/6/7) dan PHP sebagai API dan masih mendapatkan kesalahan ini, coba tambahkan opsi tajuk berikut ke titik akhir (PHP API).

 header("Access-Control-Allow-Origin: *");
 header("Access-Control-Allow-Methods: PUT, GET, POST, PUT, OPTIONS, DELETE, PATCH");
 header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization");

Catatan : Yang dibutuhkan hanyalah Access-Control-Allow-Methods. Tapi, saya menempelkan di sini dua lainnya Access-Control-Allow-OrigindanAccess-Control-Allow-Headers , hanya karena Anda perlu semua ini diatur dengan benar agar Aplikasi Angular dapat berbicara dengan benar ke API Anda.

Semoga ini bisa membantu seseorang.

Bersulang.

Anjana Silva
sumber