在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-server
、live-server
、nginx
或 Apache
)来为本地页面提供 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); };
CefRefPtr<CefResourceHandler> HttpSchemeFactory::Create(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,const CefString& scheme_name,CefRefPtr<CefRequest> request) { 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 { 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_; };
|