上篇 Flutter2 for Web,寫了個部落格站點,已上線,我們簡單的封裝了下網路請求。我們很愉快的將專案執行到Chrome 上,但是遲遲未見資料,通過開發者工具,報如下錯誤:
這是一個跨域報錯。
我們的網頁地址是 http://localhost:62924
,然後頁面有傳送個請求,請求的介面域名是
d6579fc5-c18b-443b-a2ef-01c2b6be51d5.bspapp.com
, 那麼他倆的域名不一致,出現跨域。受到了同源策略的保護因此無法進行資料互動。
什麼是跨域
協議、域名、埠全部相同才算同一域下,三個條件有一個不一致,都不算同域,既跨域。
即使是我們自己的域名伺服器,而二級域名或三級域名不一致,也會出現跨域,如:http://img.loveli.site
與 http://blog.loveli.site
之間需要資料互動,就跨域了。
由於現在所有的瀏覽器都實行了同源策略。
同源策略由 Netscape 公司在 1995 年引入瀏覽器:在傳送 Ajax 請求時,只接收同域伺服器響應的資料資源。
同源政策的目的,是為了保證使用者資訊的安全,防止惡意的網站竊取資料。
跨域的解決方案
如何能夠使兩個不同域下的資料進行順利互動?早期最為典型方案有:
- JSONP
- 同域代理
JSONP 是利用了 script 標籤的 src 屬性來實現跨域資料互動的,因為瀏覽器解析 HTML 程式碼時,原生具有 src 屬性的標籤,瀏覽器都賦予其 HTTP 請求的能力,而且不受跨域限制,使用 src 傳送 HTTP 請求,伺服器直接返回一段 JS 程式碼的函式呼叫,將伺服器資料放在函式實參中,前端提前寫好響應的函式準備回撥,接收資料,實現跨域資料互動;
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
複製程式碼
同域代理就是使用 Ajax 向同域下的後臺傳送請求,同時攜帶真實請求的地址及引數,後臺接受請求後直接根據地址及引數 轉發請求,因為後臺是可以直接模擬 HTTP 客戶端傳送請求的,所以沒有跨域問題,而後臺接受到響應資料後再原樣返回給前端瀏覽器,從而實現跨域資料互動;
JSONP 和同域代理,本質上並沒有解決 Ajax 跨域的問題,只是繞開這個問題而另闢蹊徑實現的跨域資料互動,在資料互動層面上可以看做技術不成熟時的臨時解決方案;但是 JSONP 和同域代理使用了很多年,當然跨域問題也存在了很多年,終於有人看不下去了,提出了瀏覽器與伺服器跨域通訊的安全性通訊策略————跨域資源共享,簡稱 CORS;
CORS(線上解決方案)
CORS 是跨源資源分享(Cross-Origin Resource Sharing)的縮寫。它是W3C標準,是跨源 AJAX 請求的根本解決方法。
CORS 需要瀏覽器和伺服器同時支援。目前,所有瀏覽器都支援該功能。因此,實現 CORS 通訊的關鍵是伺服器。只要伺服器實現了 CORS 介面,就可以跨源通訊。
因本文的特殊性,我們不去修改伺服器,所以伺服器如何實現 CORS 就不過多闡述。如果想更多的瞭解 CORS,可閱讀:
請求轉發(開發場景)
因為我們不想去修改線上伺服器,那麼如何解決跨域問題?
其實也簡單,我們本地實現代理轉發伺服器,且配置 CORS。
那麼接下來,帶大家一起來實現
建立 cli 工程
如何建立一個 cli 應用?
-
第一步安裝 dart:
$ brew tap dart-lang/dart $ brew install dart 複製程式碼
-
建立 cli 命令列應用:
$ dart create -t console-full cli 複製程式碼
使用 dart create 命令,以 console-full 模板建立 cli 專案
bin/cli.dart
,該檔案包含一個頂層 main() 函式。該函式是你應用的入口lib/cli.dart
,包含一些功能性的函式方法,這些函式方法將會匯入到 bin/cli.dart 檔案中。pubspec.yaml
,包含應用的後設資料,包括應用依賴的 package 資訊以及所需的版本等,跟 Flutter 工程一樣
-
執行應用
$ cd cli $ dart run Hello world: 42! 複製程式碼
到這裡我們就完成了一個 cli 應用,接下來我們來實現代理伺服器。
代理伺服器
因為網頁我們現在是通過 localhost
進行訪問的,所以我們需要建立一個本地伺服器,對請求進行轉發。
我們搭建一個埠為 4500 的代理伺服器。
首先我們需要在 pubspec.yaml
引入 shelf_proxy
:
shelf_proxy: ^0.1.0+7
複製程式碼
然後在 bin/cli.dart
中實現:
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_proxy/shelf_proxy.dart';
void main(List<String> arguments) async {
var reqHandle = proxyHandler("https://d6579fc5-c18b-443b-a2ef-01c2b6be51d5.bspapp.com");
/// 繫結本地埠,4500,轉發到真正的伺服器中
var server = await shelf_io.serve(reqHandle, 'localhost', 4500);
// 這裡設定請求策略,允許所有
server.defaultResponseHeaders.add('Access-Control-Allow-Origin', '*');
server.defaultResponseHeaders.add('Access-Control-Allow-Credentials', true);
print('Serving at http://${server.address.host}:${server.port}');
}
複製程式碼
通過 shelf_proxy 很快就實現了個簡單的代理伺服器,再次執行
$ dart run
Serving at http://localhost:4500
複製程式碼
接下來,我們需要修改 web-demo
的程式碼,在 lib/config.dart
中:
/// 開發環境
class ConfigDebug extends Config {
@override
String get baseUrl => "http://localhost:4500/http/";
}
複製程式碼
在 ConfigDebug 中,將 baseUrl 設定為我們本地代理伺服器的地址。
然後在 locator.dart
,實現如果是 debug 環境就配置為 ConfigDebug :
// 配置專案環境
if (kDebugMode) {
locator.registerSingleton<Config>(ConfigDebug());
} else {
locator.registerSingleton<Config>(ConfigProduct());
}
複製程式碼
重新執行我們的專案:
資料請求成功了!
總結
跨域,在開發中是非常容易遇到的。解決方案,由早期通過 JSONP
和同域代理(當然還有其他的方法)的方案,但是沒有根本解決問題。比較合理的做法是通過伺服器實現 CORS。
我們沒有給線上伺服器配置 CORS,直接建立本地伺服器且配置了 CORS,轉發請求到線上,起到一箇中轉的作用。算是自給自足吧!
參閱
加入 Flutter 微信交流群,請關注微信公眾號:OldBirds