級別:★☆☆☆☆
標簽:「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 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.訪問不可達。
繼續看這段報錯,可以發現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為例:運行起來的效果如下:
上周和同事CH聊天學到的內容:Flutter web項目顯示的網頁的特點:
顯示網頁源代碼的時候,可以網頁發現顯示的內容是html的body 中嵌套的main.dart.js。
二、Flutter web 項目預覽
運行Flutter web項目 默認會在Chrome瀏覽器中顯示,不過在本機的Safari 瀏覽器中及模擬器中的瀏覽器中輸入相應的網址,也可以顯示相應的視圖。
三、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沒有做說明。
如下圖所示:
四、簡易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 示意圖如下:
筆者在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 的url 和 請求服務端資源的url的 端口號
不同。
請求的響應頭中設置可跨域的origin,解決跨域問題
設置跨域的url 有2種設置方式:
- 1.設置一個或多個url;
- 如:request.response
..headers
.add('Access-Control-Allow-Origin', request.headers['origin'])
- 如:request.response
- 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:63062
或 http://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 相應數據。
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 build web
會在項目的build 目錄中生成相應的資源文件及html 和js文件,把相關文件放置到服務端靜態文件目錄下即可。實現上線Flutter web項目。
參考學習網址
- Flutter build release channels
- 使用 Flutter 構建 Web 應用
- HTTP訪問控制(CORS)
- 奇舞周刊
- Qtest 測試之道
- 詳解 CORS 跨域資源共享
- 跨域資源共享 CORS 詳解
- proxy_pass url 反向代理的坑
推薦文章:
用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的啟動流程