Electron 中的 protocol 可以用作资源请求拦截器,或者软件内的自定义协议,实现私有的方法处理。但网上关于 protocol 的介绍有些少,或者是模棱两可的介绍,正好前段时间研究了这部份,这里记录一下所得。
protocol.registerStandardSchemes
1 2 3
| protocol.registerSchemesAsPrivileged([ { scheme: "atom", privileges: { standard: true } }, ]);
|
- 这里 API 文档说,只有注册了后,才能使用相对路径,并且才能访问
FileSystem API
- Web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies) 这些本地缓存功能会无效,因为通过 file 加载的文件并没有注册成
StandardSchemes
Register Protocol
Electron 官网上面对每个接口都有介绍,不过不是很详细,下面是一些主要的接口。
- protocol.registerFileProtocol(scheme, handler[, completion]), 注册 scheme 后,在返回的时候,以文件的路径作为返回值。其实这里也可以返回一个 Stream。
- protocol.registerBufferProtocol(scheme, handler[, completion]), 以 Buffer 作为返回值
1 2 3 4 5 6 7 8 9
| protocol.registerBufferProtocol( "atom", (request, callback) => { callback({ mimeType: "text/html", data: Buffer.from("<h5>Response</h5>") }); }, (error) => { if (error) console.error("Failed to register protocol"); } );
|
- protocol.registerStringProtocol(scheme, handler[, completion]), 以 String 作为返回值
- protocol.registerHttpProtocol(scheme, handler[, completion])
- protocol.registerStreamProtocol(scheme, handler[, completion]), 以 Stream 作为返回值
1 2 3 4 5 6 7 8 9
| protocol.registerStreamProtocol( "atom", (request, callback) => { callback(fs.createReadStream("index.html")); }, (error) => { if (error) console.error("Failed to register protocol"); } );
|
- protocol.interceptFileProtocol(scheme, handler[, completion]), 以文件路径作为返回值
- protocol.interceptStringProtocol(scheme, handler[, completion]), 以 String 作为返回值
- protocol.interceptBufferProtocol(scheme, handler[, completion]), 以 Buffer 作为返回值
- protocol.interceptHttpProtocol(scheme, handler[, completion])
- protocol.interceptStreamProtocol(scheme, handler[, completion]), 以 String 作为返回值
实现一个资源请求的缓存
- 自定义一个 Protocol 实现请求访问本地文件,如下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <img src="atom://oval.svg" />;
protocol.registerFileProtocol( "atom", (request, callback) => { const url = request.url.substr(7); callback(fs.createReadStream(path.normalize(`${__dirname}/${url}`))); }, (error) => { if (error) console.error("Failed to register protocol"); } );
|
- 实现一个带缓存的网络请求,如果本地有缓存,就不再请求,直接返回已经在本地的内容
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
| <img id="img2" src="atom-cached://vastiny.com/visitor.png" />;
protocol.registerFileProtocol( "atom-cached", (request, callback) => { const url = `https://${request.url.substr(14)}`; const cachedPath = path.normalize(`${__dirname}/oval-cached.png`); if (fs.existsSync(cachedPath)) { callback({ path: cachedPath }); return; } fetch(url).then((res) => { const dest = fs.createWriteStream(cachedPath); dest.on("finish", () => { callback({ path: cachedPath }); }); res.body.pipe(dest); }); }, (error) => { if (error) console.error("Failed to register protocol"); } );
|
注册的 File 和 Http 协议到底有什么不同
简单来讲,http 协议有 host,而 file 协议没有 host ,例如下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <img id="img1" src="atom://oval.svg" />;
protocol.registerSchemesAsPrivileged([ { scheme: "atom", privileges: { standard: true } }, ]);
protocol.registerFileProtocol( "atom", (request, callback) => { const url = request.url.substr(7); console.log("request url:", request.url); callback({ path: path.normalize(`${__dirname}/${url}`) }); }, (error) => { if (error) console.error("Failed to register protocol"); } );
|
这里面就会报错, 说找不到 /xxx/xxx/oval.svg/
文件,让 scheme 变成与 http URI 标准一样的,会让 atom://oval.svg
识别成 atom://oval.svg/
,因为会认为 oval.svg 是一个 host。
拦截器无限循环
例如下面这个例子,放在 Electron 中后,会出现无限循环拦截和请求 https 链接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <img id="img1" src="https://vastiny.com/visitor.png" />;
protocol.interceptHttpProtocol( "https", (request, callback) => { if (request.url.endsWith("png")) { } callback({ url: request.url, method: request.method, }); }, (error) => { if (error) console.error("Failed to intercept protocol"); } );
|
可以用 Session 实现每次拦截的请求通转移到另外一个 Session 。也就是拦截器只能拦截 default Session。
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
| <img id="img1" src="https://vastiny.com/visitor.png" />;
const mainSession = session.fromPartition("main"); const mainWindow = new BrowserWindow({ webPreferences: { session: mainSession, }, });
protocol.interceptHttpProtocol( "https", (request, callback) => { if (request.url.endsWith("png")) { } callback({ url: request.url, method: request.method, session: null, }); }, (error) => { if (error) console.error("Failed to intercept protocol"); } );
|
上面的例子,能让拦截器 callback 的时候,不再被自己拦截。session:null
可以是自己再弄一个 session,这里简单的设置为 null 也是可以的。以目前的解决方案,算是比较好的解决了无限循环的问题,并且也能依此实现无感的资源缓存。
References