初識Flutter web

級別:★☆☆☆☆
標簽:「Flutter web」「Dart Server」「blocked by CORS Policy」「跨域」
作者: WYW
審校: QiShare團隊

前言 筆者最近了解了Flutter web相關的內容,本文會分享創建Flutter web項目、Flutter web項目預覽,Flutter web項目和Flutter mobile(Flutter Android/iOS)項目的差別、搭建簡易Dart服務器(解決跨域問題)、上線Flutter web項目相關內容。

一、創建Flutter web 項目

準備Flutter web 環境

更新本地環境為 beta channel最新版。(dev channel 也可以)

flutter channel beta

flutter upgrade

Flutter有如下4個channel:

flutter channel
Flutter channels:
  beta
* dev
  master
  stable

Flutter 官方建議使用 stable 的channel。

master 是當前最新的channel;

dev 是當前最新的充分測試后的channel;

beta是每個月Flutter官方調整選出來的最好的dev的channel,并提升為beta channel;

stable是Flutter 認為是當前最穩定的channel。

穩定性而言:master < dev < beta < stable 。更多內容可查看:Flutter build release channels

開啟項目支持Flutter web

flutter config --enable-web

如果想在當前已有項目Flutter mobile項目的基礎上,添加Flutter web支持,可 cd 到 Flutter mobile 項目目錄下,添加Flutter web支持。

新建Flutter web項目

如果之前沒有創建過Flutter 項目,新建一個Flutter web項目可以使用如下命令。

flutter create 項目名(小寫) 如:flutter create qi_flutter_web_demo

現有項目生成Flutter web 相關文件

如果之前創建過Flutter 項目,想現有項目生成web文件夾及index.html等文件可使用如下命令。

flutter create .

Flutter web 新增index.html 等

運行項目命令:flutter run -d chrome

遇到問題:運行失敗

運行失敗報錯如下:

wangyongwangdeiMac:qi_flutter_page wangyongwang$ flutter run -d chrome

Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust

this source!

Downloading Web SDK... 1.1s

Launching lib/main.dart on Chrome in debug mode...

Error compiling dartdevc module:qi_flutter_page|lib/main_web_entrypoint.ddc.js

packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few positional arguments:

1 required, 0 given.

entrypoint.main();

       ^                                                             

AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js

Failed after 23.3s

Building application for the web... 33.5s

Failed to build application for the Web.

猜測原因:訪問網址https://storage.flutter-io.cn.不可達

起初,筆者猜測原因是這個網址https://storage.flutter-io.cn.訪問不可達;不過試過運行新創建的Flutter web項目,發現新建的Flutter web項目可以正常運行,可以排除問題不在于網址https://storage.flutter-io.cn.訪問不可達。

Document not found

繼續看這段報錯,可以發現Flutter web 項目的main 方法中不能有參數。

packages/qi_flutter_page/main_web_entrypoint.dart:9:18: Error: Too few
positional arguments: 1 required, 0 given.
entrypoint.main();
^

AssetNotFoundException: qi_flutter_page|lib/main_web_entrypoint.ddc.js
Failed after 22.9s

問題在于main方法中參數

運行Flutter web 項目的時候,main方法中不能有參數。

void main(List<String> args) {

}

// 刪除main方法名中的參數后,可以正常運行。

void main() {

}

筆者以之前寫的項目qi_flutter_page為例:運行起來的效果如下:

Flutter web 項目預覽
Flutter web 項目預覽

上周和同事CH聊天學到的內容:Flutter web項目顯示的網頁的特點:

顯示網頁源代碼的時候,可以網頁發現顯示的內容是html的body 中嵌套的main.dart.js。

顯示頁面源文件
頁面源文件

二、Flutter web 項目預覽

運行Flutter web項目 默認會在Chrome瀏覽器中顯示,不過在本機的Safari 瀏覽器中及模擬器中的瀏覽器中輸入相應的網址,也可以顯示相應的視圖。

Flutter web 項目預覽

