0%

Cef自定义协议处理工厂并处理HTTP资源请求

  1. App类
  2. HttpSchemeFactory类

App类

App.cpp

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
#include "App.h"
#include <include/wrapper/cef_helpers.h>
#include <include/views/cef_browser_view.h>
#include <include/views/cef_window.h>
#include "WindowDelegate.h"
#include "HttpSchemeFactory.h"
#include "BrowserClient.h"

void App::OnContextInitialized()
{
// 确保在主线程中调用
CEF_REQUIRE_UI_THREAD();

// 注册自定义协议处理工厂
CefRegisterSchemeHandlerFactory("http", "my-domain", new HttpSchemeFactory("my-domain"));

auto url = "http://my-domain/index.html";

// 创建浏览器窗口
CefWindowInfo info;
info.SetAsPopup(nullptr, "Hello World");

CefBrowserSettings settings;
settings.windowless_frame_rate = 60; // 最低帧率

CefBrowserHost::CreateBrowser(info, new BrowserClient(), url, settings, nullptr, nullptr);
}
  • 通过CefRegisterSchemeHandlerFactory()函数注册自定义协议
  • 第一个参数是协议,默认用http
  • 第二个参数是域名,可以自定义;为空则表示某协议下所有域名

HttpSchemeFactory类

HttpSchemeFactory.h

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
#pragma once

#include <include\cef_scheme.h>

class HttpSchemeFactory : public CefSchemeHandlerFactory
{
public:
HttpSchemeFactory() = default;
HttpSchemeFactory(const std::string& domain) : _domain(domain) {}

// 不允许删除和拷贝
HttpSchemeFactory(const HttpSchemeFactory&) = delete;
HttpSchemeFactory& operator=(const HttpSchemeFactory&) = delete;

// 当浏览器内核以 用户自定义的协议 发起请求时,会调用这个方法
// 该方法接管了浏览器内核的请求,并给出响应
CefRefPtr<CefResourceHandler> Create(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& scheme_name,
CefRefPtr<CefRequest> request) override;

private:
IMPLEMENT_REFCOUNTING(HttpSchemeFactory);

std::string _domain;
};
  • 定义了一个域名,用于标识,只处理该域名下的请求(一个网页可能会发出包含其他协议内容/其他域名的http请求)

HttpSchemeFactory.cpp

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
73
74
75
76
77
78
79
80
81
#include "HttpSchemeFactory.h"
#include <include/wrapper/cef_helpers.h>
#include <include/wrapper/cef_stream_resource_handler.h>
#include <include/cef_urlrequest.h>
#include <filesystem>

#include <iostream>

#ifdef min
#undef min
#endif // min

CefRefPtr<CefResourceHandler> HttpSchemeFactory::Create(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, const CefString& scheme_name, CefRefPtr<CefRequest> request)
{
CEF_REQUIRE_IO_THREAD();

std::string url = request->GetURL().ToString();
std::string urlPrefix = "http://";

if (url.substr(0, urlPrefix.size()).compare(urlPrefix) == 0)
{
// 删除前缀
url.erase(0, urlPrefix.size());

// 处理接口调用请求
std::string apiPrefix = this->_domain + "/api";
if (url.substr(0, apiPrefix.size()).compare(apiPrefix) == 0)
{
CefRequest::HeaderMap headers;
headers.insert(std::make_pair("Accept", "*/*"));
auto req = CefRequest::Create();
req->SetMethod("GET");
req->SetURL("http://localhost:1234" + url.substr(this->_domain.size())); // 代理
req->GetHeaderMap(headers);

return new HttpResourceHandler(req);
}
// 读取文件
else
{
// 删除参数(可能存在)
auto paramIdx = url.find_first_of('?');
if (paramIdx != std::string::npos)
{
url.erase(paramIdx);
}

// 获取exe路径
TCHAR buf[MAX_PATH] = { 0 };
GetModuleFileName(NULL, buf, MAX_PATH);

// 获取文件路径
std::filesystem::path targetPath(buf);
targetPath = targetPath.parent_path().append("Resources").append(url);
if (!std::filesystem::exists(targetPath))
{
DLOG(ERROR) << std::string("文件不存在:") << targetPath.generic_wstring();
return nullptr;
}

// 创建文件读写流
std::string ext = targetPath.extension().generic_string();
std::string mime;
if (ext == ".html")
mime = "text/html";
else if (ext == ".js")
mime = "application/javascript";
else if (ext == ".css")
mime = "text/css";
else
mime = "*/*";

auto stream = CefStreamReader::CreateForFile(targetPath.generic_wstring());
return new CefStreamResourceHandler(mime, stream);
}
}
else
{
return CefRefPtr<CefResourceHandler>();
}
}
  • 当页面通过协议://域名/路由?参数的基本格式请求访问某个资源时,我们可以在Create()函数中进行截获并自定义内容;
  • 如果返回一个空指针,则表示不处理这个资源请求,Cef会采用默认方式继续读取资源;

