Flutter2 for Web 跨域問題

oldbirds發表於2021-03-20

上篇 Flutter2 for Web,寫了個部落格站點,已上線,我們簡單的封裝了下網路請求。我們很愉快的將專案執行到Chrome 上,但是遲遲未見資料,通過開發者工具,報如下錯誤:

-w852

這是一個跨域報錯。

我們的網頁地址是 http://localhost:62924,然後頁面有傳送個請求,請求的介面域名是 d6579fc5-c18b-443b-a2ef-01c2b6be51d5.bspapp.com, 那麼他倆的域名不一致,出現跨域。受到了同源策略的保護因此無法進行資料互動。

什麼是跨域

協議、域名、埠全部相同才算同一域下,三個條件有一個不一致,都不算同域,既跨域。

即使是我們自己的域名伺服器,而二級域名或三級域名不一致,也會出現跨域,如:http://img.loveli.sitehttp://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 專案

    -w299

    • 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());
  }
複製程式碼

重新執行我們的專案:

-w1856

資料請求成功了!

總結

跨域,在開發中是非常容易遇到的。解決方案,由早期通過 JSONP 和同域代理(當然還有其他的方法)的方案,但是沒有根本解決問題。比較合理的做法是通過伺服器實現 CORS。

我們沒有給線上伺服器配置 CORS,直接建立本地伺服器且配置了 CORS,轉發請求到線上,起到一箇中轉的作用。算是自給自足吧!

參閱

加入 Flutter 微信交流群,請關注微信公眾號:OldBirds

相關文章