Vastiny

Jun 15, 2019

Electron 的 Protocol 实例解析

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
    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
    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
// Renderer process: index.html
<img src="atom://oval.svg" />

// Main process: main.js
protocol.registerFileProtocol(
'atom',
(request, callback) => {
const url = request.url.substr(7)
// callback({ path: path.normalize(`${__dirname}/${url}`) })
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
// Renderer process: index.html
<img id="img2" src="atom-cached://vastiny.com/visitor.png" />

// Main process: main.js
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
21
// Renderer process: index.html
<img id="img1" src="atom://oval.svg" />


// Main process, Before app ready: main.js
protocol.registerSchemesAsPrivileged([
{ scheme: 'atom', privileges: { standard: true } },
])

// Main process: main.js
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
// Renderer process: index.html
<img id="img1" src="https://vastiny.com/visitor.png" />

// Main process: main.js
protocol.interceptHttpProtocol(
'https',
(request, callback) => {
if (request.url.endsWith('png')) {
// blablabla...
}
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
// Renderer process: index.html
<img id="img1" src="https://vastiny.com/visitor.png" />

// Main process, After app ready: main.js
const mainSession = session.fromPartition('main')
const mainWindow = new BrowserWindow({
// ...
webPreferences: {
// ...
session: mainSession,
}
})

// Main process: main.js
protocol.interceptHttpProtocol(
'https',
(request, callback) => {
if (request.url.endsWith('png')) {
// blablabla...
}
callback({
url: request.url,
method: request.method,
session: null,
})
},
error => {
if (error) console.error('Failed to intercept protocol')
},
)

上面的例子,能让拦截器 callback 的时候,不再被自己拦截。session:null 可以是自己再弄一个 session,这里简单的设置为 null 也是可以的。以目前的解决方案,算是比较好的解决了无限循环的问题,并且也能依此实现无感的资源缓存。

References

OLDER > < NEWER