在CEF中通过内置协议加载本地html文件

在CEF中通过内置协议加载本地html文件

在通常情况下我们加载一个html文件,是通过File协议来加载的:

1
file:///D:/project/cef/html/index.html

但是这个方法有种种弊端。

File协议弊端

1. 安全限制

file:// 协议与 HTTP 协议相比,存在更多的安全限制。在浏览器中,file:// 协议访问本地文件时,浏览器会严格限制访问权限,以避免潜在的安全漏洞。这些限制包括:

  • 跨域问题file:// 协议加载的本地页面可能无法正常执行跨域请求。比如,AJAX 请求、WebSocket 连接等都可能受阻,因为本地文件的跨域访问限制。
  • 混合内容:如果本地页面中引用了网络上的资源,或者本地文件需要请求外部资源,这种混合内容可能会被浏览器拒绝加载,尤其是在严格的内容安全策略下。

2. 浏览器兼容性和一致性

即使是 Chromium 内核(CEF 基于 Chromium)也不推荐直接使用 file:// 协议,原因之一是,file:// 协议的行为在不同平台或不同版本的浏览器中可能会有所不同。例如,某些操作系统和浏览器会对本地文件有不同的权限控制或路径解析方式,导致在不同的环境下,加载本地文件的效果不一致。

3. JavaScript 和文件访问权限

CEF 中的 JavaScript 执行环境与浏览器的执行环境类似,因此,file:// 协议加载的本地页面可能会遭遇一些权限问题。例如,JavaScript 可能无法像 HTTP 协议一样访问外部资源,或者可能受到浏览器的文件访问权限限制。特别是在 Web 安全性方面,浏览器通常会限制 file:// 页面对本地文件系统的读写操作,防止潜在的恶意脚本访问系统文件。

4. 调试和开发体验不佳

使用 file:// 协议时,调试体验通常比通过 HTTP 协议加载页面差。例如,无法像在开发服务器中那样使用浏览器开发工具的调试功能;而且可能遇到一些开发工具或框架的功能无法正常使用,特别是当涉及到热重载、热更新或调试日志时,file:// 协议通常没有 HTTP 环境下那样便捷的支持。

5. 性能问题

file:// 协议加载的页面性能可能会受到操作系统文件系统的影响,尤其是在复杂的页面中,加载静态资源(如 JS、CSS 文件)时,可能会有更多的延迟或资源加载问题。通过 HTTP 协议加载页面,通常会有更高的性能优化,因为现代浏览器和 HTTP 协议都有很多优化机制(如缓存、连接池、并发请求等),这在本地文件加载时并不总是能充分利用。

通过本地Web服务解决

为了避免这些问题,CEF和大多数现代开发环境都推荐通过启动一个本地 Web 服务器来加载本地页面。使用像HTTP协议访问本地文件系统的好处包括:

  • 可以避免 file:// 协议的权限和安全限制。
  • 可以确保更好的跨平台兼容性和一致性。
  • 可以充分利用浏览器的 HTTP 缓存、请求优化和调试工具。

通常,可以通过启动一个简单的本地服务器(如 http-serverlive-servernginxApache)来为本地页面提供 HTTP 服务,这样就能通过 http://localhost 或其他本地IP地址来访问你的页面。

注册内置协议处理工厂

首先我们可以使用上篇中App中的OnContextInitialized方法

如上图

1
2
3
4
5
6
7
8
9
void App::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
//下面两行代码为新增代码
CefRegisterSchemeHandlerFactory("https", "bayeeaa", new HttpSchemeFactory());
std::string url = "https://bayeeaa/index.html?a=123";
CefBrowserSettings settings;
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(nullptr, url, settings, nullptr, nullptr, nullptr);
CefWindow::CreateTopLevelWindow(new WindowDelegate(browser_view));
}

可以看出我们在浏览器主线程初始化成功后,调用了这个 CEF 提供的 API :CefRegisterSchemeHandlerFactory。这个方法为应用注册一个 HttpSchemeFactory 对象,注册成功则返回 true ,失败则返回 false 。我们观察CefRegisterSchemeHandlerFactory中的三个参数,第一个为协议,第二个为域名,域名不一定要.com和.cn结尾,第三个是自定义的协议工厂。

注册成功后我们就可以进行执行操作了。

接管并处理请求

HttpSchemeFactory 类的工作很简单,当浏览器以指定的自定义协议加载页面时,它负责接管并处理请求。我们先来看一下这个类的头文件代码:

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
#pragma once
#include "include/cef_app.h"
#include "include/cef_resource_handler.h"
#include "include/cef_request.h"

class HttpSchemeFactory : public CefSchemeHandlerFactory {
public:
HttpSchemeFactory() = default;

// 删除拷贝函数,防止拷贝实例
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);
};

// CefSchemeHandlerFactory::Create 的实现
CefRefPtr<CefResourceHandler> HttpSchemeFactory::Create(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,const CefString& scheme_name,CefRefPtr<CefRequest> request) {
// 在此根据请求 URL 以及其他信息创建一个自定义的 CefResourceHandler。
// 你可以根据 request 的内容来选择不同的行为,比如读取本地文件、返回静态内容等。
// 示例: 返回一个简单的字符串作为 HTTP 响应
CefRefPtr<CefResourceHandler> handler = new MyHttpResourceHandler(); // 这里你可以返回自定义的处理器
return handler;
}

处理请求:

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
class MyHttpResourceHandler : public CefResourceHandler {
public:
MyHttpResourceHandler() : offset_(0), is_done_(false) {}

bool ProcessRequest(CefRefPtr<CefRequest> request, CefRefPtr<CefCallback> callback) override {
// 假设返回一个简单的 HTML 内容
std::string html_content = "<html><body><h1>Hello from custom scheme!</h1></body></html>";
response_stream_ = CefStreamReader::CreateForData(html_content.data(), html_content.size());

// 设置响应头
CefRefPtr<CefResponse> response = CefResponse::Create();
response->SetMimeType("text/html");
response->SetStatus(200);
response->SetStatusText("OK");

callback->Continue();
return true;
}

bool GetResponseHeaders(CefRefPtr<CefResponse> response, int& response_length, CefString& redirect_url) override {
// 需要返回响应长度
response_length = response_stream_->GetSize();
return true;
}

bool ReadResponse(void* data_out, int bytes_to_read, int& bytes_read, CefRefPtr<CefCallback> callback) override {
// 读取响应数据
int bytes_left = response_stream_->GetSize() - offset_;
if (bytes_left > 0) {
int bytes_to_copy = std::min(bytes_left, bytes_to_read);
response_stream_->Read(data_out, offset_, bytes_to_copy);
offset_ += bytes_to_copy;
bytes_read = bytes_to_copy;
return true;
}
bytes_read = 0;
return false;
}

IMPLEMENT_REFCOUNTING(MyHttpResourceHandler);

private:
int offset_;
bool is_done_;
CefRefPtr<CefStreamReader> response_stream_;
};


在CEF中通过内置协议加载本地html文件
https://bayeeaa.github.io/2025/02/08/在CEF中通过内置协议加载本地html文件/
Author
Ye
Posted on
February 8, 2025
Licensed under