三、Flutter web項目 與 Flutter mobile 項目的不同

筆者在把現有Flutter mobile項目,直接支持Flutter web 的過程中遇到了網絡請求報異常的問題,另外簡單測試了2個三方庫的在Flutter web項目中的體現。

HttpClient() 不能用于Flutter web 項目

try {
  HttpClient client = HttpClient();
} catch (e) {
  print('捕獲異常:$e');
}

捕獲異常:NoSuchMethodError: invalid member on null: 'indexOf'

Flutter web 項目的網絡請求可以使用html.httpRequest。

import 'dart:html' as html;

html.HttpRequest.request(url).then((responseValue) {
     
 });

三方庫支持情況

筆者這里舉2個自己使用過的2個三方庫,shared_preferences、url_launcher均支持 Flutter web 項目。

下列代碼對于Flutter web 項目中仍然支持打開加載url的窗口。

String soUrl = 'https://www.so.com';
if (await canLaunch(soUrl)) {
  await launch(soUrl);
}

由如下代碼及相應結果可知,shared_preferences 也支持 Flutter web 項目。

void _incrementCounter() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    int counter = (prefs.getInt('counter') ?? 0) + 1;
    print('Pressed $counter times.');
    await prefs.setInt('counter', counter);
  }

ListTile2
Pressed 1 times.
ListTile2
Pressed 2 times.
ListTile2
Pressed 3 times.
ListTile2
Pressed 4 times.
ListTile2
Pressed 5 times.

三方庫一般會注明支持的平臺(Android、iOS或Web)。
url_launcher 5.4.1支持 Flutter web 項目。
shared_preferences 支持 Flutter web 項目。
sqflite自己注明了支持Android和iOS,是否支持Flutter web沒有做說明。

如下圖所示:

url_launcher
shared_preferences
sqflite

四、簡易Dart服務器

使用如下代碼,可以本地啟動一個Dart服務。

main() async {
  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
  await for (var request in server) {
    request.response
      ..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
      ..write('Hello Dart! 你好Dart')
      ..close();
  }
 }

瀏覽器中直接請求http://127.0.0.1:9988 示意圖如下:

簡易Dart 服務器示意

筆者在Flutter web項目中請求,http://127.0.0.1:9988的時候,遇到了跨域問題,下邊分享下相關問題及處理方式。

跨域問題

跨域問題描述

跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被準許訪問來自不同源服務器上的指定的資源。當一個資源從與該資源本身所在的服務器不同的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。

比如,站點 http://domain-a.com 的某 HTML 頁面通過 的 src 請求 http://domain-b.com/image.jpg。網絡上的許多頁面都會加載來自不同域的CSS樣式表,圖像和腳本等資源。

出于安全原因,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味著使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。 引自HTTP訪問控制(CORS)

舉2個例子比如我們自身當前域名為abc.com, 訪問def.com 會出現跨域的問題。
比如我們自身當前域名為abc.com端口號為1234(abc.com:1234),那么訪問abc.com:5678也會出現跨域問題。

筆者使用Flutter web項目 請求服務端資源的時候遇到了跨域問題。

http://localhost:55355/#/ 中的內容訪問http://127.0.0.1:9988

Flutter web項目跨域現象圖現象圖如下:

Flutter web項目跨域問題

出現當前跨域問題的原因是端口號不同,訪問Flutter web 的url 和 請求服務端資源的url的 端口號 不同。

請求的響應頭中設置可跨域的origin,解決跨域問題

設置跨域的url 有2種設置方式:

  • 1.設置一個或多個url;
    • 如:request.response
      ..headers
      .add('Access-Control-Allow-Origin', request.headers['origin'])
  • 2.設置跨域的值為*;
    •   ..headers.add('Access-Control-Allow-Origin', '*')
      

注意:如果在本地測試使用,可以使用第二種方式,直接了當。但是一般線上的話最好使用第一種方式設置是否可以跨域請求。因為設置是否可以跨域,算是服務器在響應瀏覽器請求數據時的一種保護策略。