下面是两个用到的类的定义和实现:

class UrlRequestClient

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

// 自定义网络请求事件处理
class UrlRequestClient : public CefURLRequestClient
{
public:
UrlRequestClient(const CefRefPtr<CefCallback> cb)
:m_status(-1), m_callback(cb) {}

void OnDownloadData(CefRefPtr<CefURLRequest> request,
const void* data,
size_t data_length) override
{
m_data += std::string(static_cast<const char*>(data), data_length);
}

bool GetAuthCredentials(bool isProxy,
const CefString& host,
int port,
const CefString& realm,
const CefString& scheme,
CefRefPtr<CefAuthCallback> callback) override
{
return false;
}

void OnDownloadProgress(CefRefPtr<CefURLRequest> request,
int64 current,
int64 total) override {}
void OnUploadProgress(CefRefPtr<CefURLRequest> request,
int64 current,
int64 total) override {}
void OnRequestComplete(CefRefPtr<CefURLRequest> request) override
{
this->m_status = request->GetResponse()->GetStatus();
this->m_callback->Continue();
}

auto data() const { return this->m_data; }

private:
std::string m_data;
int m_status;
CefRefPtr<CefCallback> m_callback;

IMPLEMENT_REFCOUNTING(UrlRequestClient);
};

class HttpResourceHandler

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
73
74
75
76
77
78
79
80

// 自定义资源处理类
// 创建了通过某个协议访问某个资源的请求,返回值就是一个HttpResourceHandler
class HttpResourceHandler :public CefResourceHandler
{
public:
HttpResourceHandler(const CefRefPtr<CefRequest> req)
:m_req(req), m_dataOffset(0) {}

HttpResourceHandler(const HttpResourceHandler&) = delete;
HttpResourceHandler& operator=(const HttpResourceHandler&) = delete;

// 请求发生时。都设置成 false 则会开始执行ProcessRequest()(位于IO线程中,request请求只能在这类线程中发送)
bool Open(CefRefPtr<CefRequest> request, bool& handle_request, CefRefPtr<CefCallback> callback) override
{
DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO));

handle_request = false;
return false;
}

// 在 IO 线程中接管请求
bool ProcessRequest(CefRefPtr<CefRequest> request, CefRefPtr<CefCallback> callback) override
{
CEF_REQUIRE_IO_THREAD();

this->m_client = CefRefPtr<UrlRequestClient>(new UrlRequestClient(callback));
CefURLRequest::Create(this->m_req, this->m_client, nullptr);

return true;
}

// 处理响应头
void GetResponseHeaders(CefRefPtr<CefResponse> response, int64& response_length, CefString& redirectUrl) override
{
CEF_REQUIRE_IO_THREAD();
}

// 请求取消时
void Cancel() override
{
CEF_REQUIRE_IO_THREAD();
}

/// <summary>
/// 读取数据时触发(可能多次调用,即持有这个类实例的地方,通过这个函数,读取相关数据)
/// </summary>
/// <param name="data_out">本次读取输出的数据</param>
/// <param name="bytes_to_read">期望读取的数据长度</param>
/// <param name="bytes_read">实际读取的数据长度</param>
/// <param name="callback">回调函数</param>
/// <returns>指示:是否读取数据。false则表明没有数据读取;true && bytes_read>0:完成读取,结束;true && bytes_read==0:等待调用callback完成数据读取;</returns>
bool Read(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefResourceReadCallback> callback) override
{
DCHECK(!CefCurrentlyOn(TID_UI) && !CefCurrentlyOn(TID_IO));

bool has_data = false;
bytes_read = 0;
auto data = this->m_client->data();

if (m_dataOffset < data.size())
{
int transferSize = std::min(bytes_to_read, static_cast<int>(data.size() - m_dataOffset));
memcpy(data_out, data.c_str() + m_dataOffset, transferSize);

m_dataOffset += transferSize;
bytes_read = transferSize;
has_data = true;
}

return has_data;
}

private:
size_t m_dataOffset; // 读取数据的偏移量(可能一次没读完,记录一下读到哪了)
CefRefPtr<CefRequest> m_req;
CefRefPtr<UrlRequestClient> m_client;

IMPLEMENT_REFCOUNTING(HttpResourceHandler);
};