在计算机网络中,代理服务器 扮演着发起请求的客户端与服务器之间的中间人的角色。客户端连接到代理服务器,请求一些服务,比如文件,网页,或其它可以从服务器获得的资源,代理服务器以简化和控制复杂度的形式获取请求的响应。代理被发明以为分布式系统添加结构和封装。
在我们做移动端开发时,代理常常可以作为我们网络调试的利器。然而我们设置的代理究竟是如何对网络访问的整个过程产生影响的呢?本文将尝试回答这个问题。
系统静态代理服务器信息的解析
一个HTTP请求的执行过程,大体为:
- 连接准备。
- 建立TCP连接。
- 如果是HTTPS的话,完成SSL/TLS的握手。
- 如果是HTTP2的话,在SSL/TLS握手完成之后,执行HTTP2的协商。
- 发送请求。
- 获取响应。
- 结束请求,关闭连接。
与代理相关的处理,主要发生在上面的连接准备与连接建立阶段,这主要包括解析系统中保存的静态代理服务器设置信息,以及以特有的方式建立与代理之间的连接。
Chromium net在 HttpStreamFactoryImpl::Job::DoLoop(int result) 中执行解析代理信息、建立连接、处理TLS握手/HTTP2握手/QUIC握手,并创建Stream的过程:
具体到系统静态代理服务器设置信息的解析,
Chromium net在对请求的代理的处理上比较灵活,它允许为请求设置一个标记 LOAD_BYPASS_PROXY ,以使该请求的执行总是绕过代理。在HttpStreamFactoryImpl::Job::DoResolveProxy()
中会首先检查请求是否设置了这个标记,若设置,则将与服务器直连而立即返回,不再执行后面解析系统代理服务器设置系统的过程。否则继续执行。
对于代理的使用,用户通常都可以设置一些规则,比如代理的类型,比如对设置对某些域名的访问不使用代理等等。因而对于适当的代理的选择,是根据设置的规则和要访问的URL进行的。Alternative-Service是一种用于支持新协议,比如HTTP2,SPDY和QUIC这种,的机制。这种机制通过服务器向客户端返回一个 “Alt-Svc” 头部字段以表明服务器期望客户端采用的新协议。如果要使用新协议,则发送请求的URL可能会有一定的改变。在HttpStreamFactoryImpl::Job::DoResolveProxy()
中,若要使用 “Alt-Svc” SPDY/HTTP2,会先对原始的Url做一定的修饰,并以修饰后的Url为基础去选择代理。
最后通过ProxyService解析代理信息,选择代理服务器。
解析代理之后,执行的HttpStreamFactoryImpl::Job::DoResolveProxyComplete()
主要是对解析的结果做检查。在这里会过滤掉不支持的代理,并返回最终的检查结果。为了保持处理逻辑的简便统一,即使没有设置任何代理服务器,解析的代理服务器列表也不会是空的,而是包含一个类型为DIRECT的代理设置。
默认的ProxyService
HttpStreamFactoryImpl::Job::DoResolveProxy()
所用到的 ProxyService
来自于HttpNetworkSession
。而 HttpNetworkSession
的 ProxyService
则是通过如下过程一步一步从 URLRequestContextBuilder
传过来的:
|
|
在 URLRequestContextBuilder::Build()
中可以看到如下的几行代码:
然而,对于Android而言,使用的并不是这里创建的 ProxyService
。ProxyConfigService
和 ProxyService
都是在更早的时候创建的。ProxyConfigService
创建的位置 (components/cronet/android/cronet_url_request_context_adapter.cc) 如下:
而 ProxyService
的创建位置 (components/cronet/android/cronet_url_request_context_adapter.cc) 则在 CronetURLRequestContextAdapter::InitializeOnNetworkThread()
:
ProxyConfigService
和 ProxyService
的实际创建过程(位于net/proxy/proxy_service.cc )如下:
可见在Android平台,默认的ProxyConfigService
为 ProxyConfigServiceAndroid
, ProxyService
本身并不单单是接口,它在解析代理信息时,除了依赖静态信息外,还会依赖 ProxyResolverFactory
和 ProxyResolver
去获得代理信息。按照设计, ProxyResolver
将会填充用于特定URL的代理的列表。通常的 ProxyResolver
后端都是一个PAC脚本,但也不一定。一个 ProxyResolver
可以在同一时间为多个URL服务。
而在Android平台 ProxyResolverFactory
和 ProxyResolver
的实现分别为 ProxyResolverFactoryForNullResolver
和 ProxyResolverNull
。可以看一下ProxyResolverFactoryForNullResolver
和 ProxyResolverNull
的实现(位于net/proxy/proxy_service.cc ):
由此,可以认为在Android平台是没有 ProxyResolver
后端的,也就是代理解析,基本上只依赖系统的静态配置信息。
ProxyService的初始化
在 ProxyService
创建的时候,会做一些初始化:
这里主要是将 ProxyService
对象注册为网络状态的监听者,以监听IP地址和 DNS 的改变,并注册为 ProxyConfigService
的监听者以监听。由于创建初始,previous_state 为 STATE_NONE,因而并不会做更多别的事情。
ProxyConfigService的初始化
ProxyConfigService
是Android平台中 ProxyService
获取代理配置信息的关键,回头再来看 ProxyConfigService
的创建及初始化过程。如我们前面看到的,创建对象的位置在CronetURLRequestContextAdapter::InitRequestContextOnMainThread()
。具体的过程( 位于net/proxy/proxy_config_service_android.cc )如下:
在这里主要是创建 ProxyConfigServiceAndroid::Delegate
,并做初始化。初始化主要包括 SetupJNI()
和 FetchInitialConfig()
,其中 SetupJNI()
是这样的:
可以看到,它主要是创建了一个类型为org.chromium.net.ProxyChangeListener
的Java对象,并调用了该对象的 start(long nativePtr)
方法( 位于net/android/java/src/org/chromium/net/ProxyChangeListener.java )。来看这个Java类的实现:
可以看到,这里主要是注册了一个监听 Action 为 Proxy.PROXY_CHANGE_ACTION 的 BroadcastReceiver。再来看 FetchInitialConfig()
:
可以看到,这里做了两件事,一是获取系统的代理配置信息,方法主要还是通过读取系统属性完成;二是通知监听者,这主要是ProxyService
。
对于 Action 为 Proxy.PROXY_CHANGE_ACTION 的 BroadcastReceiver,是在注册完成之后几乎立即就会得到通知的。ProxyReceiver的实现如下:
|
|
这个Receiver在收到通知后,会将代理信息传递到C/C++层。最终调用ProxyConfigServiceAndroid::Delegate::JNIDelegateImpl
:
继而调用 ProxyConfigServiceAndroid::Delegate
的相应方法:
ProxySettingsChanged()
如同 FetchInitialConfig()
一样,是从system property中获取代理配置信息,并通知监听者。而 ProxySettingsChangedTo()
则是以传入的代理配置信息构造配置,并通知监听者。
可见,BroadcastReceiver
通知时的这次配置信息更新会冲掉最初通过 FetchInitialConfig()
获取的那些。
总结一下,在Android中,chromium net获取代理配置信息的方法是:
- 在
ProxyConfigServiceAndroid
创建过程中,从system property中读取代理设置信息,同时注册BroadcastReceiver以监听系统代理配置的改变。 - 在收到系统广播消息的通知时,若广播中包含详细的代理配置信息,则以这些信息更新Chromium net的代理设置;否则,再次读取system property获取代理配置信息。
代理解析
ProxyConfigService
在代理配置发生改变时,会将新的代理配置通知给ProxyService
:
在这里主要是将新的代理配置信息保存在 fetched_config_
中,继而将配置保存在 config_ 中,并设置状态标记 currentstate 为 ready。
HttpStreamFactoryImpl::Job::DoResolveProxy()
通过 ProxyService
的 ResolveProxy()
来为特定的URL找到合适的代理服务器:
这个过程应用代理规则,选择适当的代理服务器给调用者。
Done。