2016-01 from–https://blog.csdn.net/Luoshengyang/article/details/50527574
2014-06 from–https://www.cnblogs.com/fangkm/p/3784660.html
2018-12 from–https://blog.csdn.net/camike/article/details/85230513
2017-03 from–https://blog.csdn.net/u012155923/article/details/66973857?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
Chromium在Browser进程中为网页创建了一个Frame Tree之后,会将网页的URL发送给Render进程进行加载。Render进程接收到网页URL加载请求之后,会做一些必要的初始化工作,然后请求Browser进程下载网页的内容。Browser进程一边下载网页内容,一边又通过共享内存将网页内容传递给Render进程解析,也就是创建DOM Tree。本文接下来就分析网页URL的加载过程。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!
Render进程之所以要请求Browser进程下载网页的内容,是因为Render进程没有网络访问权限。出于安全考虑,Chromium将Render进程启动在一个受限环境中,使得Render进程没有网络访问权限。那为什么不是Browser进程主动下载好网页内容再交给Render进程解析呢?
这是因为Render进程是通过WebKit加载网页URL的,WebKit不关心自己所在的进程是否有网络访问权限,它通过特定的接口访问网络。这个特定接口由WebKit的使用者,也就是Render进程中的Content模块实现。Content模块在实现这个接口的时候,会通过IPC请求Browser进程下载网络的内容。这种设计方式使得WebKit可以灵活地使用:既可以在有网络访问权限的进程中使用,也可以在没有网络访问权限的进程中使用,并且使用方式是统一的。
从前面Chromium Frame Tree创建过程分析一文可以知道,Browser进程中为要加载的网页创建了一个Frame Tree之后,会向Render进程发送一个类型为FrameMsg_Navigate的IPC消息。Render进程接收到这个IPC消息之后,处理流程如图1所示:
图1 网页URL加载过程
Render进程执行了一些初始化工作之后,就向Browser进程发送一个类型为ResourceHostMsg_RequestResource的IPC消息。Browser进程收到这个IPC消息之后,就会通过HTTP协议请求Web服务器将网页的内容返回来。请求得到响应后,Browser进程就会创建一块共享内存,并且通过一个类型为ResourceMsg_SetDataBuffer的IPC消息将这块共享内存传递给Render进程的。
以后每当下载到新的网页内容,Browser进程就会将它们写入到前面创建的共享内存中去,并且发送Render进程发送一个类型为ResourceMsg_DataReceived的IPC消息。Render进程接收到这个IPC消息之后,就会从共享内存中读出Browser进程写入的内容,并且进行解析,也就是创建一个DOM Tree。这个过程一直持续到网页内容下载完成为止。
接下来,我们就从Render进程接收类型为FrameMsg_Navigate的IPC消息开始分析网页URL的加载过程。Render进程是通过RenderFrameImpl类的成员函数OnMessageReceived接收类型为FrameMsg_Navigate的IPC消息的,如下所示:
- bool RenderFrameImpl::OnMessageReceived(const IPC::Message& msg) {
- ……
- bool handled = true;
- IPC_BEGIN_MESSAGE_MAP(RenderFrameImpl, msg)
- IPC_MESSAGE_HANDLER(FrameMsg_Navigate, OnNavigate)
- ……
- IPC_END_MESSAGE_MAP()
- return handled;
- }
这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
RenderFrameImpl类的成员函数OnMessageReceived将类型为FrameMsg_Navigate的IPC消息分发给另外一个成员函数OnNavigate处理,后者的实现如下所示:
- void RenderFrameImpl::OnNavigate(const FrameMsg_Navigate_Params& params) {
- ……
- bool is_reload = RenderViewImpl::IsReload(params);
- ……
- WebFrame* frame = frame_;
- ……
- if (is_reload) {
- ……
- } else if (params.page_state.IsValid()) {
- ……
- } else if (!params.base_url_for_data_url.is_empty()) {
- ……
- } else {
- // Navigate to the given URL.
- WebURLRequest request(params.url);
- ……
- frame->loadRequest(request);
- ……
- }
- ……
- }
这个函数定义在文件external/chromium_org/content/renderer/render_frame_impl.cc中。
从前面Chromium Frame Tree创建过程分析一文可以知道,RenderFrameImpl类的成员变量frame_指向的是一个WebLocalFrameImpl对象。如果当前正在处理的RenderFrameImpl对象还没有加载过URL,并且当前要加载的URL不为空,RenderFrameImpl类的成员函数OnNavigate会调用成员变量frame_指向的WebLocalFrameImpl对象的成员函数loadRequest加载指定的URL。
WebLocalFrameImpl类的成员函数loadRequest的实现如下所示:
- void WebLocalFrameImpl::loadRequest(const WebURLRequest& request)
- {
- ……
- const ResourceRequest& resourceRequest = request.toResourceRequest();
- if (resourceRequest.url().protocolIs(“javascript”)) {
- loadJavaScriptURL(resourceRequest.url());
- return;
- }
- frame()->loader().load(FrameLoadRequest(0, resourceRequest));
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebLocalFrameImpl.cpp中。
如果参数request描述的URL指定的协议是”javascript”,那么表示要加载的是一段JavaScript。这时候WebLocalFrameImpl类的成员函数loadRequest会调用另外一个成员函数loadJavaScriptURL加载这段JavaScript。
在其它情况下,WebLocalFrameImpl类的成员函数loadRequest首先调用成员函数frame获得成员变量m_frame描述的一个LocalFrame对象,接着又调用这个LocalFrame对象的成员函数loader获得其成员变量m_loader描述的一个FrameLoader对象。有了这个FrameLoader对象之后,就调用它的成员函数load加载参数request描述的URL。
WebLocalFrameImpl类的成员变量m_frame描述的LocalFrame对象和LocalFrame类的成员变量m_loader描述的FrameLoader对象的创建过程,可以参考前面Chromium Frame Tree创建过程分析一文。接下来我们继续分析FrameLoader类的成员函数load的实现,如下所示:
- void FrameLoader::load(const FrameLoadRequest& passedRequest)
- {
- ……
- FrameLoadRequest request(passedRequest);
- ……
- FrameLoadType newLoadType = determineFrameLoadType(request);
- NavigationAction action(request.resourceRequest(), newLoadType, request.formState(), request.triggeringEvent());
- ……
- loadWithNavigationAction(action, newLoadType, request.formState(), request.substituteData(), request.clientRedirect());
- ……
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/FrameLoader.cpp中。
FrameLoader类的成员函数load主要是调用另外一个成员函数loadWithNavigationAction加载参数passedRequest描述的URL。
FrameLoader类的成员函数loadWithNavigationAction的实现如下所示:
- void FrameLoader::loadWithNavigationAction(const NavigationAction& action, FrameLoadType type, PassRefPtrWillBeRawPtr formState, const SubstituteData& substituteData, ClientRedirectPolicy clientRedirect, const AtomicString& overrideEncoding)
- {
- ……
- const ResourceRequest& request = action.resourceRequest();
- ……
- m_policyDocumentLoader = client()->createDocumentLoader(m_frame, request, substituteData.isValid() ? substituteData : defaultSubstituteDataForURL(request.url()));
- ……
- m_provisionalDocumentLoader = m_policyDocumentLoader.release();
- ……
- m_provisionalDocumentLoader->startLoadingMainResource();
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/FrameLoader.cpp中。
FrameLoader类的成员函数loadWithNavigationAction首先调用成员函数client获得一个FrameLoaderClientImpl对象,接着再调用这个FrameLoaderClientImpl对象的成员函数createDocumentLoader为参数action描述的URL创建了一个WebDataSourceImpl对象,并且保存在成员变量m_policyDocumentLoader中。关于FrameLoader类的成员函数client和FrameLoaderClientImpl类的成员函数createDocumentLoader的实现,可以参考前面Chromium Frame Tree创建过程分析一文。
FrameLoader类的成员函数loadWithNavigationAction接下来又将成员变量m_policyDocumentLoader描述的WebDataSourceImpl对象转移到另外一个成员变量m_provisionalDocumentLoader中,最后调用这个WebDataSourceImpl对象的成员函数startLoadingMainResource加载参数action描述的URL。
WebDataSourceImpl类的成员函数startLoadingMainResource是从父类DocumentLoader继承下来的,它的实现如下所示:
- void DocumentLoader::startLoadingMainResource()
- {
- ……
- FetchRequest cachedResourceRequest(request, FetchInitiatorTypeNames::document, mainResourceLoadOptions);
- m_mainResource = m_fetcher->fetchMainResource(cachedResourceRequest, m_substituteData);
- ……
- m_mainResource->addClient(this);
- ……
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/loader/DocumentLoader.cpp中。
从前面Chromium Frame Tree创建过程分析一文可以知道,DocumentLoader类的成员变量m_fetcher描述的是一个ResourceFetcher对象,DocumentLoader类的成员函数startLoadingMainResource调用这个ResourceFetcher对象的成员函数fetchMainResource请求加载本地变量cachedResourceRequest描述的资源。这个资源描述的即为上一步指定要加载的URL。
ResourceFetcher类的成员函数fetchMainResource执行结束后,会返回一个RawResource对象。这个RawResource对象保存在WebDataSourceImpl类的成员变量m_mainResource中。这个RawResource对象描述的是一个异步加载的资源,DocumentLoader类的成员startLoadingMainResource调用它的成员函数addClient将当前正在处理的DocumentLoader对象添加到它的内部去,用来获得异步加载的资源数据,也就是本地变量cachedResourceRequest描述的URL对应的网页内容。
RawResource类的成员函数addClient是从父类Resource继承下来的,它的实现如下所示:
- void Resource::addClient(ResourceClient* client)
- {
- if (addClientToSet(client))
- didAddClient(client);
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。
Resource类的成员函数addClient调用另外一个成员函数addClientToSet将参数client描述的一个DocumentLoader对象保存在内部,如下所示:
- bool Resource::addClientToSet(ResourceClient* client)
- {
- ……
- m_clients.add(client);
- return true;
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。
Resource类的成员函数addClientToSet将参数client描述的一个DocumentLoader保存在成员变量m_clients描述的一个Hash Set中,以便当前正在处理的Resource对象描述的网页内容从Web服务器下载回来的时候,可以交给它处理。
接下来我们继续分析WebDataSourceImpl类的成员函数startLoadingMainResource调用成员变量m_fetcher描述的ResourceFetcher对象的成员函数fetchMainResource加载本地变量cachedResourceRequest描述的URL的过程,如下所示:
- ResourcePtr ResourceFetcher::fetchMainResource(FetchRequest& request, const SubstituteData& substituteData)
- {
- ……
- return toRawResource(requestResource(Resource::MainResource, request));
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp中。
ResourceFetcher类的成员函数fetchMainResource调用另外一个成员函数requestResource加载参数request描述的URL。ResourceFetcher类的成员函数requestResource会返回一个RawResource对象给调用者,即ResourceFetcher类的成员函数fetchMainResource。后者又会将这个RawResource对象返回给它的调用者。
ResourceFetcher类的成员函数requestResource的实现如下所示:
- ResourcePtr ResourceFetcher::requestResource(Resource::Type type, FetchRequest& request)
- {
- ……
- KURL url = request.resourceRequest().url();
- ……
- const RevalidationPolicy policy = determineRevalidationPolicy(type, request.mutableResourceRequest(), request.forPreload(), resource.get(), request.defer(), request.options());
- switch (policy) {
- ……
- case Load:
- resource = createResourceForLoading(type, request, request.charset());
- break;
- …..
- }
- ……
- if (resourceNeedsLoad(resource.get(), request, policy)) {
- ……
- if (!m_documentLoader || !m_documentLoader->scheduleArchiveLoad(resource.get(), request.resourceRequest()))
- resource->load(this, request.options());
- ……
- }
- ……
- return resource;
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp中。
ResourceFetcher类的成员函数requestResource首先调用成员函数createResourceForLoading为参数request描述的URL创建一个RawResource对象,如下所示:
- ResourcePtr ResourceFetcher::createResourceForLoading(Resource::Type type, FetchRequest& request, const String& charset)
- {
- ……
- ResourcePtr resource = createResource(type, request.resourceRequest(), charset);
- ……
- return resource;
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp中。
ResourceFetcher类的成员函数createResourceForLoading调用函数createResource根据参数type和request创建一个RawResource对象,如下所示:
- static Resource* createResource(Resource::Type type, const ResourceRequest& request, const String& charset)
- {
- switch (type) {
- ……
- case Resource::MainResource:
- case Resource::Raw:
- case Resource::TextTrack:
- case Resource::Media:
- return new RawResource(request, type);
- ……
- }
- ……
- return 0;
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceFetcher.cpp中。
从前面的调用过程可以知道,参数type的值等于Resource::MainResource,因此函数createResource创建的是一个RawResource对象。
回到ResourceFetcher类的成员函数requestResource中,它调用成员函数createResourceForLoading为参数request描述的URL创建了一个RawResource对象之后,接下来又调用成员函数resourceNeedsLoad判断该URL是否需要进行加载。如果需要进行加载,那么ResourceFetcher类的成员函数requestResource又会调用成员变量m_documentLoader描述的一个DocumentLoader对象的成员函数scheduleArchiveLoad判断要加载的URL描述的是否是一个存档文件。如果不是,那么就会调用前面创建的RawResource对象的成员函数load从Web服务器下载对应的网页内容。
我们假设request描述的URL需要进行加载,并且不是一个存档文件,因此接下来我们继续分析RawResource类的成员函数load的实现。RawResource类的成员函数load是从父类Resource继承下来的,它的实现如下所示:
- void Resource::load(ResourceFetcher* fetcher, const ResourceLoaderOptions& options)
- {
- ……
- ResourceRequest request(m_resourceRequest);
- ……
- m_loader = ResourceLoader::create(fetcher, this, request, options);
- m_loader->start();
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/Resource.cpp中。
Resource类的成员变量m_resourceRequest描述的是要加载的URL,Resource类的成员函数load首先调用ResourceLoader类的静态成员函数create为其创建一个ResourceLoader对象,如下所示:
- PassRefPtr ResourceLoader::create(ResourceLoaderHost* host, Resource* resource, const ResourceRequest& request, const ResourceLoaderOptions& options)
- {
- RefPtr loader(adoptRef(new ResourceLoader(host, resource, options)));
- loader->init(request);
- return loader.release();
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceLoader.cpp中。
从这里可以看到,ResourceLoader类的静态成员函数create创建的是一个ResourceLoader对象。这个ResourceLoader对象经过初始化之后,会返回给调用者。
回到Resource类的成员函数load中,它为要加载的URL创建了一个ResourceLoader对象之后,会调用这个ResourceLoader对象的成员函数start开始加载要加载的URL,如下所示:
- void ResourceLoader::start()
- {
- ……
- m_loader = adoptPtr(blink::Platform::current()->createURLLoader());
- ……
- blink::WrappedResourceRequest wrappedRequest(m_request);
- m_loader->loadAsynchronously(wrappedRequest, this);
- }
这个函数定义在文件external/chromium_org/third_party/WebKit/Source/core/fetch/ResourceLoader.cpp中。
ResourceLoader类的成员函数start首先调用由Chromium的Content模块实现的一个blink::Platform接口的成员函数createURLLoader创建一个WebURLLoaderImpl对象,接着再调用这个WebURLLoaderImpl对象的成员函数loadAsynchronously对象成员变量m_request描述的URL进行异步加载。
Chromium的Content模块的BlinkPlatformImpl类实现了blink::Platform接口,它的成员函数createURLLoader的实现如下所示:
- WebURLLoader* BlinkPlatformImpl::createURLLoader() {
- return new WebURLLoaderImpl;
- }
这个函数定义在文件external/chromium_org/content/child/blink_platform_impl.cc中。
从这里可以看到,BlinkPlatformImpl类的成员函数createURLLoader创建的是一个WebURLLoaderImpl对象。这个WebURLLoaderImpl对象会返回给调用者。
接下来我们继续分析WebURLLoaderImpl类的成员函数loadAsynchronously异步加载一个URL的过程,如下所示:
- void WebURLLoaderImpl::loadAsynchronously(const WebURLRequest& request,
- WebURLLoaderClient* client) {
- ……
- context_->set_client(client);
- context_->Start(request, NULL);
- }
这个函数定义在文件external/chromium_org/content/child/web_url_loader_impl.cc中。
从前面的调用过程可以知道,参数client描述的是一个ResourceLoader对象。这个ResourceLoader对象会保存在WebURLLoaderImpl类的成员变量content_描述的一个WebURLLoaderImpl::Context对象的内部。这是通过调用WebURLLoaderImpl::Context类的成员函数set_client实现的,如下所示:
- class WebURLLoaderImpl::Context : public base::RefCounted,
- public RequestPeer {
- public:
- ……
- void set_client(WebURLLoaderClient* client) { client_ = client; }
- private:
- ……
- WebURLLoaderClient* client_;
- ……
- };
这个函数定义在文件external/chromium_org/content/child/web_url_loader_impl.cc中。
WebURLLoaderImpl::Context类的成员函数set_client将参数client描述的ResourceLoader对象保存在成员变量client_中。
回到WebURLLoaderImpl类的成员函数loadAsynchronously中,它接下来会继续调用成员变量content_描述的一个WebURLLoaderImpl::Context对象的成员函数Start加载参数request描述的URL,如下所示:
- void WebURLLoaderImpl::Context::Start(const WebURLRequest& request,
- SyncLoadResponse* sync_load_response) {
- ……
- GURL url = request.url();
- ……
- RequestInfo request_info;
- ……
- request_info.url = url;
- ……
- bridge_.reset(ChildThread::current()->resource_dispatcher()->CreateBridge(
- request_info));
- ……
- if (bridge_->Start(this)) {
- AddRef(); // Balanced in OnCompletedRequest
- } else {
- bridge_.reset();
- }
- }
这个函数定义在文件external/chromium_org/content/child/web_url_loader_impl.cc中。
WebURLLoaderImpl::Context类的成员函数Start首先调用当前Render进程的一个ChildThread单例的成员函数resource_dispatcher获得一个ResourceDispatcher对象,如下所示:
- class CONTENT_EXPORT ChildThread
- : public IPC::Listener,
- public IPC::Sender,
- public NON_EXPORTED_BASE(mojo::ServiceProvider) {
- public:
- ……
- ResourceDispatcher* resource_dispatcher() const {
- return resource_dispatcher_.get();
- }
- ……
- private:
- ……
- // Handles resource loads for this process.
- scoped_ptr resource_dispatcher_;
- ……
- };
这个函数定义在文件external/chromium_org/content/child/child_thread.h中。
ChildThread类的成员函数resource_dispatcher返回的是成员变量resource_dispatcher_描述的一个ResourceDispatcher对象。
回到WebURLLoaderImpl::Context类的成员函数Start中,它获得了一个ResourceDispatcher对象之后,接着调用这个ResourceDispatcher对象的成员函数CreateBridge创建一个IPCResourceLoaderBridge对象,如下所示:
- ResourceLoaderBridge* ResourceDispatcher::CreateBridge(
- const RequestInfo& request_info) {
- return new IPCResourceLoaderBridge(this, request_info);
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
从这里可以看到,ResourceDispatcher类的成员函数CreateBridge创建的是一个IPCResourceLoaderBridge对象,并且会将这个IPCResourceLoaderBridge对象返回给调用者。
回到WebURLLoaderImpl::Context类的成员函数Start中,它获得了一个IPCResourceLoaderBridge对象之后,接着调用这个IPCResourceLoaderBridge对象的成员函数Start加载参数request描述的URL,如下所示:
- bool IPCResourceLoaderBridge::Start(RequestPeer* peer) {
- ……
- // generate the request ID, and append it to the message
- request_id_ = dispatcher_->AddPendingRequest(peer,
- request_.resource_type,
- request_.origin_pid,
- frame_origin_,
- request_.url,
- request_.download_to_file);
- return dispatcher_->message_sender()->Send(
- new ResourceHostMsg_RequestResource(routing_id_, request_id_, request_));
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
IPCResourceLoaderBridge类的成员变量dispatcher_描述的是一个ResourceDispatcher对象,IPCResourceLoaderBridge类的成员函数Start首先调用这个ResourceDispatcher对象的成员函数AddPendingRequest将参数peer描述的一个WebURLLoaderImpl::Context对象保存在内部,如下所示:
- int ResourceDispatcher::AddPendingRequest(RequestPeer* callback,
- ResourceType::Type resource_type,
- int origin_pid,
- const GURL& frame_origin,
- const GURL& request_url,
- bool download_to_file) {
- // Compute a unique request_id for this renderer process.
- int id = MakeRequestID();
- pending_requests_[id] = PendingRequestInfo(callback,
- resource_type,
- origin_pid,
- frame_origin,
- request_url,
- download_to_file);
- return id;
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
ResourceDispatcher类的成员函数AddPendingRequest首先调用成员函数MakeRequestID生成一个Request ID,接着将参数callback描述的一个WebURLLoaderImpl::Context对象封装在一个PendingRequestInfo对象中,并且以上述Request ID为键值,将这个PendingRequestInfo对象保存在成员变量pending_requests_描述的一个Hash Map中。
回到IPCResourceLoaderBridge类的成员函数Start中,它接下来调用成员变量dispatcher_描述的ResourceDispatcher对象的成员函数message_sender获得一个IPC::Sender对象,并且通过这个IPC::Sender对象向Browser进程发送一个类型为ResourceHostMsg_RequestResource的IPC消息,用来请求Browser进程下载成员变量request_描述的URL对应的网页的内容。
在Browser进程中,类型为ResourceHostMsg_RequestResource的IPC消息是由ResourceDispatcherHostImpl类的成员函数OnMessageReceived进行接收的,如下所示:
- bool ResourceDispatcherHostImpl::OnMessageReceived(
- const IPC::Message& message,
- ResourceMessageFilter* filter) {
- ……
- bool handled = true;
- IPC_BEGIN_MESSAGE_MAP(ResourceDispatcherHostImpl, message)
- IPC_MESSAGE_HANDLER(ResourceHostMsg_RequestResource, OnRequestResource)
- ……
- IPC_MESSAGE_UNHANDLED(handled = false)
- IPC_END_MESSAGE_MAP()
- ……
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
ResourceDispatcherHostImpl类的成员函数OnMessageReceived将类型为ResourceHostMsg_RequestResource的IPC消息分发给另外一个成员函数OnRequestResource处理,后者的实现如下所示:
- void ResourceDispatcherHostImpl::OnRequestResource(
- int routing_id,
- int request_id,
- const ResourceHostMsg_Request& request_data) {
- BeginRequest(request_id, request_data, NULL, routing_id);
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
ResourceDispatcherHostImpl类的成员函数OnRequestResource调用另外一个成员函数BeginRequest开始下载参数request_data描述的URL对应的网页内容,后者的实现如下所示:
- void ResourceDispatcherHostImpl::BeginRequest(
- int request_id,
- const ResourceHostMsg_Request& request_data,
- IPC::Message* sync_result, // only valid for sync
- int route_id) {
- ……
- // Construct the request.
- net::CookieStore* cookie_store =
- GetContentClient()->browser()->OverrideCookieStoreForRenderProcess(
- child_id);
- scoped_ptr new_request;
- new_request = request_context->CreateRequest(
- request_data.url, request_data.priority, NULL, cookie_store);
- ……
- scoped_ptr handler(
- CreateResourceHandler(
- new_request.get(),
- request_data, sync_result, route_id, process_type, child_id,
- resource_context));
- if (handler)
- BeginRequestInternal(new_request.Pass(), handler.Pass());
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
ResourceDispatcherHostImpl类的成员函数BeginRequest首先从参数request_data取出要下载网页内容的URL,接着又将该URL封装在一个URLRequest对象中。
ResourceDispatcherHostImpl类的成员函数BeginRequest接下来又调用另外一个成员函数CreateResourceHandler创建了一个AsyncResourceHandler对象。这个AsyncResourceHandler对象用来异步接收和处理从Web服务器下载回来的网页内容。
ResourceDispatcherHostImpl类的成员函数CreateResourceHandler的实现如下所示:
- scoped_ptr ResourceDispatcherHostImpl::CreateResourceHandler(
- net::URLRequest* request,
- const ResourceHostMsg_Request& request_data,
- IPC::Message* sync_result,
- int route_id,
- int process_type,
- int child_id,
- ResourceContext* resource_context) {
- // Construct the IPC resource handler.
- scoped_ptr handler;
- if (sync_result) {
- ……
- handler.reset(new SyncResourceHandler(request, sync_result, this));
- } else {
- handler.reset(new AsyncResourceHandler(request, this));
- // The RedirectToFileResourceHandler depends on being next in the chain.
- if (request_data.download_to_file) {
- handler.reset(
- new RedirectToFileResourceHandler(handler.Pass(), request));
- }
- }
- ……
- // Install a CrossSiteResourceHandler for all main frame requests. This will
- // let us check whether a transfer is required and pause for the unload
- // handler either if so or if a cross-process navigation is already under way.
- bool is_swappable_navigation =
- request_data.resource_type == ResourceType::MAIN_FRAME;
- // If we are using –site-per-process, install it for subframes as well.
- if (!is_swappable_navigation &&
- CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess)) {
- is_swappable_navigation =
- request_data.resource_type == ResourceType::SUB_FRAME;
- }
- if (is_swappable_navigation && process_type == PROCESS_TYPE_RENDERER)
- handler.reset(new CrossSiteResourceHandler(handler.Pass(), request));
- // Insert a buffered event handler before the actual one.
- handler.reset(
- new BufferedResourceHandler(handler.Pass(), this, request));
- ……
- handler.reset(
- new ThrottlingResourceHandler(handler.Pass(), request, throttles.Pass()));
- return handler.Pass();
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
从前面的调用过程可以知道,参数sync_result的值等于NULL,因此ResourceDispatcherHostImpl类的成员函数CreateResourceHandler首先创建了一个AsyncResourceHandler对象,保存在本地变量handler中,表示要通过异步方式下载参数request描述的URL。
接下来ResourceDispatcherHostImpl类的成员函数CreateResourceHandler又会根据情况创建其它的Handler对象。这些Handler对象会依次连接在一起。其中,后面创建的Handler对象位于前面创建的Handler对象的前面。下载回来的网页内容将依次被这些Handler对象处理。这意味着下载回来的网页内容最后会被最先创建的AsyncResourceHandler对象进行处理。为了简单起见,后面我们只分析这个AsyncResourceHandler对象处理下载回来的网页内容的过程,也就是假设ResourceDispatcherHostImpl类的成员函数CreateResourceHandler返回给调用者的是一个AsyncResourceHandler对象。
回到ResourceDispatcherHostImpl类的成员函数BeginRequest中,它最后调用另外一个成员函数BeginRequestInternal下载本地变量new_request描述的URL对应的网页内容,如下所示:
- void ResourceDispatcherHostImpl::BeginRequestInternal(
- scoped_ptr request,
- scoped_ptr handler) {
- ……
- ResourceRequestInfoImpl* info =
- ResourceRequestInfoImpl::ForRequest(request.get());
- ……
- linked_ptr loader(
- new ResourceLoader(request.Pass(), handler.Pass(), this));
- …..
- StartLoading(info, loader);
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
ResourceDispatcherHostImpl类的成员函数BeginRequestInternal将参数request描述的URL和参数handler描述的AsyncResourceHandler对象封装在一个ResourceLoader对象后,调用另外一个成员函数StartLoading开始加载参数request描述的URL。
ResourceDispatcherHostImpl类的成员函数StartLoading的实现如下所示:
- void ResourceDispatcherHostImpl::StartLoading(
- ResourceRequestInfoImpl* info,
- const linked_ptr& loader) {
- ……
- loader->StartRequest();
- }
这个函数定义在文件external/chromium_org/content/browser/loader/resource_dispatcher_host_impl.cc中。
ResourceDispatcherHostImpl类的成员函数StartLoading主要是调用参数loader描述的ResourceLoader对象的成员函数StartRequest开始加载其内部封装的URL。
ResourceLoader类的成员函数StartRequest的实现如下所示:
- void ResourceLoader::StartRequest() {
- ……
- // Give the handler a chance to delay the URLRequest from being started.
- bool defer_start = false;
- if (!handler_->OnWillStart(request_->url(), &defer_start)) {
- Cancel();
- return;
- }
- if (defer_start) {
- deferred_stage_ = DEFERRED_START;
- } else {
- StartRequestInternal();
- }
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员变量handler_描述的便是前面我们假设ResourceDispatcherHostImpl类的成员函数CreateResourceHandler返回的AsyncResourceHandler对象。ResourceLoader类的成员函数StartRequest调用这个AsyncResourceHandler对象的成员函数OnWillStart询问是要取消、延迟、还是马上下载当前正在处理的ResourceLoader对象封装的URL对应的网页内容。
我们假设是第三种情况,这时候ResourceLoader类的成员函数StartRequest就会马上调用另外一个成员函数StartRequestInternal下载当前正在处理的ResourceLoader对象封装的URL对应的网页内容。
ResourceLoader类的成员函数StartRequestInternal的实现如下所示:
- void ResourceLoader::StartRequestInternal() {
- ……
- request_->Start();
- ……
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员变量request_描述的是前面在ResourceDispatcherHostImpl类的成员函数BeginRequest中创建的一个URLRequest对象。这个URLRequest对象封装了要下载的URL。ResourceLoader类的成员函数StartRequestInternal通过调用这个URLRequest对象的成员函数Start就可以启动下载网页的过程了。
URLRequest类是Chromium在Net模块中提供的一个类,用来执行具体的网络操作,也就是根据约定的协议请求Web服务器返回指定URL对应的网页的内容。这个过程我们留给读者自行分析。
Web服务器响应了请求之后,Chromium的Net模块会调用ResourceLoader类的成员函数OnResponseStarted,它的实现如下所示:
- void ResourceLoader::OnResponseStarted(net::URLRequest* unused) {
- ……
- if (request_->status().is_success()) {
- StartReading(false); // Read the first chunk.
- }
- ……
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员函数OnResponseStarted检查Web服务器的响应是否成功,例如Web服务器是否根据HTTP协议返回了200响应。如果成功的话,那么接下来就会调用另外一个成员函数StartReading读出第一块数据。
ResourceLoader类的成员函数StartReading的实现如下所示:
- void ResourceLoader::StartReading(bool is_continuation) {
- int bytes_read = 0;
- ReadMore(&bytes_read);
- ……
- if (!is_continuation || bytes_read <= 0) {
- OnReadCompleted(request_.get(), bytes_read);
- } else {
- // Else, trigger OnReadCompleted asynchronously to avoid starving the IO
- // thread in case the URLRequest can provide data synchronously.
- base::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(&ResourceLoader::OnReadCompleted,
- weak_ptr_factory_.GetWeakPtr(),
- request_.get(),
- bytes_read));
- }
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员函数StartReading调用成员函数ReadMore读取Web服务器返回来的数据,读出来的数据大小保存在本地变量bytes_read中。
ResourceLoader类的成员函数ReadMore的实现如下所示:
- void ResourceLoader::ReadMore(int* bytes_read) {
- ……
- scoped_refptr buf;
- int buf_size;
- if (!handler_->OnWillRead(&buf, &buf_size, -1)) {
- Cancel();
- return;
- }
- ……
- request_->Read(buf.get(), buf_size, bytes_read);
- ……
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员函数ReadMore首先调用成员变量handler_描述的一个AsyncResourceHandler对象的成员函数OnWillRead获取一个Buffer。这个Buffer用来保存从Web服务器返回来的数据。这些数据可以通过调用ResourceLoader类的成员变量reqeust_描述的一个URLRequest对象的成员函数Read获得。
AsyncResourceHandler对象的成员函数OnWillRead的实现如下所示:
- bool AsyncResourceHandler::OnWillRead(scoped_refptr* buf,
- int* buf_size,
- int min_size) {
- ……
- if (!EnsureResourceBufferIsInitialized())
- return false;
- ……
- char* memory = buffer_->Allocate(&allocation_size_);
- …..
- *buf = new DependentIOBuffer(buffer_.get(), memory);
- *buf_size = allocation_size_;
- ……
- return true;
- }
这个函数定义在文件external/chromium_org/content/browser/loader/async_resource_handler.cc中。
AsyncResourceHandler对象的成员函数OnWillRead首先调用成员函数EnsureResourceBufferIsInitialized确保成员变量buffer_指向了一块共享内存,然后再从这块共享内存中分配一块大小等于成员变量allocation_size_的值的缓冲区,用来返回给调用者保存从Web服务器返回来的数据。
AsyncResourceHandler类的成员函数EnsureResourceBufferIsInitialized的实现如下所示:
- bool AsyncResourceHandler::EnsureResourceBufferIsInitialized() {
- if (buffer_.get() && buffer_->IsInitialized())
- return true;
- ……
- buffer_ = new ResourceBuffer();
- return buffer_->Initialize(kBufferSize,
- kMinAllocationSize,
- kMaxAllocationSize);
- }
这个函数定义在文件external/chromium_org/content/browser/loader/async_resource_handler.cc中。
AsyncResourceHandler类的成员函数EnsureResourceBufferIsInitialized首先检查成员变量buffer_是否指向了一个ResourceBuffer对象,并且这个ResourceBuffer对象描述的共享内存是否已经创建。
如果AsyncResourceHandler类的成员变量buffer_还没有指向一个ResourceBuffer对象,或者指向了一个ResourceBuffer对象,但是这个ResourceBuffer对象描述的共享内存还没有创建,那么AsyncResourceHandler类的成员函数EnsureResourceBufferIsInitialized就会创建一个ResourceBuffer对象保存在成员变量buffer_中,并且调用这个ResourceBuffer对象的成员函数Initialize创建一块大小为kBufferSize的共享内存。这块共享内存每次可以分配出来的缓冲区最小值为kMinAllocationSize,最大值为kMaxAllocationSize。
在Android平台上,调用ResourceBuffer类的成员函数Initialize创建的共享内存实际上是匿名共享内存。匿名共享内存可以通过Binder机制在两个进程之间进行共享。这一点可以参考前面Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析一文。这样Browser进程就可以通过这块匿名共享内存将下载回来的网页内容传递给Render进程处理。
这一步执行完成后,回到ResourceLoader类的成员函数StartReading中,如果没有读出数据(表明数据已经下载完毕),或者参数is_continuation的值等于false(表示读出来的是第一个数据块),那么ResourceLoader类的成员函数StartReading就会调用成员函数OnReadCompleted马上进行下一步处理。其余情况下,为了避免当前(网络)线程被阻塞,ResourceLoader类的成员函数StartReading并不会马上调用成员函数OnReadCompleted处理读出来的数据,而是延后一个消息处理,也就是等ResourceLoader类的成员函数StartReading返回到Chromium的Net模块之后再作处理。
接下来我们继续分析ResourceLoader类的成员函数OnReadCompleted的实现,如下所示:
- void ResourceLoader::OnReadCompleted(net::URLRequest* unused, int bytes_read) {
- ……
- CompleteRead(bytes_read);
- ……
- if (bytes_read > 0) {
- StartReading(true); // Read the next chunk.
- } else {
- // URLRequest reported an EOF. Call ResponseCompleted.
- DCHECK_EQ(0, bytes_read);
- ResponseCompleted();
- }
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员函数OnReadCompleted首先调用成员函数CompleteRead处理当前读出来的数据,数据的大小由参数bytes_read描述。如果当前读出来的数据的大小大于0,那么就表示数据还没读完,这时候就需要调用前面分析的成员函数StartReading继续进行读取。注意,这时候传递成员函数StartReading的参数为true,表示不是第一次读取Web服务器返回来的数据。
另一方面,如果当前读出来的数据的大小小于等于0,那么就说明Web服务器已经把所有的数据都返回来了,这时候ResourceLoader类的成员函数OnReadCompleted就调用另外一个成员函数ResponseCompleted结束读取数据。
接下来我们继续分析ResourceLoader类的成员函数CompleteRead的实现,以便了解Browser进程将下载回来的网页内容返回给Render进程处理的过程,如下所示:
- void ResourceLoader::CompleteRead(int bytes_read) {
- ……
- bool defer = false;
- if (!handler_->OnReadCompleted(bytes_read, &defer)) {
- Cancel();
- }
- ……
- }
这个函数定义在external/chromium_org/content/browser/loader/resource_loader.cc中。
ResourceLoader类的成员函数CompleteRead将读取出来的数据交给成员变量handler_描述的一个AsyncResourceHandler对象处理,这是通过调用它的成员函数OnReadCompleted实现的。
AsyncResourceHandler类的成员函数OnReadCompleted的实现如下所示:
- bool AsyncResourceHandler::OnReadCompleted(int bytes_read, bool* defer) {
- ……
- if (!sent_first_data_msg_) {
- base::SharedMemoryHandle handle;
- int size;
- if (!buffer_->ShareToProcess(filter->PeerHandle(), &handle, &size))
- return false;
- filter->Send(new ResourceMsg_SetDataBuffer(
- GetRequestID(), handle, size, filter->peer_pid()));
- sent_first_data_msg_ = true;
- }
- int data_offset = buffer_->GetLastAllocationOffset();
- int64_t current_transfer_size = request()->GetTotalReceivedBytes();
- int encoded_data_length = current_transfer_size – reported_transfer_size_;
- reported_transfer_size_ = current_transfer_size;
- filter->Send(new ResourceMsg_DataReceived(
- GetRequestID(), data_offset, bytes_read, encoded_data_length));
- ……
- }
这个函数定义在文件external/chromium_org/content/browser/loader/async_resource_handler.cc。
当AsyncResourceHandler类的成员变量sent_first_data_msg_的值等于false的时候,表示当前正在处理的AsyncResourceHandler对象还没有向Render进程返回过从Web服务器下载回来的网页内容。这时候AsyncResourceHandler类的成员函数OnReadCompleted首先要向Render进程发送一个类型为ResourceMsg_SetDataBuffer的IPC消息。这个IPC消息会将AsyncResourceHandler类的成员变量buffer_描述的共享内存传递给Render进程,以便Render进程接下来可以通过这块共享内存读取从Web服务器下载回来的网页内容。
最后,AsyncResourceHandler类的成员函数OnReadCompleted再向Render进程发送一个类型为ResourceMsg_DataReceived的IPC消息。这个IPC消息告诉Render进程从前面所描述的共享内存的什么位置开始读取多少数据。有了这些数据之后,Render进程就可以构建网页的DOM Tree了。
接下来我们就继续分析Render进程接收和处理类型为ResourceMsg_SetDataBuffer和ResourceMsg_DataReceived的IPC消息的过程。
Render进程是通过ResourceDispatcher类的成员函数DispatchMessage接收类型为ResourceMsg_SetDataBuffer和ResourceMsg_DataReceived的IPC消息的,如下所示:
- void ResourceDispatcher::DispatchMessage(const IPC::Message& message) {
- IPC_BEGIN_MESSAGE_MAP(ResourceDispatcher, message)
- ……
- IPC_MESSAGE_HANDLER(ResourceMsg_SetDataBuffer, OnSetDataBuffer)
- IPC_MESSAGE_HANDLER(ResourceMsg_DataReceived, OnReceivedData)
- ……
- IPC_END_MESSAGE_MAP()
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
从这里可以看到,ResourceDispatcher类的成员函数DispatchMessage把类型为ResourceMsg_SetDataBuffer的IPC消息分发给成员函数OnSetDataBuffer处理,把类型为ResourceMsg_DataReceived的IPC消息分发给成员函数OnReceivedData处理。
ResourceDispatcher类的成员函数OnSetDataBuffer的实现如下所示:
- void ResourceDispatcher::OnSetDataBuffer(int request_id,
- base::SharedMemoryHandle shm_handle,
- int shm_size,
- base::ProcessId renderer_pid) {
- ……
- PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
- ……
- request_info->buffer.reset(
- new base::SharedMemory(shm_handle, true)); // read only
- bool ok = request_info->buffer->Map(shm_size);
- ……
- request_info->buffer_size = shm_size;
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
从前面的分析可以知道,Render进程在请求Browser进程下载指定URL对应的网页内容之前,会创建一个PendingRequestInfo对象。这个PendingRequestInfo对象以一个Request ID为键值保存在ResourceDispatcher类的内部。这个Request ID即为参数request_id描述的Request ID。因此,ResourceDispatcher类的成员函数OnSetDataBuffer可以通过参数request_id获得一个PendingRequestInfo对象。有了这个PendingRequestInfo对象之后,ResourceDispatcher类的成员函数OnSetDataBuffer就根据参数shm_handle描述的句柄创建一个ShareMemory对象,保存在它的成员变量buffer中。
ResourceDispatcher类的成员函数OnSetDataBuffer最后调用上述ShareMemory对象的成员函数Map即可将Browser进程传递过来的共享内存映射到当前进程的地址空间来,这样以后就可以直接从这块共享内存读出Browser进程下载回来的网页内容。
ResourceDispatcher类的成员函数OnReceivedData的实现如下所示:
- void ResourceDispatcher::OnReceivedData(int request_id,
- int data_offset,
- int data_length,
- int encoded_data_length) {
- ……
- PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
- ……
- if (request_info && data_length > 0) {
- ……
- linked_ptr retain_buffer(request_info->buffer);
- ……
- const char* data_start = static_cast<char*>(request_info->buffer->memory());
- ……
- const char* data_ptr = data_start data_offset;
- ……
- // Check whether this response data is compliant with our cross-site
- // document blocking policy. We only do this for the first packet.
- std::string alternative_data;
- if (request_info->site_isolation_metadata.get()) {
- request_info->blocked_response =
- SiteIsolationPolicy::ShouldBlockResponse(
- request_info->site_isolation_metadata, data_ptr, data_length,
- &alternative_data);
- request_info->site_isolation_metadata.reset();
- // When the response is blocked we may have any alternative data to
- // send to the renderer. When |alternative_data| is zero-sized, we do not
- // call peer\’s callback.
- if (request_info->blocked_response && !alternative_data.empty()) {
- data_ptr = alternative_data.data();
- data_length = alternative_data.size();
- encoded_data_length = alternative_data.size();
- }
- }
- if (!request_info->blocked_response || !alternative_data.empty()) {
- if (request_info->threaded_data_provider) {
- request_info->threaded_data_provider->OnReceivedDataOnForegroundThread(
- data_ptr, data_length, encoded_data_length);
- // A threaded data provider will take care of its own ACKing, as the
- // data may be processed later on another thread.
- send_ack = false;
- } else {
- request_info->peer->OnReceivedData(
- data_ptr, data_length, encoded_data_length);
- }
- }
- ……
- }
- ……
- }
这个函数定义在文件external/chromium_org/content/child/resource_dispatcher.cc中。
ResourceDispatcher类的成员函数OnReceivedData首先获得参数request_id对应的一个PendingRequestInfo对象,保存在本地变量request_info中。有了这个PendingRequestInfo对象之后,就可以根据参数data_offset和data_length从它的成员变量buffer描述的共享内存中获得Browser进程下载回来的网页内容。
如果这是一个跨站(cross-site)请求下载回来的内容,ResourceDispatcher类的成员函数OnReceivedData会调用SiteIsolationPolicy类的静态成员函数ShouldBlockResponse根据Cross-Site Document Blocking Policy决定是否需要阻止下载回来的内容在当前Render进程中加载。关于Chromium的Cross-Site Document Blocking Policy,可以参考Site Isolation和Blocking Cross-Site Documents for Site Isolation这两篇文章。
如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse表明要阻止下载回来的内容在当前Render进程中加载,那么本地变量request_info指向的PendingRequestInfo对象的成员变量blocked_response的值就会等于true。这时候如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse还返回了Alternative Data,那么这个Alternative Data就会替换下载回来的网页内容交给WebKit处理。
如果SiteIsolationPolicy类的静态成员函数ShouldBlockResponse没有阻止下载回来的内容在当前Render进程中加载,或者阻止的同时也提供了Alternative Data,那么ResourceDispatcher类的成员函数OnReceivedData接下来继续判断本地变量request_info指向的PendingRequestInfo对象的成员变量threaded_data_provider是否指向了一个ThreadedDataProvider对象。如果指向了一个ThreadedDataProvider对象,那么ResourceDispatcher类的成员函数OnReceivedData会将下载回来的网页内容交给这个ThreadedDataProvider对象的成员函数OnReceivedDataOnForegroundThread处理。否则的话,下载回来的网页内容将会交给本地变量request_info指向的PendingRequestInfo对象的成员变量peer描述的一个WebURLLoaderImpl::Context对象的成员函数OnReceivedData处理。
WebKit在请求Chromium的Content模块下载指定URL对应的网页内容时,可以指定将下载回来的网页内容交给一个后台线程进行接收和解析,这时候本地变量request_info指向的PendingRequestInfo对象的成员变量threaded_data_provider就会指向一个ThreadedDataProvider对象。这个ThreadedDataProvider对象就会将下载回来的网页内容交给一个后台线程接收和解析。我们不考虑这种情况,因此接下来我们继续分析WebURLLoaderImpl::Context类的成员函数OnReceivedData的实现,如下所示:
- void WebURLLoaderImpl::Context::OnReceivedData(const char* data,
- int data_length,
- int encoded_data_length) {
- ……
- if (ftp_listing_delegate_) {
- // The FTP listing delegate will make the appropriate calls to
- // client_->didReceiveData and client_->didReceiveResponse.
- ftp_listing_delegate_->OnReceivedData(data, data_length);
- } else if (multipart_delegate_) {
- // The multipart delegate will make the appropriate calls to
- // client_->didReceiveData and client_->didReceiveResponse.
- multipart_delegate_->OnReceivedData(data, data_length, encoded_data_length);
- } else {
- client_->didReceiveData(loader_, data, data_length, encoded_data_length);
- }
- }
这个函数定义在文件external/chromium_org/content/child/web_url_loader_impl.cc中。
当从Web服务器返回来的网页内容的MIME类型为“text/vnd.chromium.ftp-dir”时,WebURLLoaderImpl::Context类的成员变量ftp_listing_delegate_指向一个FtpDirectoryListingResponseDelegate对象。这时候从Web服务器返回来的网页内容是一些FTP目录,上述FtpDirectoryListingResponseDelegate对象对这些网页内容进行一些排版处理后,再交给WebKit处理,也就是ResourceLoader类的成员变量client_描述的一个ResourceLoader对象处理。
当从Web服务器返回来的网页内容的MIME类型为“multipart/x-mixed-replace”时,WebURLLoaderImpl::Context类的成员变量multipart_delegate_指向一个MultipartResponseDelegate对象。这时候从Web服务器返回来的网页内容包含若干个数据块,每一个数据块都有单独的MIME类型,并且它们之间通过一个Boundary String。上述MultipartResponseDelegate对象根据Boundary String解析出每一数据块之后,再交给WebKit处理,也就是ResourceLoader类的成员变量client_描述的一个ResourceLoader对象处理。
在其余情况下,WebURLLoaderImpl::Context类的成员函数OnReceivedData直接把Web服务器返回来的网页内容交给WebKit处理,也就是调用ResourceLoader类的成员变量client_描述的一个ResourceLoader对象的成员函数didReceiveData进行处理。
至此,我们就分析完成Chromium下载指定URL对应的网页内容的过程了。下载回来的网页内容将由WebKit进行处理,也就是由ResourceLoader类的成员函数didReceiveData进行处理。这个处理过程即为网页内容的解析过程,解析后就会得到一棵DOM Tree。有了DOM Tree之后,接下来就可以对下载回来的网页内容进行渲染了。在接下来的一篇文章中,我们再详细分析WebKit根据网页内容生成DOM Tree的过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
本文探讨一下chromium中加载URL的流程,具体来说是从地址栏输入URL地址到通过URLRequest类请求http流的过程。
为避免繁琐, URL请求过程中的NavigationController类和WebContents类姑且略过,直接从RenderViewHostImpl::Navigate方法下手,该方法通过ViewMsg_Navigate消息,将URL请求信息发送到render进程。Render进程中通过RenderViewImpl类的OnNavigate方法响应该请求,
该方法将URL请求信息封装成一个WebURLRequest对象,调用WebKit层WebFrameImpl对象的loadRequest方法。这里有必要简单介绍下几个关键类之间的关系:
RenderViewImpl派生自RenderView类,是Render进程中网页展示部分,对应于browser进程中的RenderViewHostImpl类,负责与browser进程进行IPC消息的通讯、对网页中的诸多行为进行响应。
RenderViewImpl维护一个WebView对象,两者通过一个map对象一一映射。
在WebView接口的实现类中,有一个Page成员,表示整个页面, Page类中又有一个或多个Frame对象,至少有一个主Frame。WebFrame接口应该是Page中主Frame的public访问代理,其关联操作发生在WebViewImpl类的initializeMainFrame方法里。
言归正传,WebFrameImpl的loadRequest方法最终会将请求转给FrameLoader的load方法,
FrameLoader是Frame的加载器。FrameLoader会将该请求委派给内部DocumentLoader成员的startLoadingMainResource方法来处理,再往下探讨之前,先把类之间的结构关系梳理一下:
DocumentLoader类:做为FrameLoader的成员,负责Document的加载。
ResourceFetcher类:资源提取器,相当于Resource的工厂类,负责创建不同的资源类型
的Resource封装。比如Image资源对应的是ImageResource封装类,
Script对应的是ScriptResource封装类。
在这里DocumentLoader调用ResourceFetcher的fetchMainResource方法加载Document资源, 创建的封装类为RawResource,创建完成后调用Resource的load方法,内部创建ResourceLoader类来执行具体的加载行为。
分析到ResourceLoader类,方有拨开云雾见日月的感觉,该类内部维护一个WebURLLoader接口,顾名思义,这才是真正执行URL加载的地方,不过只是个抽象接口,隶属WebKit的public接口部分,与平台相关,chromium对此接口有自己的实现,位于webkit_glue层 的WebURLLoaderImpl实现WebURLLoader接口,相关类结构如下:
IPCResourceLoaderBridge将URL请求任务封装成ResourceHostMsg_RequestResource
消息发送到browser进程,由browser进程请求URL数据。
自此,URL请求消息由browser进程抛到render进程,之后绕一大圈又回到browser进程执行任务, 路由过程中将页面的框架搭建起来, 之后由browser进程的URLRequest请求到的网络数据发送到render进程所做的解析操作都是对页面框架的添砖加瓦。
接下来分析一下browser进程对render进程抛送过来的URL请求的处理。
在ResourceDispatcherHostImpl类的OnRequestResource方法对render进程发送过来的ResourceHostMsg_RequestResource消息进行响应,相关类结构如下:
每收到一个URL请求, ResourceDispatcherHostImpl会创建一个ResourceLoader对象来承接URL的加载任务, ResourceLoader封装URLRequest类的使用,并将请求过程中的相关事件和数据分派到ResourceHandler链中。ResourceHandler链中的AsyncResourceHandler类负责将请求事件和数据封装消息发送到render进程中。Render进程ResourceDispatcher类来响应这些消息。
Chromium源码实在庞大,很多流程以前都看过,没做记录很快就没了印象,还是需要把学习的成果总结出来已做备忘。
chromium内核代码一直在更新,最近又有了大动作。尤其是IPC通信部分,因为性能问题,传统的IPC已经被弃用,虽然不是完全舍弃,但除了严重依赖于时序关系的Navigate相关消息外,其他的所有IPC::Channel都被替换成了mojom。这就导致以前的FrameMsg_Navigate、ResourceHostMsg_RequestSource等IPC消息在chromium代码中不再可见了。因为大体的流程没有改变,有兴趣的可以回顾一下老罗的文章,这里只是从头梳理一下chromium69版本内核代码加载一个url的整个过程,以及该过程中涉及到的一些重要的类和方法。
(个人理解得不够深刻,如有问题,请留言指教,不胜感激)
用户在地址栏输入一个URL,浏览器加载该URL的过程,被称为“Navigate”,所以该过程中会有很多类和方法名中,包含有该单词,如有遇到,可以认为该类或方法很可能和URL加载过程相关。
第一阶段(发送请求):
1. WebContents
在浏览器中代表一个页面的实体,它是一个抽象类,实现于WebContentsImpl。每个WebContents对象都会有一个NavigationController,它管理着URL的前进/后退列表,负责加载URL到WebContents中。所以,浏览器在导航URL的时候,会首先创建一个WebContentsImpl对象,调用其GetController()方法,获取到其NavigationController成员。获取到与WebContents关联的NavigationController后,浏览器一般会调用它的LoadURLWithParams()方法进行目标URL的加载工作。
2. NavigationController
每个WebContents里都关联着一个NavigationController对象,且每个NavigationController也只被关联在一个WebContents中,二者一一对应。NavigationController也是一个抽象类,它的实现类的类名,也是在后面加个”Impl”,即NavigationControllerImpl。当其成员函数LoadURLWithParams()方法被调用后,它会在进行简单的url判断后,调用成员函数NavigateWithoutEntry()继续处理。该函数实现里主要做了三件事:
1. 确定目标URL所要加载在FrameTree中的哪个node上,即得到一个FrameTreeNode对象;
2. 创建一个NavigationEntry对象,用于组建一个NavigationRequest;
3. 输送NavigationRequest到目标FrameTreeNode的NavigatorImpl对象的Navigate()方法中。
3. Navigator
该类负责在一棵FrameTree的节点中执行URL导航操作,可以被同一棵FrameTree上的多个FrameTreeNode所共享,但不能被多棵FrameTree的子节点所共享。该类是一个抽象类,实现类为NavigatorImpl。Navigate()方法中,先判断当前指定的FrameTreeNode所代表的网页中是否有悬挂的BeforeUnload事件处理器需要执行,如果有,则先执行BeforeUnload事件处理程序,稍后派发NavigationRequest到FrameTreeNode;如果没有,则立即派发。派发形式如下:
1. 调用FrameTreeNode的CreateNavigationRequest()方法,将NavigationRequest对象存储;
2. 调用FrameTreeNode中NavigationRequest对象的BeginNavigation()方法进行加载。
BeforeUnload事件的判断处理,是在第一步和第二步中间。
4. NavigationRequest
该类存在于UI线程,确保URL请求会在IO线程中的ResourceDispatcherHost中执行,描述UI线程和IO线程之间的交互。该类先对目标URL进行了内容安全策略检查,以及注册了各种NavigationThrottles对目标URL进行审批,最终会创建一个NavigationURLLoader对象。
5. NavigationURLLoader
该类实现类NavigationURLLoaderImpl构造函数中,进行线程调度,在IO线程中执行StartWithoutNetworkService()方法,并在该方法中调用ThrottlingURLLoader::CreateLoaderAndStart()方法创建了一个ThrottlingURLLoader对象。
6. ThrottlingURLLoader
该类继承于network::mojom::URLLoaderClient,可以进行IPC通信,且Render进程和Browser进程都有其实例化对象。类名中带着”Throttling”的,且和URL加载相关的类,通常都会根据某些自定义规则,对网络数据进行拦截过滤处理,就像一个瓶塞一样。它的CreateLoaderAndStart()方法,创建完自身的一个实例对象后,调用其Start()方法。Start方法接收一个SharedURLLoaderFactory类实例(该实例是在NavigationURLLoaderImpl的StartWithNetworkService()方法中创建的),并调用SharedURLLoaderFactory实例的CreateLoaderAndStart()。
7. SingleRequestURLLoaderFactory
该类继承于network::SharedURLLoaderFactory,而SharedURLLoaderFactory又继承于mojom::URLLoaderFactory。URLLoaderFactory这一系列的近亲类(比如WebUIURLLoaderFactory、FileURLLoaderFactory、CORSURLLoaderFactory等),都可以创建一个mojom::URLLoader对象,既可以跨进程加载url并得到返回数据,又可以同进程加载url,该性质来自于mojom的调用机制。
SingleRequestURLLoaderFactory的CreateLoaderAndStart方法中执行回调函数,调用堆栈返回到了URLLoaderRequestController::CreateNonNetworkServiceURLLoader(),该方法调用ResourceDispatcherHostImpl类的BeginNavigationRequest()。
8. ResourceDispatcherHostImpl
ResourceDispatcher和ResourseDispatcherHost分别是Render进程和Borwser进程进行资源分发的接口类。
在BeginNavigationRequest()方法中创建了一个URLRequest对象,在BeginRequestInternal()方法中创建了一个ResourceLoader对象,然后在StartLoading()方法中,调用ResourceLoader对象的StartRequest()方法开始加载请求。
9. ResourceLoader
该类集中接收转发URLRequest、SSLErrorHandler、SSLClientAuthHandler、ResourceHandler相关的事件。
该类StartRequestInternal()方法中,直接调用了URLRequest对象的Start()方法,至此结束了Navigate()的第一个过程。
第二阶段(数据响应):
网络模块获取到响应头数据,数据流向及处理方法。
ResourceLoader类的ResponseCompleted()方法被调用,然后通过ResourceLoader成员变量handler的OnResponseCompleted()方法向上传递数据。主要的handler类有MimeSniffingResourceHandler、CrossSiteDocumentResourceHandler、InterceptingResourceHandler、MojoAsyncResourceHandler等,各个handler都可以对数据进行截获处理,最终NavigationRequest类的OnResponseStarted()方法被调用。该方法最终调用到RenderFrameHostImpl::CommitNavigation(),RenderFrameHostImpl发送了一个IPC消息到Render进程。
第三阶段(Render进程发起主要资源(一般指html文件)网络请求):
1. RenderFrameImpl
CommitNavigation()函数除了携带response_header、request_params等基本信息,还有mojom通信相关接口url_loader_client_endpoints和Browser进程目前所支持的URLLoaderFactory列表subresource_loader_factories。关于mojom接口的绑定过程,参考Converting Legacy Chrome IPC To Mojo一文,这里不详细赘述。参数subresource_loader_factories是一个Bundle,包裹着从Browser进程传递过来的各种URLLoaderFactory,前面说过,URLLoaderFactory可以跨进程进行资源请求,而不同的URLLoaderFactory用来请求不同scheme的资源。比如WebUIURLLoaderFactory用来请求浏览器内置页面,url格式一般类似于chrome://page;再比如FileSystemURLLoaderFactory用来请求本地资源,url格式类似于file:///C:\\test.txt。进行网络请求的时候,Render进程去factory列表里根据url的scheme里查找对应的URLLoaderFactory,调用它的CreateLoaderAndStart()方法进行资源请求。
RenderFrameImpl类的CommitNavigation()方法被mojom消息调起,它根据消息携带的header信息、request参数信息以及url_loader_client_endpoints创建一个WebURLRequest对象,并调用成员变量frame_的CommitNavigation()将其传递过去。RenderFrameImpl的成员变量frame_指向了WebLocalFrameImpl,WebLocalFrameImpl接收到WebURLRequest对象后,将其转换成FrameLoadRequest类型,然后调用传递给FrameLoader类的CommitNavigation()函数。
2. FrameLoader
CommitNavigation()接收到FrameLoadRequest后,直接调用了StartLoad()函数,在StartLoad()函数中,创建并用FrameLoadRequest参数初始化了一个DocumentLoader对象,然后调起DocumentLoader对象的StartLoading()方法。
3. DocumentLoader
StartLoading()函数准备好request、fetcher等参数,调用RawResource类的静态方法FetchMainResource()去请求主要资源。
4. RawResource
FetchMainResource()函数,根据Resoure::kMainResource类型去创建一个ResourceFactory对象,同FetchParameters对象一起作为参数,调起参数列表中fetcher的RequestResource()函数。
5. ResourceFetcher
RequestResource()函数创建Resource对象,调用StartLoad()方法。StartLoad()方法创建一个ResourceLoader对象,调用loader的Start()方法。
6. ResourceLoader
Start()方法调用ResourceLoaderScheduler::Request(),最终回调到ResourceLoader::StartWith()方法中。ResourceLoader的StartWith()调用WebURLLoaderImpl类的LoadAsynchronously()方法。
7. WebURLLoaderImpl
LoadAsynchronously()通过自己的成员变量context_,对request进行加载。最终从WebURLLoaderImpl::Context::Start()方法中调用了ResourceDispatcher类的StartAsync()方法。
8. ResourceDispatcher
调用ThrottlingURLLoader类的CreateLoaderAndStart()方法。
9. ThrolltingURLLoader
Start()函数接收一个SharedURLLoaderFactory和一个ResourceRequest参数,调用factory的CreateLoaderAndStart()方法。
10. ChildURLLoaderFactoryBundle
CreateLoaderAndStart()方法被调起,参数包含network::ResourceRequest和一个network::mojom::URLLoaderRequest对象,根据request.url获取对应的URLLoaderFactory,然后调用该factory的CreateLoaderAndStart()发送跨进程IPC消息到Browser Process。
第四阶段(请求主要资源):
Browser进程接收CreateLoaderAndStart()方法的跨进程调用的位置,是在ResourceMessageFilter类的同名方法CreateLoaderAndStart()。 该类有一个成员变量url_loader_factory_,指向CORSURLLoaderFactory,且该类的CreateLoaderAndStart()方法被调用。最终传递到URLLoaderFactoryImpl::CreateLoaderAndStart(),该方法获取全局的ResourceDispatcherHostImpl实例,调用其OnRequestResourceWithMojo(),代替以前的ResourceHostMsg_RequestResource消息。ResourceDispatcherHostImpl接收来自Render进程的网络请求相关参数,调用OnRequestResourceInternal()方法开始对该请求进行加载。过程同第一阶段相同。
当数据请求有结果时,同样是几个Handler类的OnReadCompleted()方法最先被调用,然后通过network::mojom::URLLoaderClientProxy类的OnStartLoadingResponseBody()方法将数据结果跨进程通知回Render进程。
第五阶段(接收处理主要资源,发起子资源请求):
同名方法OnStartLoadingResponseBody()被调用,分别经过以下几个类,最终到达HTMLTreeBuilder:
URLResponseBodyConsumer::OnReadable()
WebURLLoaderImpl::RequestPeerImpl::OnReceivedData()
WebURLLoaderImpl::Context::OnReceivedData()
ResourceLoader::DidReceviedData()
RawResource::AppendData()
DocumentLoader::DataReceived() ::ProcessData() ::CommitData() ::InstallNewDocument()
HTMLDocumentParser::AppendBytes() ::PumpPendingSpeculations() ::ProcessTokenizedChunkFromBackgroundParser()
HTMLTreeBuilder::ConstructTree()
在构建DOM树的时候,如果发现一个子节点需要加载资源,比如css文件。则HTMLDocumentParser类的DocumentElementAvailable()方法会被调用,然后调用自身的资源预加载器preloader_的TakeAndPreload()对资源进行加载。之后的调用过程如下:
HTMLResourcePreloader::Preload()
PreloadRequest::Start()
DocumentLoader::StartPreload()
CSSStyleSheetResource::Fetch()
ResourceFetcher::RequestResource()
过程同上…..
ChildURLLoaderFactoryBundle::CreateLoaderAndStart()
以下是堆栈调用的一个大概过程:
- content::NavigationControllerImpl::LoadURLWithParams()
- content::NavigationControllerImpl::NavigateWithoutEntry()
- content::NavigatorImpl::Navigate()
- content::NavigationRequest::BeginNavigation()
- content::NavigationHandleImpl::WillStartRequest()
- content::NavigationRequest::OnStartCehcksComplete()
- content::NavigationURLLoader::Create()
- content::NavigationURLLoaderImpl::NavigationURLLoaderImpl()
- content::NavigationURLLoaderImpl::StartWithoutNetworkService()
- content::ThrottlingURLLoader::CreateLoaderAndStart()
- content::ThrottlingURLLoader::Start()
- content::ThrottlingURLLoader::StartNow()
- content::SingleRequestURLLoaderFactory::CreateLoaderAndStart()
- content::SingleRequestURLLoaderFactory::HandleRequest()
- content::NavigationURLLoaderImpl::URLLoaderRequestController::CreateNonNetworkServiceURLLoader();
- content::ResourceDispatcherHostImpl::BeginNavigationRequest()
- content::ResourceDispatcherHostImpl::BeginNavigationRequestInternal() –> content::ResourceLoader::ResourceLoader()
- content::ResourceDispatcherHostImpl::StartLoading()
- content::ResourceLoader::StartRequest()
- content::ResourceLoader::ScopedDeferral::~ScopedDeferral() //判断状态
- content::ResourceLoader::Resume()
- content::ResourceLoader::StartRequestInternal()
- net::URLRequest::Start()
- content::NavigationURLLoaderImpl::OnReceiveResponse()
- content::NavigationRequest::OnResponseStarted()
- content::NavigationHandleImpl::WillProcessResponse()
- content::NavigationRequest::OnWillProcessResponseChecksComplete()
- content::NavigationRequest::CommitNavigation()
- content::RenderFrameHostImpl::CommitNavigation()
- content::mojom::FrameNavigationControlProxy::CommitNavigation() // Send IPC Message To Render Process
- —————–Render Process————————————-
- ………
- content::RenderFrameImpl::CommitNavigation()
- blink::WebLocalFrameImpl::CommitNavigation()
- blink::FrameLoader::CommitNavigation()
- blink::FrameLoader::StartLoad()
- blink::DocumentLoader::StartLoading()
- blink::RawResource::FetchMainResource()
- blink::ResourceFetcher::RequestResource()
- blink::ResourceFetcher::StartLoad()
- blink::ResourceLoader::Start()
- blink::ResourceLoaderScheduler::Request()
- blink::ResourceLoaderScheduler::Run()
- blink::ResourceLoader::Run()
- blink::ResourceLoader::StartWith(blink::ResourceRequest& request)
- content::WebURLLoaderImpl::LoadAsynchronously()
- content::WebURLLoaderImpl::Context::Start()
- content::ResourceDispatcher::StartAsync()
- content::ThrottlingURLLoader::CreateLoaderAndStart()
- content::ThrottlingURLLoader::Start()
- content::ThrottlingURLLoader::StartNow()
- content::ChildURLLoaderFactoryBundle::CreateLoaderAndStart()
- network::mojom::URLLoaderFactoryProxy::CreateLoaderAndStart()
- ————————————————-Browser Process—————–
- content::ResourceMessageFilter::CreateLoaderAndStart()
- network::cors::CORSURLLoaderFactory::CreateLoaderAndStart()
- content::URLLoaderFactoryImpl::CreateLoaderAndStart()
- content::ResourceDispatcherHostImpl::OnRequestResourceWithMojo()
- content::ResourceDispatcherHostImpl::OnRequestResourceInternal()
- content::ResourceDispatcherHostImpl::BeginRequest()
- content::ResourceDispatcherHostImpl::StartLoading()
- content::ResourceLoader::StartRequest()
- content::ResourceLoader::ScopedDeferral::~ScopedDeferral() //判断状态
- content::ResourceLoader::Resume()
- content::ResourceLoader::StartRequestInternal()
- net::URLRequest::Start()
- content::LayeredResourceHandler::OnReadCompleted()
- content::InterceptingResourceHandler::OnReadCompleted()
- maxthon::MxResourceSnifferHandler::OnReadCompleted()
- content::MojoAsyncResourceHandler::OnReadCompleted()
- network::mojom::URLLoaderClientProxy::OnStartLoadingResponseBody()
- ———————–Renderer Process————————————-
- content::URLLoaderClientImpl::OnStartLoadingResponseBody()
- content::URLResponseBodyConsumer::OnReadable()
- content::WebURLLoaderImpl::RequestPeerImpl::OnReceivedData()
- content::WebURLLoaderImpl::Context::OnReceivedData()
- blink::ResourceLoader::DidReceiveData()
- blink::RawResource::AppendData()
- blink::Resource::AppendData()
- blink::DocumentLoader::DataReceived()
- blink::DocumentLoader::ProcessData()
- blink::DocumentLoader::CommitData()
- blink::HTMLDocumentParser::AppendBytes()
- blink::DocumentLoader::CommitNavigation()
- blink::DocumentLoader::InstallNewDocument()
- blink::HTMLDocumentParser::PumpPendingSpeculations()
- blink::HTMLDocumentParser::ProcessTokenizedChunkFromBackgroundParser()
- blink::HTMLTreeBuilder::ConstructTree()
- blink::HTMLTreeBuilder::ProcessToken()
- blink::HTMLTreeBuilder::ProcessStartTag()
- blink::HTMLConstructionSite::InsertHTMLHtmlStartTagBeforeHTML()
- blink::HTMLHtmlElement::InsertedByParser()
- blink::HTMLDocumentParser::DocumentElementAvailable()
- blink::ResourcePreloader::TakeAndPreload()
- blink::HTMLResourcePreloader::Preload()
- blink::PreloadRequest::Start()
- blink::DocumentLoader::StartPreload()
- blink::CSSStyleSheetResource::Fetch()
- blink::ResourceFetcher::RequestResource()
- blink::ResourceFetcher::StartLoad()
- blink::ResourceLoader::Start()
- blink::ResourceLoaderScheduler::Request()
- blink::ResourceLoaderScheduler::Run()
- blink::ResourceLoader::Run()
- blink::ResourceLoader::StartWith(blink::ResourceRequest& request)
- content::WebURLLoaderImpl::LoadAsynchronously()
- content::WebURLLoaderImpl::Context::Start()
- content::ResourceDispatcher::StartAsync()
- content::ThrottlingURLLoader::CreateLoaderAndStart()
- content::ThrottlingURLLoader::Start()
- content::ThrottlingURLLoader::StartNow()
- content::ChildURLLoaderFactoryBundle::CreateLoaderAndStart(n)
- network::mojom::URLLoaderFactoryProxy::CreateLoaderAndStart()
今天先列一个大纲,详细叙述需要慢慢填充,望大家谅解!
上一节我们介绍了Chrome对于DNS协议的解析,今天我们继续介绍一个更为大家所熟知的协议,HTTP协议。
HTTP协议是一种很常见的协议,在chromium网络库中,对HTTP的解析主要是分为两部分,一部分是去缓存数据的获取,另外一部分则是重新加载网络资源。简单点说就是当我们在浏览器中输入 http://www.bytedance.net 的时候,优先去浏览器中的缓存数据查找是否存在相应的资源,如果存在则直接加在缓存中的数据,否则就需要建立TCP连接,进行网络数据的加载。对于刚才举的那个例子,要看在chrome中是否已经有缓存数据存在,只需要在浏览器中输入 chrome://cache/ 即可查看:
和DNS一样,在net网络库中的HTTP源码文件多达上百个,一个个去看效率低并且也学不到什么有用的东西,还是像上次一样先找出其主流程,分析关键函数,了解整个过程的来龙去脉,最后才是逐个击破。
可以把HTTP的资源加载看作是浏览器解析一个用户指定的URL的过程(实际上URL不仅仅包含http,还包含https、ftp等等协议),而整个chrome的net库对外的一个大的接口函数就是UrlRequest,顾名思义,这个函数代表一个URL请求,net库就是要根据这个请求,快速准确地返回相应的数据,这个函数的文件位于 /net/url_request。 当我们在浏览器中输入 http://www.bytedance.net 时,通过URL解析,得到其scheme是http,这个时候就会调用到HTTP模块进行相应的处理,关于这一部分的详细过程,后续我会再找机会详细分析URL的解析过程。
当HTTP模块收到这个URL解析请求时,就像我们最先提到的那样,会先去调用HttpCache::Transaction::Start 这个方法,这个函数的部分代码如下:
int HttpCache::Transaction::Start(const HttpRequestInfo* request,
const CompletionCallback& callback,
const BoundNetLog& net_log) {
…
next_state_ = STATE_GET_BACKEND;
int rv = DoLoop(OK);
// Setting this here allows us to check for the existence of a callback_ to
// determine if we are still inside Start.
if (rv == ERR_IO_PENDING) {
callback_ = tracked_objects::ScopedTracker::TrackCallback(
FROM_HERE_WITH_EXPLICIT_FUNCTION(
"422516 HttpCache::Transaction::Start"),
callback);
}
return rv;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
可以看到这部分代码主要是启动了一个状态机,并且制定了初始状态STATE_GET_BACKEND 和 结束状态 ERR_IO_PENDING。关于这个结束状态,是为了防止状态机阻塞后不能及时结束而导致程序无法继续执行,因此当状态码为ERR_IO_PENDING,则直接跳出该状态机循环。启动这个状态机是为了从缓存中获取我们想要的数据,这部分的流程分支有很多,光状态机的state就有40种之多,作者也将一些流程分支都整理了出来(这里只贴了一部分,想看完整版的可以去http_cache_transaction.cc 这个文件中看)
大概看了下,好家伙,有14种状态,这还只是作者列出来的,一些异常情况还未考虑进来,但是从根本上来看,大致可以分为两种情况:
情况一就是Cached entry,这部分状态就代表我们从缓存数据中找到了想要的资源,直接拿过来用就可以了,不需要再去进行网络请求。
情况二就是Not-cached entry,这部分状态代表我们并没有从缓存中找到想要的数据,因此我们需要调用 SendRequest 来实现网络加载资源的请求,如果调用成功,即我们通过网络请求获取了想要的资源,除了将资源返回给用户之外,还需要调用CacheWriteData 来更新缓存中的数据,这样等下次访问时,就可以直接在cache命中。
再来看一下读取缓存数据的流程:
首先是状态变更:GetBackend* -> InitEntry -> OpenEntry* -> AddToEntry* -> CacheReadResponse* -> CacheDispatchValidation
用文字解读一下就是,初始化Entry,打开这个Entry,根据URL获取Entry数据,读取数据并且构造响应消息体。
这里的cache_key_实际上就是URL,通过这个值便可以唯一检索到缓存中的数据。
以上是HttpCache::Transaction状态机的实现概述,简单点说就是以URL为key值在缓存中查找数据,如果匹配成功则直接返回,如果不成功则需要发送网络请求,这就涉及到我们接下来要介绍的HttpNetworkTransaction了。
当我们需要调用网络加载所需资源时,就要把状态机状态置为STATE_SEND_REQUEST,此时就会调用到下面这个函数:
我们注意到这个函数最后是调用了network_trans中的Start方法,此时便会调用到
int HttpNetworkTransaction::Start 这个函数里来,同样的,在该函数中也新启动了一个状态机,并且将初始状态设置为 STATE_NOTIFY_BEFORE_CREATE_STREAM :
NetworkTransaction层的状态机状态码有以下几种,比之前的cache层要少很多
顺着这个状态码整理NetworkTransaction层的大致流程如下:
DoCreateStream —> DoInitStream —> DoGenerateProxyAuthToken —> DoGenerateServerAuthToken —> DoInitRequestBody —> DoBuildRequest —> DoSendRequest —> DoReadHeaders —> DoReadBody
用文字解释一下就是:创建strem,初始化参数,建立连接,初始化请求主体,发送请求,并且读取相应的返回信息,最后根据需要处理连接。
这里要提一下的是DoSendRequest方法,它实际上调用的是HttpBasicStream::SendRequest的方法,而HttpBasicStream则调用HttpStreamParser中的SendRequest方法来实现,其大致思路就是创建连接,构造消息体,调用tcp socket进行消息头和数据的传输,最终将结果返回给NetworkTransaction层,有兴趣的同学可以去看一下代码的具体实现,这部分代码位于 http_stream_parser.cc 文件中。
介绍完NetworkTransaction层的网络传输层以后,还会做一个动作,那就是将得到的数据写入到cache中去,这个我们在之前已经提到过,这里就不再赘述了。
关于HTTP的解析就先介绍到这里,简单总结一下,对于一个URL请求,当解析为HTTP请求时,先去cache层中查找,如果找到了,则构造数据并返回;如果没找到,再去调用network层发起网络请求,通过调用底层的tcp socket实现数据的传输,当完成加载以后,再将其写入到cache层中。当然,这只是最常见的一种流程,关于HTTP还有许多需要探索和学习的地方,这些都留在以后的章节吧。