前言
Flutter端在處理網絡請求的時候,最常用的庫當然是Dio
了,那么在改造成web端的時候,最先處理的必然是網絡請求,否則沒有數據去處理驅動實圖渲染。
官方鏈接
- pub
https://pub.dev/packages/dio
- github
https://github.com/cfug/dio/blob/main/dio/README-ZH.md
適配器問題
這里根據官網的指引去看web相關的配置,發現只需要更改適配器HttpClientAdapter
即可,放出官方的截圖:
然后我想說,這就是一個坑!!!,配置上去運行起來是不可用的。但是我們后面再說這個問題。
我們為了兼容多平臺運行,必須使用條件編譯的方式進行引入:
這里我建立了三個文件:
ai_network_mobile_adapter.dart作為移動端的適配器:移動端是支持代理的
import 'package:dio/dio.dart';
import 'package:dio/io.dart';HttpClientAdapter getAdapter() {return IOHttpClientAdapter(// createHttpClient: () {// final client = HttpClient();// client.findProxy = (uri) {// return 'PROXY 192.168.11.26:8888';// };// client.badCertificateCallback =// (X509Certificate cert, String host, int port) => true; //忽略證書// return client;// },);
}
ai_network_web_adapter.dart:web端適配器
import 'package:dio/browser.dart';
import 'package:dio/dio.dart';HttpClientAdapter getAdapter() {final adapter = HttpClientAdapter() as BrowserHttpClientAdapter;adapter.withCredentials = true;return adapter;
}
再通過條件編譯文件去引入:
export 'ai_network_web_adapter.dart'if (dart.library.html) 'ai_network_web_adapter.dart'if (dart.library.io) 'ai_network_mobile_adapter.dart';
這樣我們只需要使用getAdapter
方法,條件編譯會自動幫我們選中不同端的適配器。
這里不能用官網的方式去寫,一定要這么寫,這樣才有效:
HttpClientAdapter getAdapter() {final adapter = HttpClientAdapter() as BrowserHttpClientAdapter;adapter.withCredentials = true;return adapter;
}
請求加解密
在移動端為了安全,必然會有請求上面的加解密,這里面涉及一些原生加解密和加解密相關的庫文件,但在web端很多庫不被支持,這里方式有很多,跟后端商量一下就可以,加特殊參數或者使用c的方式,這里不做過多贅述,不過也是時間問題和增加爆破成本。
跨域問題
跨域問題在瀏覽器環境是必然會出現的,特別是在本地調試的時候,這里我看了網上有很多種方案,大多都是做一層代理,可以使用瀏覽器插件,像我上一篇文章提到的插件,或者使用shelf_proxy
import 'dart:io';import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_proxy/shelf_proxy.dart';/// 命令 : dart ./lib/proxy_config.dart
void configServer(HttpServer server) {// 這里設置請求策略,允許所有server.defaultResponseHeaders.add('Access-Control-Allow-Origin', '*');server.defaultResponseHeaders.add('Access-Control-Allow-Credentials', true);server.defaultResponseHeaders.add('Access-Control-Allow-Methods', '*');server.defaultResponseHeaders.add('Access-Control-Allow-Headers', '*');server.defaultResponseHeaders.add('access-control-expose-headers', '*');print('Serving at http://${server.address.host}:${server.port}');
}Future<void> main() async {var reqHandle = proxyHandler("http://example.com/"); //要代理的域名/// 綁定本地端口,4500,轉發到真正的服務器中var reqServer = await shelf_io.serve(reqHandle, 'localhost', 4500);configServer(reqServer);
}
開啟一個代理服務器也可以。
不過還有一種簡單的方式:
直接關閉瀏覽器的安全模式:
"args": ["--target","lib/main.dart","--web-browser-flag","--disable-web-security","--web-renderer","html"],
當然最終上線還是要跟后端溝通好請求數據的要求。
預檢請求或CORS問題
瀏覽器對于復雜請求會發出一個預檢請求,也就是方法為OPTIONS的,這就是為什么在web端,同一個接口會觸發兩次的原因。
然后問題就來了,我的請求是Post,并且數據格式也是多表單數據,為什么還會發出OPTIONS請求呢,因為OPTIONS請求是先直接訪問你的一級域名,然后不帶任何數據去請求訪問后端是否允許發送跨域請求的,這個時候正常都不會支持,因為明明可以直接發送數據,不需要多一次預檢請求,發了反而失敗了導致CORS
,然后就不發送正常的請求了。
所以問題的關鍵在于,dio什么情況下會讓你的請求變成復雜的請求?
這里我就去官方github倉庫看了,發現不少人也提出了這樣的問題,分享這一個問題吧:
https://github.com/cfug/dio/issues/2125
標題就是:Flutter web - simple request causing OPTIONS request
為什么簡單的請求會發出OPTIONS 請求呢?
這位同學就說了:
如果你在web端,使用了connectTimeout
/ sendTimeout
/ onSendProgress
這三個函數,在web端沒有什么意義,反而會造成CORS,所以我在web端,單獨對這三個方法做了null處理,結果確實是不會發送OPTIONS請求了。
我還沒仔細去看這三個函數在源碼中的實現,有時間會去研究一下,但是確實是解決了問題。
網絡狀態檢測
之前在移動端可能會使用dio去做網絡狀態檢測,能訪問的通,就是有網絡,這在web端是不可靠的,因為web端有跨域問題,訪問其他域名大概率會失敗,但失敗不意味著你沒有網絡,因此可以使用web端原生的方法去檢測網絡。
結論
如果你有更多有趣的想法,歡迎在下方留言,我在查找很多關于flutter轉web相關的內容,發現解決方案真的很少,很多都必須從官方倉庫去獲取,希望能給你帶來一些幫助。