AngularJS thực hiện một yêu cầu HTTP TÙY CHỌN cho tài nguyên nguồn gốc chéo


264

Tôi đang cố gắng thiết lập AngularJS để liên lạc với tài nguyên nguồn gốc chéo, nơi máy chủ tài sản cung cấp các tệp mẫu của tôi nằm trên một miền khác và do đó, yêu cầu XHR mà các góc thực hiện phải là miền chéo. Tôi đã thêm tiêu đề CORS thích hợp vào máy chủ của mình cho yêu cầu HTTP để thực hiện công việc này, nhưng dường như nó không hoạt động. Vấn đề là khi tôi kiểm tra các yêu cầu HTTP trong trình duyệt của mình (chrome), yêu cầu được gửi đến tệp nội dung là một yêu cầu TÙY CHỌN (nó phải là một yêu cầu NHẬN).

Tôi không chắc liệu đây có phải là lỗi trong AngularJS hay không nếu tôi cần cấu hình thứ gì đó. Từ những gì tôi hiểu, trình bao bọc XHR không thể thực hiện yêu cầu HTTP TÙY CHỌN để có vẻ như trình duyệt đang cố gắng tìm hiểu xem có được "tải xuống" tài sản trước khi thực hiện yêu cầu GET không. Nếu đây là trường hợp, thì tôi có cần phải đặt tiêu đề CORS (Access-Control-Allow-Origin: http://asset.host ... ) Với máy chủ tài sản không?

Câu trả lời:


226

Yêu cầu TÙY CHỌN không có nghĩa là lỗi AngularJS, đây là cách tiêu chuẩn Chia sẻ tài nguyên chéo bắt buộc các trình duyệt hành xử. Vui lòng tham khảo tài liệu này: https://developer.mozilla.org/en-US/docs/HTTP_access_control , trong phần "Tổng quan" có ghi:

Tiêu chuẩn chia sẻ tài nguyên nguồn gốc hoạt động bằng cách thêm các tiêu đề HTTP mới cho phép các máy chủ mô tả tập hợp các nguồn gốc được phép đọc thông tin đó bằng trình duyệt web. Ngoài ra, đối với các phương thức yêu cầu HTTP có thể gây ra tác dụng phụ đối với dữ liệu người dùng (cụ thể là đối với các phương thức HTTP khác với GET hoặc sử dụng POST với các loại MIME nhất định). Đặc tả bắt buộc các trình duyệt "đánh dấu trước" yêu cầu, thu hút các phương thức được hỗ trợ từ máy chủ với tiêu đề yêu cầu HTTP TÙY CHỌN và sau đó, "phê duyệt" từ máy chủ, gửi yêu cầu thực tế với phương thức yêu cầu HTTP thực tế. Máy chủ cũng có thể thông báo cho khách hàng xem "thông tin đăng nhập" (bao gồm dữ liệu Xác thực cookie và HTTP) có nên được gửi cùng với yêu cầu hay không.

Rất khó để cung cấp một giải pháp chung có thể hoạt động cho tất cả các máy chủ WWW vì thiết lập sẽ thay đổi tùy thuộc vào chính máy chủ và động từ HTTP mà bạn dự định hỗ trợ. Tôi sẽ khuyến khích bạn vượt qua bài viết tuyệt vời này ( http://www.html5rocks.com/en/tutorials/cors/ ) có nhiều chi tiết hơn về các tiêu đề chính xác cần được gửi bởi máy chủ.


23
@matsko Bạn có thể giải thích những gì bạn đã làm để giải quyết vấn đề này không? Tôi đang đối mặt với cùng một vấn đề, theo đó một yêu cầu $resource POST của AngularJS đang tạo một yêu cầu TÙY CHỌN đến máy chủ ExpressJS phụ trợ của tôi (trên cùng một máy chủ; nhưng một cổng khác).
dbau

6
Đối với tất cả những người bỏ phiếu - bên cạnh việc không thể cung cấp thiết lập cấu hình chính xác cho tất cả các máy chủ web ngoài đó - câu trả lời sẽ mất 10 trang trong trường hợp này. Thay vào đó tôi đã liên kết đến một bài viết cung cấp thêm chi tiết.
pkozlowski.opensource

5
Bạn nói đúng rằng bạn không thể đưa ra câu trả lời - bạn sẽ cần thêm tiêu đề vào phản hồi TÙY CHỌN của mình bao gồm tất cả các tiêu đề mà trình duyệt yêu cầu, trong trường hợp của tôi, sử dụng chrome, đó là tiêu đề 'chấp nhận' và 'x -requested-with '. Trong chrome, tôi đã tìm ra những gì tôi cần thêm bằng cách xem yêu cầu mạng và xem chrome yêu cầu gì. Tôi đang sử dụng nodejs / expressjs làm hỗ trợ nên tôi đã tạo một dịch vụ trả về phản hồi cho yêu cầu TÙY CHỌN bao gồm tất cả các tiêu đề cần thiết. -1 bởi vì tôi không thể sử dụng câu trả lời này để tìm ra phải làm gì, phải tự mình tìm ra nó.
Ed Sykes

2
Tôi biết rằng đã hơn 2 năm sau, nhưng ... OP đề cập đến các yêu cầu GET nhiều lần (tôi nhấn mạnh thêm): «[...] yêu cầu được gửi đến tệp tài sản là một yêu cầu TÙY CHỌN ( nên là một yêu cầu NHẬN yêu cầu ). » và «trình duyệt đang cố gắng tìm hiểu xem có được" tải xuống "tài sản trước hay không trước khi nó thực hiện yêu cầu GET ». Làm thế nào nó không thể là một lỗi AngularJS? Không nên cấm các yêu cầu preflight cho GET?
Polettix

4
@polettix thậm chí yêu cầu GET có thể kích hoạt yêu cầu trước chuyến bay trong trình duyệt nếu tiêu đề tùy chỉnh được sử dụng. Kiểm tra "các yêu cầu không đơn giản" trong bài viết tôi đã liên kết: html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request . Một lần nữa, đó là cơ chế của trình duyệt , không phải AngularJS.
pkozlowski.opensource

70

Đối với Angular 1.2.0rc1 +, bạn cần thêm resourceUrlWhlistist.

1.2: phiên bản phát hành họ đã thêm chức năng escForRegapi để bạn không còn phải thoát chuỗi. Bạn chỉ có thể thêm url trực tiếp

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

đảm bảo thêm ** cho các thư mục phụ. Đây là một jsbin hoạt động cho 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: Nếu bạn vẫn đang ở phiên bản RC, Angular 1.2.0rc1, giải pháp sẽ như sau:

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

Dưới đây là ví dụ jsbin nơi nó hoạt động cho 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pre 1.2: Đối với các phiên bản cũ hơn (ref http://better-inter.net/eneac-cors-in-angular-js/ ), bạn cần thêm 2 dòng sau vào cấu hình của mình:

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

Dưới đây là ví dụ jsbin nơi nó hoạt động cho các phiên bản 1.2 trước: http://jsbin.com/olavok/11/edit


Điều này làm việc cho tôi để. Đây là phép thuật thích hợp cho phía máy chủ với nodejs / express: gist.github.com/dirkk0/5967221
dirkk0

14
Điều này chỉ hoạt động cho yêu cầu GET, vẫn không thể tìm thấy giải pháp cho yêu cầu POST trên tên miền chéo.
Pnct

2
Kết hợp câu trả lời này với câu trả lời user2304582 ở trên sẽ hoạt động cho các yêu cầu POST. Bạn phải yêu cầu máy chủ của mình chấp nhận các yêu cầu POST từ máy chủ bên ngoài đã gửi nó
Charlie Martin

Điều này không có vẻ như nó sẽ tự hoạt động với tôi ... vì vậy -1. Bạn cần một cái gì đó trên cùng một URL sẽ đáp ứng yêu cầu TÙY CHỌN như là một phần của kiểm tra trước chuyến bay. Bạn có thể giải thích tại sao điều này sẽ làm việc?
Ed Sykes

1
@EdSykes, tôi đã cập nhật câu trả lời cho 1.2 và thêm một ví dụ jsbin đang hoạt động. Hy vọng rằng sẽ giải quyết nó cho bạn. Hãy chắc chắn rằng bạn nhấn nút "Chạy với JS".
JStark

62

LƯU Ý: Không chắc chắn nó hoạt động với phiên bản Angular mới nhất.

NGUYÊN:

Cũng có thể ghi đè yêu cầu TÙY CHỌN (chỉ được thử nghiệm trong 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 = {};
}]);

1
Hoạt động tốt với tôi với Chrome, FF & IE 10. IE 9 trở xuống nên được kiểm tra nguyên bản trên các máy Windows 7 hoặc XP.
DOM

3
Ngoài ra, người ta có thể đặt riêng phương thức này cho phương thức $ resource đó chứ không phải trên toàn cầu:$resource('http://yourserver/yourentity/:id', {}, {query: {method: 'GET'});
h7r

3
Có bất kỳ bắt với điều này? Có vẻ như rất đơn giản, và câu trả lời là rất sâu trong chủ đề? chỉnh sửa: Hoàn toàn không hoạt động.
Sebastialonso

Mã này đi đâu? trong app.js, control.js hoặc nơi chính xác.
Murlidhar Fichadia

Mã này không làm gì cho một yêu cầu nhận. Tôi đã thêm quy tắc riêng cho get cũng trong cấu hình của mình
kushalvm

34

Dịch vụ của bạn phải trả lời một OPTIONSyêu cầu với các tiêu đề như sau:

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]

Đây là một tài liệu tốt: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server


1
Để giải thích chi tiết về phần [cùng TIẾP CẬN-KIỂM SOÁT-YÊU CẦU từ yêu cầu], như tôi đã đề cập trên một câu trả lời khác, bạn cần xem yêu cầu TÙY CHỌN mà trình duyệt của bạn đang thêm. Sau đó, thêm các tiêu đề đó vào dịch vụ mà bạn đang xây dựng (hoặc máy chủ web). Trong expressjs trông giống như: ejs.options (' ', chức năng (yêu cầu, phản hồi) { answer.header (' Access-Control-Allow-Origin', ' '); answer.header ('Access-Control-allow-Phương thức ',' GET, PUT, POST, DELETE '); answer.header (' Access-Control-allow-Headers ',' Content-Type, Authorization, accept, x-request-with '); answer.send (); });
Ed Sykes

1
Nhận xét của Ed Sykes rất chính xác, lưu ý rằng các tiêu đề được gửi trên phản hồi TÙY CHỌN và các tiêu đề được gửi trên phản hồi POST phải giống hệt nhau, ví dụ: Access-Control-Allow-Origin: * không giống như Access- Control-Allow-Origin: * vì khoảng trắng.
Lucia

20

Tài liệu tương tự nói

Không giống như các yêu cầu đơn giản (đã thảo luận ở trên), trước tiên, các yêu cầu "được chiếu trước" gửi tiêu đề yêu cầu HTTP TÙY CHỌN đến tài nguyên trên miền khác, để xác định xem yêu cầu thực tế có an toàn để gửi hay không. Các yêu cầu trên nhiều trang web được chiếu trước như thế này vì chúng có thể có liên quan đến dữ liệu người dùng. Cụ thể, một yêu cầu được nêu trước nếu:

Nó sử dụng các phương thức khác ngoài GET hoặc POST. Ngoài ra, nếu POST được sử dụng để gửi dữ liệu yêu cầu với Loại nội dung không phải là application / x-www-form-urlencoding, Multipart / form-data hoặc text / plain, ví dụ: nếu yêu cầu POST gửi tải trọng XML đến máy chủ sử dụng application / xml hoặc text / xml, sau đó yêu cầu được chiếu trước.

Nó đặt các tiêu đề tùy chỉnh trong yêu cầu (ví dụ: yêu cầu sử dụng một tiêu đề như X-PINGOTHER)

Khi yêu cầu ban đầu là Nhận mà không có tiêu đề tùy chỉnh, trình duyệt sẽ không thực hiện tùy chọn yêu cầu như hiện tại. Vấn đề là nó tạo ra một tiêu đề X-Requested-With buộc yêu cầu Tùy chọn. Xem https://github.com/angular/angular.js/pull/1454 về cách xóa tiêu đề này


10

Điều này đã khắc phục vấn đề của tôi:

$http.defaults.headers.post["Content-Type"] = "text/plain";

10

Nếu bạn đang sử dụng máy chủ nodeJS, bạn có thể sử dụng thư viện này, nó hoạt động tốt với tôi https://github.com/expressjs/cors

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

app.use(cors());

và sau khi bạn có thể làm một npm update.


4

Đây là cách tôi khắc phục vấn đề này trên ASP.NET

  • Trước tiên, bạn nên thêm gói nuget Microsoft.AspNet.WebApi.Cors

  • Sau đó sửa đổi tệp App_Start \ WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
  • Thêm thuộc tính này vào lớp trình điều khiển của bạn

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
  • Tôi đã có thể gửi json đến hành động bằng cách này

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

Tham khảo: Kích hoạt yêu cầu nguồn gốc chéo trong ASP.NET Web API 2


2

Bằng cách nào đó tôi đã sửa nó bằng cách thay đổi

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

đến

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

0

Mô tả hoàn hảo trong bình luận của pkozlowski. Tôi đã có giải pháp làm việc với AngularJS 1.2.6 và ASP.NET Web Api nhưng khi tôi đã nâng cấp AngularJS lên 1.3.3 thì yêu cầu không thành công.

  • Giải pháp cho máy chủ Api Web là thêm xử lý các yêu cầu TÙY CHỌN ở đầu phương thức cấu hình (thông tin thêm trong bài đăng trên blog này ):

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

Ở trên không làm việc cho tôi. Có một giải pháp đơn giản hơn nhiều để cho phép CORS sử dụng với AngularJS với ASP.NET WEB API 2.2 trở lên. Nhận gói Microsoft WebAPI CORS từ Nuget sau đó trong tệp cấu hình WebAPI của bạn ... var cors = new EnableCorsAttribution ("www.example.com", " ", " "); config.EnableCors (cors); Cụ thể trên Microsoft trang web ở đây asp.net/web-api/overview/security/...
kmcnamee

0

Nếu bạn đang sử dụng Jersey cho REST API, bạn có thể làm như dưới đây

Bạn không phải thay đổi triển khai dịch vụ web của mình.

Tôi sẽ giải thích cho Jersey 2.x

1) Trước tiên, thêm Bộ lọc Phản hồi như hiển thị bên dưới

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) sau đó trong tệp web.xml, trong khai báo servlet jersey, thêm vào bên dưới

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

