有了前端畫面跟後端API。
最終的目的當然是要透過前端UI來呼叫後端的API,然後拿取得的API Result來做其他的應用。
而不是只用Postman或是瀏覽器來執行執行API網址這樣而已。
這時一定會遇到CORS的問題
前端的部分,我們做一個按鈕,按下後會顯示API Result。

//呼叫後端API的 Typescript function
qq(): Observable<any>
{
const url = `http://遠端ip/ApiSite1/api/WeatherForecast/Get`;
const options = this.generatePostOptions();
return this.httpClient.get<any>(url, options)
}
不意外的,一點下去就出現了CORS的問題。

什麼是CORS(Cross-Origin Resource Sharing,跨來源資源共用)
只要是不符合以下三點,那這兩個站台之間就是不符合同源政策 (same-origin policy):
1. 相同的通訊協定 (protocol),即 http/https
2. 相同的網域 (domain)
3. 相同的通訊埠 (port)
以我們這次的範例來說。我們要透過 http://localhost:4200 來呼叫位於遠端http://遠端ip/ApiSite1/api/WeatherForecast/Get的API。依照下表來看,這兩個站台之間是不符合同源政策的。
前端站台 | 後端API站台 | |
通訊協定 (protocol) | http | http |
網域 (domain) | localhost | 遠端ip |
通訊埠 (port) | 4200 | 80 |
要讓不符合同源政策的兩個站台可以透過Javascript(ex:AJAX…)取得非同源的資源時,就要透過CORS。
後端如何處理CORS
省略...
builder.Services.AddCors(options =>
{
options.AddPolicy("allowCors",
builder =>
{
builder
//允許http://localhost:4200取得資源
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});
省略...
app.UseCors("allowCors");
省略...
我們必加上.WithOrigins("http://localhost:4200")
,告訴後端Server,我們允許"http://localhost:4200"
來跟我們請求資源。
修改完後端程式後,記得要重新發佈到Ubuntu Server上,然後重啟ApiSite1.service
sudo systemctl stop ApiSite1.service
sudo systemctl start ApiSite1.service
local前端呼叫遠端API測試結果:
成功取得API Result了!


遠端前端呼叫遠端API測試結果:
成功取得API Result了!
由於前端專案跟後端API的程式都是放在同一台Server上,兩個站台的通訊協定(http). IP(Domain). port號(80)都相同,有符合同源正確,就不會出現CORS問題了


呼叫遠端的Open API
這時候又有個疑問了,如果我們的Angular要呼叫的API不是在我們自己架設的Server上的話,那我們要怎們在Server端程式碼加上.WithOrigins("http://localhost:4200")
。我們先用下面這個查詢桃園公車資訊的Open API來做測試:

qq(): Observable<any>
{
const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
const options = this.generatePostOptions();
return this.httpClient.get<any>(url, options)
}
不意外的,一點下去就出現了CORS的問題。Request Header的Host跟Origin(來源)很明顯的不符合同源。



伺服器又不是我們自己維護的,我們要怎樣讓對方能允許我們的資源請求呢?我們先在Angular的src/底下建立proxy.conf.json設定檔。target
:是我們目標API的網址"/"
:是我們要鏡像的網址 Prefix,所有連到 http://localhost:4200/xxxx 的網址,都會自動轉發到 http://data.taipei/xxxx,以此類推!
{
"/OPD-io": {
"target": "http://apidata.tycg.gov.tw",
"secure": false,
"changeOrigin": true
}
}
修改angular.json。讓Angular啟動時知道我們設定的代理參數
"architect": {
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "your-application-name:build",
"proxyConfig": "src/proxy.conf.json"
}
}
}
由於我們已經有設定代理了,要記得把API網址前面Domain的部分拿掉。
qq(): Observable<any>
{
//const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
const url = `/OPD-io/bus4/GetRoute.json`;
const options = this.generatePostOptions();
return this.httpClient.get<any>(url, options)
}
設定好代理檔後,記得ng b & ng s
ng build
ng serve
成功取得API Result了!
可以看到原本的遠端Host是 apidata.tycg.com.gov.tw,被代理成有符合同源政策 (same-origin policy)的 http:localhost:4200了。
呼叫的遠端API網址也變成了 http://localhost:4200/OPD-io/bus4/GetRoute.json了(但實際上真的呼叫的API還是 http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json)。



另外補充
如果我們沒有使用proxy.conf.json,也沒有在要呼叫的API URL前面加上Domain的話,預設的遠端Host會是本機站台喔!
有加proxy.conf.json跟沒加上proxy.conf.json的結果可以跟上一個步驟比較看看
註解掉proxyConfig:
"options": {
"browserTarget": "your-application-name:build",
//"proxyConfig": "src/proxy.conf.json"
},
API URL前面拿掉Domain
qq(): Observable<any>
{
//const url = `http://apidata.tycg.gov.tw/OPD-io/bus4/GetRoute.json`;
const url = `/OPD-io/bus4/GetRoute.json`;
const options = this.generatePostOptions();
return this.httpClient.get<any>(url, options)
}
設定好代理檔後,記得ng b & ng s
ng build
ng serve


Ref:
。Access-Control-Allow-Origin
。[教學] 深入了解 CORS (跨來源資源共用): 如何正確設定 CORS?
。.NET API CORS: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header.NET API
。如何在 Angular CLI 建立 proxy.config.json 轉發呼叫遠端 RESTful APIs
。Angular 中 如何修复跨域 (CORS) 问题
。Angular 入門教學 - 建立proxy.conf.json設定,使得能在開發中讀取遠端API
。Angular官方教學 - 代理到後端伺服器