其中重點是在響應頭中添加可以跨域的請求域。

..headers.add('Access-Control-Allow-Origin', 'http://localhost:55355')

如'Access-Control-Allow-Origin'可以指定特定的url,使url能夠跨域請求。

如有需要指定允許多個url進行跨域請求。可以根據請求的origin的值,判斷是否要做跨域響應頭的處理。

如:如下代碼設置了當請求的origin 為http://localhost:63062http://localhost:55355 的時候,會添加跨域處理的響應頭。

main() async {
  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);

  var server = await HttpServer.bind(InternetAddress.loopbackIPv4, 9988);
  await for (var request in server) {
    var accessControlAllowOrigin = [
      'http://localhost:63062',
      'http://localhost:55355'
    ];
    if (request.headers['origin'] != null) {
      for (String tempAllowOrigin in accessControlAllowOrigin) {
        if (request.headers['origin'].first.contains(tempAllowOrigin)) {
          request.response
            ..headers
                .add('Access-Control-Allow-Origin', request.headers['origin'])
            // ..headers.add('Access-Control-Allow-Origin', '*')
            ..headers.contentType =
                ContentType('text', 'plain', charset: 'utf-8')
            ..write('Hello Dart! 你好Dart 跨域')
            ..close();
        }
      }
    } else {
      request.response
        ..headers.contentType = ContentType('text', 'plain', charset: 'utf-8')
        ..write('Hello Dart! 你好Dart 不需要跨域')
        ..close();
    }
  }
 }
解決跨域問題

筆者在上邊說明了處理運行Flutter web項目的時候,處理本地服務端接口和Flutter web項目運行網址出現跨域問題的處理方式。(其實對于編譯后的Flutter web項目的產物直接放到自己的服務端項目的的靜態文件目錄下的時候,不會出現上述問題)
有時候我們的請求內容可能就需要跨域去請求數據,而且對方如果也不便添加相應的跨域響應頭。此時,可使用Nginx 做反向代理來處理跨域問題。

Nginx 反向代理解決遠端跨域問題
server {
        listen       9080;
        server_name  localhost;
        
        location ~ /columns/Qtest {
            proxy_pass https://testerhome.com;
        }
    }

經上述處理,可以在本地的127.0.0.1:9080/columns/Qtest請求到 Qtest測試之道https://testerhome.com/columns/Qtest 相應數據。

本地Flutter web 項目跨域訪問TesterHome Qtest測試之道

Nginx 配置反向代理及rewrite訪問路徑可實現訪問遠端文件不跨域。

        location / {
           proxy_pass https://weekly.75team.com;
        }
        
        location ~ /api/qiwuzhoukanWeb {
            rewrite /api/qiwuzhoukanWeb /;
            proxy_pass https://weekly.75team.com;
        }

經上述處理,可以在本地的127.0.0.1:9080/api/qiwuzhoukanWeb請求到 奇舞周刊https://weekly.75team.com 相應數據。

本地Flutter web 項目跨域訪問奇舞周刊

五、Flutter web 項目上線

flutter build web會在項目的build 目錄中生成相應的資源文件及html 和js文件,把相關文件放置到服務端靜態文件目錄下即可。實現上線Flutter web項目。

Flutter web項目編譯產物

參考學習網址


推薦文章:
用AdHoc來測試iOS線上推送
Swift 5.1 (9) - 結構體和類
Swift 實現一個兼容iOS、tvOS、OSX的抽象層
iOS Password AutoFill
iOS 給UILabel添加點擊事件
用SwiftUI給視圖添加動畫
用SwiftUI寫一個簡單頁面
Swift 5.1 (8) - 枚舉類型
iOS App啟動優化(三)—— 自己做一個工具監控App的啟動耗時
iOS App啟動優化(二)—— 使用“Time Profiler”工具監控App的啟動耗時
iOS App啟動優化(一)—— 了解App的啟動流程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374