0

Tôi đã từ bỏ việc cố gắng khắc phục vấn đề này.

IIS web.config của tôi có liên quan "Access-Control-Allow-Methods , tôi đã thử nghiệm thêm cài đặt cấu hình vào mã Angular của mình, nhưng sau khi đốt vài giờ cố gắng để Chrome gọi một dịch vụ web JSON tên miền chéo, tôi đã bỏ cuộc một cách thảm hại.

Cuối cùng, tôi đã thêm một trang web xử lý ASP.Net ngu ngốc, nhận được rằng để gọi dịch vụ web JSON của tôi và trả về kết quả. Nó đã lên và chạy trong 2 phút.

Đây là mã tôi đã sử dụng:

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

Và trong bộ điều khiển góc của tôi ...

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

Tôi chắc chắn có một cách đơn giản / chung chung hơn để làm điều này, nhưng cuộc sống quá ngắn ...

Điều này làm việc cho tôi, và bây giờ tôi có thể tiếp tục làm việc bình thường !!


0

Đối với dự án IIS MVC 5 / Angular CLI (Có, tôi nhận thức rõ vấn đề của bạn là với Angular JS) với API tôi đã làm như sau:

web.config dưới <system.webServer>nút

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

toàn cầu.asax.cs

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

Điều đó sẽ khắc phục các sự cố của bạn cho cả MVC và WebAPI mà không phải thực hiện tất cả các hoạt động khác. Sau đó, tôi đã tạo ra một HTTPInterceptor trong dự án Angular CLI tự động thêm vào thông tin tiêu đề có liên quan. Hy vọng điều này sẽ giúp ai đó trong một tình huống tương tự.


0

Ít đến bữa tiệc,

Nếu bạn đang sử dụng Angular 7 (hoặc 5/6/7) và PHP làm API và vẫn gặp lỗi này, hãy thử thêm các tùy chọn tiêu đề sau vào điểm cuối (API PHP).

 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");

Lưu ý : Những gì chỉ yêu cầu là Access-Control-Allow-Methods. Nhưng, tôi đang dán ở đây hai cái khác Access-Control-Allow-OriginAccess-Control-Allow-Headers, đơn giản vì bạn sẽ cần tất cả những thứ này được đặt đúng để Ứng dụng Angular nói chuyện đúng với API của bạn.

Hy vọng điều này sẽ giúp được ai đó.

Chúc mừng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.