Chromium 网络栈之URLRequestJob


发布于 2021-11-06


URLRequestJob

URLRequest有个URLRequestJob成员,通过它来处理加载各种具体的 url。比如处理 http(s) 协议的 url 就是使用URLRequestHttpJob,处理 data 协议的 url 就使用URLRequestDataJob,甚至错误的 url 也有专门的URLRequestErrorJob

URLRequest是通过URLRequestJobManager单例对象的CreateJob来创建的URLRequestJobURLRequestJobManager则去调用URLRequestJobFactory去创建URLRequestJob。如果URLRequestJobFactory处理不了,则还有内置默认的 http(s) ws(s) 协议的处理。

此外还有在 url 重定向的时候通过MaybeInterceptRedirect去接管处理逻辑,在 url 响应回来时通过 MaybeInterceptResponse 去接管处理逻辑。

下面通过几个例子来展示 Chromium 网络栈灵活的自定义能力。

自定义新的协议

这个例子中,我们自定义一个 hello 协议。例如输入 url hello://world ,则返回结果为 Welcome //world

首先我们创建一个URLRequestHelloJob,它主要是处理hello 协议的 url,并返回对应的结果。

class URLRequestHelloJob : public net::URLRequestJob {
 public:
  URLRequestHelloJob(net::URLRequest* request,
                     net::NetworkDelegate* network_delegate)
      : net::URLRequestJob(request, network_delegate) {}
  ~URLRequestHelloJob() override {}

  void Start() override { URLRequestJob::NotifyHeadersComplete(); }

 private:
  int ReadRawData(net::IOBuffer* buf, int buf_size) override {
    if (!is_eval_) {
      is_eval_ = true;
      GURL url = request()->url();
      std::string name = url.path();
      std::string response = "Welcome " + name;
      memcpy(buf->data(), response.c_str(), response.size());
      return response.size();
    }

    return 0;
  }

  bool is_eval_ = false;
};

然后我们再创建一个HelloProtocolHandler,它用于接管hello协议的 url,并返回对应的URLRequestHelloJob

class HelloProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler {
 public:
  HelloProtocolHandler() {}
  ~HelloProtocolHandler() override {}

  net::URLRequestJob* MaybeCreateJob(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate) const override {
    return new URLRequestHelloJob(request, network_delegate);
  }
};

最后,把HelloProtocolHandler注册到URLRequestContext里面,使其生效:

context_builder.SetProtocolHandler("hello",
                                     std::make_unique<HelloProtocolHandler>());

URLRequestInterceptor

URLRequestInterceptor比起ProtocolHandler更加灵活和强大。可以在多个时机控制修改请求。

拦截修改 url 的请求

class InterceptRequest : public net::URLRequestInterceptor {
 public:
  InterceptRequest() {}
  ~InterceptRequest() override {}

  net::URLRequestJob* MaybeInterceptRequest(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate) const override {
    GURL url = request->url();
    if (url.DomainIs("www.163.com")) {
      request->SetReferrer("https://www.baidu.com/");
      return net::URLRequestHttpJob::Factory(request, network_delegate,
                                             url.scheme());
    }
    return nullptr;
  }
};

  url_request_interceptors.emplace_back(std::make_unique<InterceptRequest>());

我们实现一个InterceptRequest,把它注入到网络栈的。

这样我们在访问 https://www.163.com/ 的时候,它会自动加上 referrer https://www.baidu.com/ 。

拦截修改 url 的响应结果

class InterceptResponse : public net::URLRequestInterceptor {
 public:
  InterceptResponse() {
    file_task_runner_ = base::CreateTaskRunnerWithTraits(
        {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
  }
  ~InterceptResponse() override {}

  net::URLRequestJob* MaybeInterceptRequest(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate) const override {
    return nullptr;
  }

  net::URLRequestJob* MaybeInterceptResponse(
      net::URLRequest* request,
      net::NetworkDelegate* network_delegate) const override {
    static bool is_eval = false;
    if (!is_eval) {
      is_eval = true;

      GURL url = request->url();
      std::string path = url.path();
      if (path ==
          std::string("/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png")) {
        base::FilePath file_path(L"D:\\local_web_server\\small.png");
        return new net::URLRequestFileJob(request, network_delegate, file_path,
                                          g_network_thread->task_runner());
      }
    }
    return nullptr;
  }

 private:
  scoped_refptr<base::TaskRunner> file_task_runner_;
};

  url_request_interceptors.emplace_back(std::make_unique<InterceptResponse>());

这样我们在访问 https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png 的时候,它接管后续的响应,创建一个 URLRequestFileJob,从本地文件 D:\local_web_server\small.png 读取返回结果。