一般的,在 Flutter APP 里请求 HTTP 使用的是官方提供的 http 包。
import 'package:http/http.dart' as http;
var url = 'https://jsonplaceholder.typicode.com/posts';
var response = await http.get(url);
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
print(await http.read('https://jsonplaceholder.typicode.com/posts/1'));
但是,有一个问题,在 Android 或者 iOS 上运行 Flutter APP,系统里配置的 HTTP 代理并不生效?
比如在使用 Charles 这种工具通过 HTTP 代理调试 api 请求时候,会发现 Flutter 的 http 请求没有按预期走代理,无论是 Http 还是 Https。
阅读 http 包的源码 ,可以发现其是基于 Dart HttpClient API 封装的。
Future<Response> get(url, {Map<String, String> headers}) =>
_withClient((client) => client.get(url, headers: headers));
Future<T> _withClient<T>(Future<T> Function(Client) fn) async {
var client = Client();
try {
return await fn(client);
} finally {
client.close();
}
}
abstract class Client {
/// Creates a new platform appropriate client.
///
/// Creates an `IOClient` if `dart:io` is available and a `BrowserClient` if
/// `dart:html` is available, otherwise it will throw an unsupported error.
factory Client() => createClient();
...
}
在 Android 或 iOS 平台上,我们用的实现是 IOClient :
BaseClient createClient() => IOClient();
/// A `dart:io`-based HTTP client.
class IOClient extends BaseClient {
/// The underlying `dart:io` HTTP client.
HttpClient _inner;
IOClient([HttpClient inner]) : _inner = inner ?? HttpClient();
...
}
可以看到, IOClient 用的是 dart:io 中的 HttpClient 。
而 HttpClient 中获取 HTTP 代理的关键源码如下:
abstract class HttpClient {
...
static String findProxyFromEnvironment(Uri url,
{Map<String, String> environment}) {
HttpOverrides overrides = HttpOverrides.current;
if (overrides == null) {
return _HttpClient._findProxyFromEnvironment(url, environment);
}
return overrides.findProxyFromEnvironment(url, environment);
}
...
}
class _HttpClient implements HttpClient {
...
Function _findProxy = HttpClient.findProxyFromEnvironment;
set findProxy(String f(Uri uri)) => _findProxy = f;
...
}
通过阅读 HttpClient 源码,可以知道默认的 HttpClient 实现类 _HttpClient 是通过环境变量来获取http代理( findProxyFromEnvironment )的。
那么,只需要在它创建后,重新设置 findProxy 属性即可实现自定义 HTTP 代理:
void request() {
HttpClient client = new HttpClient();
client.findProxy = (url) {
return HttpClient.findProxyFromEnvironment(
url, environment: {"http_proxy": ..., "no_proxy": ...});
}
client.getUrl(Uri.parse('https://jsonplaceholder.typicode.com/posts'))
.then((HttpClientRequest request) {
return request.close();
})
.then((HttpClientResponse response) {
// Process the response.
...
});
}
环境变量(environment)里有三个 HTTP Proxy 配置相关的key:
{
"http_proxy": "192.168.2.1:1080",
"https_proxy": "192.168.2.1:1080",
"no_proxy": "example.com,www.example.com,192.168.2.3"
}
问题来了,该怎么介入 HttpClient 的创建?
再看一下源码:
abstract class HttpClient {
...
factory HttpClient({SecurityContext context}) {
HttpOverrides overrides = HttpOverrides.current;
if (overrides == null) {
return new _HttpClient(context);
}
return overrides.createHttpClient(context);
}
...
}
答案就是 HttpOverrides 。 HttpClient 是可以通过 HttpOverrides.current 覆写的。
abstract class HttpOverrides {
static HttpOverrides _global;
static HttpOverrides get current {
return Zone.current[_httpOverridesToken] ?? _global;
}
static set global(HttpOverrides overrides) {
_global = overrides;
}
...
}
顾名思义, HttpOverrides 是用来覆写 HttpClient 的实现的,一个很简单的例子:
class MyHttpClient implements HttpClient {
...
}
void request() {
HttpOverrides.runZoned(() {
...
}, createHttpClient: (SecurityContext c) => new MyHttpClient(c));
}
但完全实现 HttpClient 的 API 又太复杂了,我们只是想设置 HTTP Proxy 而已,也就是给默认的 HttpClient 设一个自定义的 findProxy 实现就够了。
换个思路,自定义一个 MyHttpOverrides ,让 HttpOverrides.current 返回的是 MyHttpOverrides 不就好了?!
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext context) {
return super.createHttpClient(context)
..findProxy = _findProxy;
String _findProxy(url) {
return HttpClient.findProxyFromEnvironment(
url, environment: {"http_proxy": ..., "no_proxy": ...});
}
}
void main() {
// 注册全局的 HttpOverrides
HttpOverrides.global = MyHttpOverrides();
runApp(...);
}
如上代码,通过设置 HttpOverrides.global ,最终覆盖了默认 HttpClient 的 findProxy 实现。
现在新的问题来了,怎么让这个 MyHttpOverrides 能获取到原生的 HTTP Proxy 配置呢?
Flutter 和原生通信,你想到了什么?是的, MethodChannel !
定义一个全局变量 proxySettings ,在 MyHttpOverrides 里当作 findProxyFromEnvironment 的环境变量:
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext context) {
return super.createHttpClient(context)
..findProxy = _findProxy;
}
static String _findProxy(url) {
// proxySettings 当作 findProxyFromEnvironment 的 environment
return HttpClient.findProxyFromEnvironment(url, environment: proxySettings);
}
}
// 定义一个全局变量,当作环境变量
Map<String, String> proxySettings = {};
void main() {
HttpOverrides.global = MyHttpOverrides();
runApp(...);
// 加载proxy 设置,注意需要在 runApp 之后执行
loadProxySettings();
}
定义一个 MethodChannel, 名为 “yrom.net/http_proxy”,提供一个 getProxySettings 方法。
import 'package:flutter/services.dart';
Future<void> loadProxySettings() async {
final channel = const MethodChannel('yrom.net/http_proxy');
// 设置全局变量
try {
var settings = await channel.invokeMapMethod<String, String>('getProxySettings');
if (settings != null) {
proxySettings = Map<String, String>.unmodifiable(settings);
}
} on PlatformException {
}
}
通过调用 getProxySettings 方法,获取到的原生的HTTP Proxy 配置。
从而实现同步。
Android 里通过 ProxySelector API 获取 HTTP Proxy。
import java.net.ProxySelector
class MainActivity: FlutterActivity() {
private val CHANNEL = "yrom.net/http_proxy"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
if (call.method == "getProxySettings") {
result.success(getProxySettings())
} else {
result.notImplemented()
}
}
}
private fun getProxySettings() : Map<String, String> {
val settings = HashMap<>(2);
try {
val https = ProxySelector.getDefault().select(URI.create("https://yrom.net"))
if (https != null && !https.isEmpty) {
val proxy = https[0]
if (proxy.type() != Proxy.Type.DIRECT) {
settings["https_proxy"] = proxy.address().toString()
}
}
val http = ProxySelector.getDefault().select(URI.create("http://yrom.net"))
if (http != null && !http.isEmpty) {
val proxy = http[0]
if (proxy.type() != Proxy.Type.DIRECT) {
settings["http_proxy"] = proxy.address().toString()
}
}
} catch (ignored: Exception) {
}
return settings;
}
}
iOS 则通过 CFNetworkCopySystemProxySettings API 获取配置。
#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* proxyChannel = [FlutterMethodChannel
methodChannelWithName:@"yrom.net/http_proxy"
binaryMessenger:controller.binaryMessenger];
[proxyChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([@"getProxySettings" isEqualToString:call.method]) {
NSDictionary * proxySetting = (__bridge_transfer NSDictionary *)CFNetworkCopySystemProxySettings();
NSMutableDictionary * proxys = [NSMutableDictionary dictionary];
NSNumber * httpEnable = [proxySetting objectForKey:(NSString *) kCFNetworkProxiesHTTPEnable];
// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants
if(httpEnable != nil && httpEnable.integerValue != 0) {
NSString * httpProxy = [NSString stringWithFormat:@"%@:%@",[proxySetting objectForKey:(NSString *)kCFNetworkProxiesHTTPProxy],[proxySetting objectForKey:(NSString *)kCFNetworkProxiesHTTPPort]];
proxys[@"http_proxy"] = httpProxy;
}
NSNumber * httpsEnable = [proxySetting objectForKey:@"HTTPSEnable"];
if(httpsEnable != nil && httpsEnable.integerValue != 0) {
NSString * httpsProxy = [NSString stringWithFormat:@"%@:%@",[proxySetting objectForKey:@"HTTPSProxy"],[proxySetting objectForKey:@"HTTPSPort"]];
proxys[@"https_proxy"] = httpsProxy;
}
result(proxys);
}
}];
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
聪明的你看了上面的代码之后,应该会发现一些新的问题: HttpClient 的 findProxy(url) 的参数 url 似乎没用到?而且原生的 getProxySettings 实现返回的配置和具体的 url 无关?网络切换后,没有更新 proxySettings ?( ̄ε(# ̄)
理论上, getProxySettings 应该和 findProxy(url) 一样,需要定义一个额外参数 url ,然后每次 findProxy 的时候,就 invoke 一次,实时获取原生当前网络环境的 HTTP Proxy:
class MyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext context) {
return super.createHttpClient(context)
..findProxy = _findProxy;
}
static String _findProxy(url) {
String getProxySettings() {
return channel.invokeMapMethod<String, String>('getProxySettings');
}
return HttpClient.findProxyFromEnvironment(url, environment: getProxySettings());
}
}
然而现实是, MethodChannel 的 invokeMapMethod 返回的是个 Future ,但 findProxy 却是一个同步方法。。。
暂时,先把视线从 HttpClient 和 HttpOverrides 中抽离出来,回头看看发送 http 请求的代码:
import 'package:http/http.dart' as http;
var url = 'https://jsonplaceholder.typicode.com/todos/1';
var response = await http.get(url);
http 包里的的 get 的方法就是个异步的,返回的是个 Future !如果每次请求之前,同步一下 proxySettings 是不是可以解决问题?
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
Future<Map<String, String>> getProxySettings(String url) async {
final channel = const MethodChannel('yrom.net/http_proxy');
try {
var settings = await channel.invokeMapMethod<String, String>('getProxySettings', url);
if (settings != null) {
return Map<String, String>.unmodifiable(settings);
}
} on PlatformException {}
return {};
}
class MyHttpOverrides extends HttpOverrides {
final Map<String, String> environment;
MyHttpOverrides({this.environment});
@override
HttpClient createHttpClient(SecurityContext context) {
return super.createHttpClient(context)
..findProxy = _findProxy;
}
String _findProxy(url) {
return HttpClient.findProxyFromEnvironment(url, environment: environment);
}
}
Future<void> request() async {
var url = 'https://jsonplaceholder.typicode.com/todos/1';
var overrides = MyHttpOverrides(environment: await getProxySettings(url));
var response = await HttpOverrides.runWithHttpOverrides<Future<http.Response>>(
() => http.get(url),
overrides,
);
//...
}
但是这样每次 http 请求都有一次 MethodChannel 通信,会不会太频繁影响性能?每次都要等待 MethodChannel 的回调会不会导致 http 请求延迟变高?对于同一个域名的不同URL来说,代理配置应该是一致的,能不能合并到一起 getProxySettings ?
原文 https://yrom.net/blog/2020/04/09/load-http-proxy-from-platform-for-flutter-app/
咱们了解了异步 JS 是如何工作的,以及调用堆栈、事件循环、消息队列和任务队列等概念,这些概念共同构成了 JS运行时环境。虽然成为一名出色的JS开发人员并不需要学习所有这些概念,但是了解这些概念是有帮助的。
我们通常编写HTML文件,并调用CSS文件实现布局美化时,经常出现明明已经修改过CSS文件,但HTML页面却并没有产生变化的现象。下面我们来看一下解决这种情况的方法。
对于父子组件状态同步,这篇文章 《vue父子组件状态同步的最佳方式》 讲述了大多数情况下的最优解,但是当我们希望自己创建的可复用组件和封装的逻辑能够尽量行为一致的时候情况可能会有所不同
之前学vue的时候看到过异步加载数据的写法,并且在vue里面用的大都是ES6的语法。对于async和await并不太了解,网上说在uniapp中请求数据时总是数据和页面不同步
Node.js 最大的特点就是采用异步式 I/O 与事件驱动的架构设计。对于高并发的解决方案,传统的架构是多线程模型,也就是为每个业务逻辑提供一个系统线程
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!