Flutter 上应该怎么请求http?很简单,直接用 dart:io
包下的 HttpClient,如下代码:
1 2 3 4 5 6 7 8 9 10
| HttpClient client = HttpClient(); client.getUrl(Uri.parse("https://yrom.net/")) .then((HttpClientRequest request) => request.close()) .then((HttpClientResponse response) { print('Response status: ${response.statusCode}'); response.transform(utf8.decoder).listen((contents) { print('${contents}'); }); });
|
但你肯定也发现了 dart:io
的 HttpClient
所提供的API太过底层了,所以一般不会直接用,而是用Dart官方提供的 http 包(package:http),如下代码:
1 2 3 4 5 6 7 8 9 10 11
| import 'package:http/http.dart';
Client client = Client(); var url = 'https://yrom.net'; var response = await client.get(url); print('Response status: ${response.statusCode}'); print('Response body: ${response.body}');
print(await client.read('https://yrom.net'));
|
或者有很多人喜欢的 dio。
但无论怎么封装API,底层都还是 dart:io
里的HttpClient
。
你可能一直有疑问,不是说 Flutter 是单线程的,那 http 请求难道不会卡住 UI 线程,导致 UI 无响应吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class ExampleState extends State<Example> { String data = '';
Future<void> requestData(url) async { setState(() { data = 'Loading $url'; }); var readed = await http.read(url); if (!mounted) return;
setState(() { data = readed; }); } @override void initState() { super.initState(); requestData('https://yrom.net/blog/2020/06/18/flutter-httpclient-overview/'); }
@override Widget build(BuildContext context) { return SingleChildScrollView( child: Text(data), ); } }
|
答案是,不会。但…why?
带你从源码里找找 HttpClient
的本质。
dart:io HttpClient
通过阅读HttpClient
的默认实现_HttpClient
(lib/_http/http_impl.dart)类的源码可知,实现Http请求的 TCP Socket 连接和读写都是由 _NativeSocket
(lib/_internal/vm/bin/socket_patch.dart) 实现,而_NativeSocket
又经由_IOService
(lib/_internal/vm/bin/io_service_patch.dart)进行 DNS 解析获取到服务器的ip地址(InternetAddress),最终和服务器进行TCP通信。
_HttpClient
关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Future<_HttpClientRequest> _openUrl(String method, Uri uri) { ... bool isSecure = (uri.scheme == "https"); int port = uri.port; if (port == 0) { port = isSecure ? HttpClient.defaultHttpsPort : HttpClient.defaultHttpPort; } ... return _getConnection(uri.host, port, proxyConf, isSecure, timeline).then( (_ConnectionInfo info) { _HttpClientRequest send(_ConnectionInfo info) { return info.connection .send(uri, port, method.toUpperCase(), info.proxy, timeline); }
if (info.connection.closed) { return _getConnection(uri.host, port, proxyConf, isSecure, timeline) .then(send); } return send(info); }); }
|
大致流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| _HttpClient + | _getConnection v _HttpClientConnection + | startConnect v Socket + | _startConnect v RawSocket + | startConnect v _NativeSocket + | v dispatch(socketLookup) _IOService
|
_HttpClientConnection
主要负责维持Socket
连接和读写,通过_HttpParser
实现Http协议包的封装和解析。
Dart 中的 Socket
既是个Stream
可用于消费(读),还是个IOSink
用于生产(写):
1
| abstract class Socket implements Stream<Uint8List>, IOSink {...}
|
撑起 Socket
家族的是_NativeSocket
,小心翼翼的维护着 Native 中创建的 Socket。
_Socket
-> _RawSocket
-> _NativeSocket
-> dart::bin::Socket
_NativeSocket
的关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class _NativeSocket { ... static Future<List<InternetAddress>> lookup(String host, {InternetAddressType type: InternetAddressType.any}) { return _IOService._dispatch(_IOService.socketLookup, [host, type._value]) .then((response) { if (isErrorResponse(response)) { throw createError(response, "Failed host lookup: '$host'"); } else { return response.skip(1).map<InternetAddress>((result) { var type = InternetAddressType._from(result[0]); return _InternetAddress(type, result[1], host, result[2], result[3]); }).toList(); } }); } ... Uint8List nativeRead(int len) native "Socket_Read"; int nativeWrite(List<int> buffer, int offset, int bytes) native "Socket_WriteList"; nativeCreateConnect(Uint8List addr, int port, int scope_id) native "Socket_CreateConnect"; ... }
|
通过阅读_NativeSocket
在Dart VM Runtime 层的对应的 C++ 代码可知,Socket 实现是非阻塞式(non-blocking) socket,再结合epoll
从而实现了async socket IO。
题外话,不阻塞读写是因为操作的是 buffer,而不会直接进行IO操作,需要用户进程不停地询问(poll)kernel 这个 buffer 是否就绪,再根据返回值做进一步处理(读/写/…)。还是没有明白?建议复习一下 Linux IO model,或者 Java 中的 NIO。
runtime/bin/socket_android.cc
中创建 socket 的代码:
1 2 3 4 5 6 7 8 9 10 11 12
| static intptr_t Create(const RawAddr& addr) { intptr_t fd; fd = NO_RETRY_EXPECTED(socket(addr.ss.ss_family, SOCK_STREAM, 0)); if (fd < 0) { return -1; } if (!FDUtils::SetCloseOnExec(fd) || !FDUtils::SetNonBlocking(fd)) { FDUtils::SaveErrorAndClose(fd); return -1; } return fd; }
|
在创建 socket 完毕后,并不会立马进行读写,而是通过一个叫EventHandler 实现的 IO 多路复用(IO multiplexing):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class _NativeSocket { ... void setHandlers({read, write, error, closed, destroyed}) { eventHandlers[readEvent] = read; eventHandlers[writeEvent] = write; eventHandlers[errorEvent] = error; eventHandlers[closedEvent] = closed; eventHandlers[destroyedEvent] = destroyed; }
void setListening({bool read: true, bool write: true}) { sendReadEvents = read; sendWriteEvents = write; if (read) issueReadEvent(); if (write) issueWriteEvent(); if (!flagsSent && !isClosing) { flagsSent = true; int flags = 1 << setEventMaskCommand; if (!isClosedRead) flags |= 1 << readEvent; if (!isClosedWrite) flags |= 1 << writeEvent; sendToEventHandler(flags); } }
void multiplex(Object eventsObj) { ... } void sendToEventHandler(int data) { int fullData = (typeFlags & typeTypeMask) | data; assert(!isClosing); connectToEventHandler(); _EventHandler._sendData(this, eventPort.sendPort, fullData); }
void connectToEventHandler() { assert(!isClosed); if (eventPort == null) { eventPort = new RawReceivePort(multiplex); } ... } }
|
其中,EventHandler
是通过 epoll
实现对 Socket IO 事件的异步处理,存活在一个独立线程,当 poll 到事件时和上述代码中的 eventPort
进行通信( 还是 isolate 那套(#°Д°) ),从而实现 IO 多路复用。_RawSocket
根据底层拉取到的事件最终分发到 _Socket
类中进行读、写、关闭、销毁等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class _RawSocket extends Stream<RawSocketEvent> implements RawSocket { final _NativeSocket _socket; _RawSocket(this._socket) { var zone = Zone.current; _controller = new StreamController( sync: true, onListen: _onSubscriptionStateChange, onCancel: _onSubscriptionStateChange, onPause: _onPauseStateChange, onResume: _onPauseStateChange); _socket.setHandlers( read: () => _controller.add(RawSocketEvent.read), write: () { writeEventsEnabled = false; _controller.add(RawSocketEvent.write); }, closed: () => _controller.add(RawSocketEvent.readClosed), destroyed: () { _controller.add(RawSocketEvent.closed); _controller.close(); }, error: zone.bindBinaryCallbackGuarded((e, st) { _controller.addError(e, st); _socket.close(); })); } }
|
总结一下大致流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| _HttpConnection ^ | + _Socket ^ | handle IO events + _RawSocket ^ | issue IO events + _NativeSocket ^ | multiplex + RawReceivePort ^ | handleEvents + EventHandler (epoll)
|
那么问题来了,有没有办法自己实现一个HttpClient
用来替换dart:io
默认的实现?
HttpOverrides
可以通过 HttpOverrides 提供自定义的 HttpClient
实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyHttpClient implements HttpClient { ... }
class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext context) { return new MyHttpClient(context); } } void main() { HttpOverrides.global = MyHttpOverrides(); ... }
|
然而,如前文所述,HttpClient
提供的API都太底层了,如果完全实现,工作量巨大,还得自己处理Proxy、SSL证书校验、Socket读写、TCP包的解析。。。
通过 MethodChannel 实现 HttpClient
从客户端APP开发角度,Android 和 iOS 的各类 httpclient 实现都已经很成熟稳定了,自然而然想到能不能直接用原生代码来实现一个。
考虑HttpClient
API 太过于底层,对于桥接原生实现不太友好,这里可以直接将接口收束到 http
包的 Client
类,这样实现起来简单多了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| +-----------+ | Client | package:http/http.dart +------^----+ | implement | +----------+-------+ | | +-----+-----+ +------+-----------+ | IOClient | | HttpClientPlugin | +-----------+ +------+-----------+ dart:io | MethodChannel('httpclient') | +-----------+-----------+ | | |Android | iOS +---v---+ +---v--------+ |OkHttp | |NSURLSession| +-------+ +------------+
|
而 Flutter 层用到 http 包的地方完全不用修改,只需要更换Client
实现类即可。
1 2
| Client client = HttpClientPlugin() print(await client.read('https://yrom.net'));
|
大致流程上,HttpClientPlugin
通过MethodChannel
将 method
, url
, body
, headers
这些http请求参数直接分发给原生,原生再进一步调用相应的 HttpClient 接口。
Dart HttpClientPlugin
类继承 BaseClient
(package:http/http.dart 中)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:http_parser/http_parser.dart';
class HttpClientPlugin extends BaseClient {
static const MethodChannel channel = MethodChannel('httpclient');
@override Future<StreamedResponse> send(BaseRequest request) async { final String method = request.method; assert(request != null && method != null); Map<String, String> headers = CaseInsensitiveMap.from(request.headers); var stream = request.finalize(); Uint8List bodyBytes = stream != null ? await stream.toBytes() : Uint8List(0); Uri url = _validateUrl(request);
try { var response = await channel.invokeMapMethod<String, dynamic>('send', { 'method': method, 'url': url.toString(), if (requiresRequestBody(method)) 'body': bodyBytes, 'headers': headers, });
Uint8List bytes = response['body']; assert(bytes != null); int contentLength = response['content_length']; if (contentLength == null || contentLength == -1) { contentLength = bytes.lengthInBytes; } int statusCode = response['status_code'];
Map<String, String> responseHeaders = CaseInsensitiveMap.from(response['headers'].cast<String, String>());
return StreamedResponse( ByteStream.fromBytes(bytes), statusCode, contentLength: contentLength, request: request, headers: responseHeaders, reasonPhrase: response['reason'], ); } catch (e, stack) { throw ClientException('Failed to send request via channel', request.url); } }
Uri _validateUrl(BaseRequest request) { Uri url = request.url; if (url == null) { throw ArgumentError.notNull('request.url'); } if (url.host.isEmpty) { throw ArgumentError("No host specified in URI $url"); } if (!url.isScheme('http') && !url.isScheme('https')) { throw ArgumentError.value( url.scheme, 'request.sheme', ); } return url; } }
bool requiresRequestBody(String method) { return method == 'PUT' || method == 'POST' || method == 'PATCH'; }
|
Android HttpclientPlugin 实现类关键方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class HttpclientPlugin : FlutterPlugin, MethodCallHandler { ... val client: OkHttpClient fun sendRequest( method: String, url: String, body: ByteArray?, headers: Map<String, String>?, result: Result ) { val httpHeaders = if (headers != null) Headers.of(headers) else Headers.Builder().build() val requestBody = if (body == null) null else object : RequestBody() { override fun contentType(): MediaType? { return httpHeaders["Content-Type"]?.let { MediaType.parse(it) } }
override fun contentLength(): Long = body.size.toLong()
override fun writeTo(sink: BufferedSink) { sink.write(body) } } val request = Request.Builder().apply { method(method, requestBody) url(url) headers(httpHeaders) }.build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { mainHandler.post { result.error("22", e.message, Log.getStackTraceString(e)) } }
override fun onResponse(call: Call, response: Response) { val map = response.use { linkedMapOf( "status_code" to it.code(), "reason" to it.message(), "headers" to response.headers().toStringMap(), "content_length" to it.body()!!.contentLength(), "body" to it.body()!!.bytes() ) } mainHandler.post { result.success(map) } } }) } }
|
iOS 实现类关键方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| void sendHttpRequest(NSString* method, NSString* url, FlutterStandardTypedData* body, NSDictionary<NSString*, NSString*>* headers, FlutterResult fResult) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: url]]; if (method) { [request setHTTPMethod:method]; } if (body && [body elementCount] > 0) { [request setHTTPBody: body.data]; } if (headers && [headers count] > 0) { [request setAllHTTPHeaderFields: headers]; } NSURLSessionDataTask* task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSInteger statusCode; NSString *reason; NSDictionary* headers; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { statusCode = [(NSHTTPURLResponse *)response statusCode]; reason = [NSHTTPURLResponse localizedStringForStatusCode:statusCode]; headers = [(NSHTTPURLResponse *)response allHeaderFields]; } else { statusCode = 200; } dispatch_async(dispatch_get_main_queue(), ^{ if (error == nil) { NSMutableDictionary<NSString*, id>* dict = [[NSMutableDictionary alloc] init]; [dict setValue:[NSNumber numberWithInteger:statusCode] forKey:@"status_code"]; [dict setValue:reason forKey:@"reason"]; [dict setValue:[FlutterStandardTypedData typedDataWithBytes:data] forKey:@"body"]; [dict setValue:headers forKey:@"headers"]; fResult(dict); } else { fResult([FlutterError errorWithCode:[NSNumber numberWithInteger:[error code]].stringValue message:[error localizedFailureReason] details: [ error localizedDescription]]); } }); }]; [task resume]; }
|
总结
通过本文,相信你跟着我一起阅读了一遍 dart:io
中的 HttpClient
源码,也了解了 Socket
大概实现,又复习了 IO Model。还通过 MethodChannel 实现了一个自己的 HttpClient Flutter 插件…
那么,恭喜你离精通 Flutter 又近了一步(*/ω\*)。
若发现文中错误欢迎评论指出,若有疑问也欢迎在评论区讨论。
广告环节
哔哩哔哩漫画招 Flutter 开发,欢迎投简历到邮箱 wangyongrong@bilibili.com