http回顾
http 协议 是应用层协议 同时还有 ftp dns tftp smtp snmp ,是一个基于TCP/IP通信协议来传递数据.
传输的类型由Content-Type加以标记,同时具有无连接、无状态等特点
一个请求由请求行(request line)、请求头部(header)、空行和请求数据四个部分组成
现在常用的是http1 的协议。 而http2 的协议 是在1的基础上升级,增加了安全性是机遇https,增加了效率通过二进制分帧来进行数据传输
在http1.1中,客户端在同一时间,针对同一域名下的请求有一定数量的限制
而http2.0中的多路复用优化了这一性能。多路复用允许同时通过单一的http/2 连接发起多重的请求-响应消息http2 不再依赖多个TCP连接去实现多流并行了
http 2.0 连接都是持久化的,接可以承载数十或数百个流的复用,
而且使用encoder来减少需要传输的header大小的同时通讯双方各自缓存一份头部字段表,避免了重复header的传输
tcp 回顾
tcp 位于传输层
用来传输数据,常见的三次握手
ip回顾
用于标识网络中的唯一一台主机或路由器. 一般用DNS 协议提供通过域名查找 IP 地址
https 回顾
一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。
SSL/TLS是一个安全通信框架,上面可以承载HTTP协议或者SMTP/POP3协议等。
https请求流程
1.首先客户端通过URL访问服务器建立SSL连接。
2.服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端。
3.客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
4.客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
5.服务器利用自己的私钥解密出会话密钥。
6.服务器利用会话密钥加密与客户端之间的通信。
回顾得知, https 会先建立ssl 连接, 请求证书, 如果证书不正确,就终止步骤,
证书的检查由系统操作,dio 没有api 可以自定义检查 ,只有一个不可信证书处理机制
用来配处理自己的证书(注意只有不可信的证书才会执行)
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback=(X509Certificate cert, String host, int port){ if(cert.pem==PEM){ // Verify the certificate return true; } return false; }; };
或者添加自签名证书(其他可信任的依然可以通过)
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) { SecurityContext sc = SecurityContext(); //file is the path of certificate sc.setTrustedCertificates(file); HttpClient httpClient = HttpClient(context: sc); return httpClient; };
如果想固定的证书校验通过.
在dio 里面有一个http_certificate_pinning 插件, 用来配置固定证书
var dio = Dio(BaseOptions(baseUrl: baseUrl)) ..interceptors.add(CertificatePinningInterceptor(allowedSHAFingerprints));
源码查看 CertificatePinningInterceptor
@override Future onRequest( RequestOptions options, RequestInterceptorHandler handler, ) async { try { //已验证的跳过 if (verifiedURLs.contains(options.baseUrl)) { return super.onRequest(options, handler); } //iOS问题 if (Platform.isIOS && secure != null) { await secure; } //当前插件是基于 ssl_pinning_plugin 插件二次开发 //直接看android 实现部分 secure = HttpCertificatePinning.check( serverURL: options.baseUrl, headerHttp: options.headers.map((a, b) => MapEntry(a, b.toString())), sha: SHA.SHA256, allowedSHAFingerprints: _allowedSHAFingerprints, timeout: _timeout, ); secure?.whenComplete(() => secure = null); final secureString = await secure ?? ''; if (secureString.contains('CONNECTION_SECURE')) { verifiedURLs.add(options.baseUrl); return super.onRequest(options, handler); } else { handler.reject( DioError( requestOptions: options, error: CertificateNotVerifiedException(), ), ); } } on Exception catch (e) { ...省略 }
最终调用了android 的handleCheckEvent 方法
private fun handleCheckEvent(call: MethodCall, result: Result) { //取值 val arguments: HashMap<String, Any> = call.arguments as HashMap<String, Any> val serverURL: String = arguments.get("url") as String val allowedFingerprints: List<String> = arguments.get("fingerprints") as List<String> val httpMethod: String = arguments.get("httpMethod") as String val httpHeaderArgs: Map<String, String> = arguments.get("headers") as Map<String, String> val timeout: Int = arguments.get("timeout") as Int val type: String = arguments.get("type") as String //开始检查 val get: Boolean = CompletableFuture.supplyAsync { this.checkConnexion(serverURL, allowedFingerprints, httpHeaderArgs, timeout, type, httpMethod) }.get() //返回结果 if(get) { result.success("CONNECTION_SECURE") }else { result.error("CONNECTION_NOT_SECURE", "Connection is not secure", "Fingerprint doesn't match") } }
代码比较简单, 直接忽略中间各种调用跳转。最终调用了getFingerprint
private fun getFingerprint(httpsURL: String, connectTimeout: Int, httpHeaderArgs: Map<String, String>, type: String, httpMethod: String): String { //调用java7的API 准备连接 val url = URL(httpsURL) val httpClient: HttpsURLConnection = url.openConnection() as HttpsURLConnection if (httpMethod == "Head") httpClient.setRequestMethod("HEAD"); httpHeaderArgs.forEach { key, value -> httpClient.setRequestProperty(key, value) } httpClient.connect() //获取服务器证书 val cert: Certificate = httpClient.getServerCertificates()[0] as Certificate //断开链接 httpClient.disconnect() //返回 return this.hashString(type, cert.getEncoded()) }