目前方案不完善,期望大佬能发表一下建议~ 尝试了很多api,都不能很好的达到效果
需求背景
在electron
中的web应用离线方案,由于目前的web应用已经大量接入了,想要各个产线去对离线方案进行大量配置更改,成本过高。所以期望离线方案能对现有web应用进行无侵入接入。
实现方案
<math xmlns="http://www.w3.org/1998/Math/MathML"> e l e c t r o n 版本 : 12.1.1 \color{#a0a0a0}{electron 版本:12.1.1} </math>electron版本:12.1.1
方案一 onBeforeRequest
+ 自定义协议
使用webRequest.onBeforeRequest
拦截所有请求,通过判断逻辑指定处理某些url。
这些特定的url,需要通过自定义协议处理。
- 注册私有协议
ts
const ses = session.fromPartition('persist:my-partition');
ses.protocol.registerFileProtocol('toLocal', (request, callback) => {
//这只是个举例,实际需要通过request.url判断使用哪个本地文件
callback({
path: '/Users/xxx/Documents/project/electron-react/dist/xxx.html',
statusCode: 200,
charset: 'utf-8',
mimeType: 'text/html',
});
- 对特定url进行识别,转发到私有协议中
ts
ses.webRequest.onBeforeRequest({ urls: ['*://*/*'] }, (details: any, callback: any) => {
// 检查请求URL是否为特定资源
if (details.url === 'https://xx.xx.com/') {
callback({
redirectURL: 'toLocal://xx.xx.com',
});
} else {
// 允许其他请求正常进行
callback({});
}
});
- 自定义协议在拦截html时,需要开启访问session(localstorage,cookie等)的权限
php
protocol.registerSchemesAsPrivileged([
{
scheme: 'toLocal',
privileges: {
standard: true,
secure: true,
supportFetchAPI: true,
bypassCSP: true,
corsEnabled: true,
},
},
]);
问题
-
使用
onBeforeRequest
+redirectURL
转发的url,会直接改变web中的request url
。-
私有协议无法使用cookie
-
在部分验证url的场景,会出现无权限问题
-
方案二 只使用拦截器
拦截自定义协议toLocal
和https
协议的请求,在https协议中处理指定url。命中后转发到toLocal
协议中,进行本地文件的读取。未命中则转发请求。
ts
ses.protocol.interceptFileProtocol('toLocal', (request, callback) => {
callback({
path: '/Users/ext.sunhaosheng1/Documents/project/electron-react1/dist/aaa.html',
});
});
ses.protocol.interceptHttpProtocol('https', (request, callback) => {
if (request.url === 'https://xxx.xx.com/') {
callback({
url: 'toLocal://xxx.xx.com',
method: request.method,
});
} else {
const uploadData = request.uploadData && {
contentType: 'application/json',
data: request.uploadData.map((i) => i.bytes.toString()).join(''),
};
callback({
url: request.url,
method: request.method,
session: session.defaultSession,
uploadData,
});
}
});
问题
-
拦截器只能注册一种,已先注册的为主。后续注册相同的scheme都会失败。
-
由于以上问题,拦截请求时,考虑使用
interceptHttpProtocol
。- 不代理的请求需要转发问题,
request
中的uploadData
数据格式和response
中的uploadData
数据格式不一致。 - 当前代码中,处理post请求中,携带file时无法处理。目前正在解决中...
- 不代理的请求需要转发问题,
interceptHttpProtocol
中的request和callback定义
ts
// request定义
interface ProtocolRequest {
// Docs: https://electronjs.org/docs/api/structures/protocol-request
headers: Record<string, string>;
method: string;
referrer: string;
uploadData?: UploadData[];
url: string;
}
interface UploadData {
// Docs: https://electronjs.org/docs/api/structures/upload-data
/**
* UUID of blob data. Use ses.getBlobData method to retrieve the data.
*/
blobUUID?: string;
/**
* Content being sent.
*/
bytes: Buffer;
/**
* Path of file being uploaded.
*/
file?: string;
}
// callback传参定义
interface ProtocolResponse {
// Docs: https://electronjs.org/docs/api/structures/protocol-response
/**
* The charset of response body, default is `"utf-8"`.
*/
charset?: string;
/**
* The response body. When returning stream as response, this is a Node.js readable
* stream representing the response body. When returning `Buffer` as response, this
* is a `Buffer`. When returning `String` as response, this is a `String`. This is
* ignored for other types of responses.
*/
data?: (Buffer) | (string) | (NodeJS.ReadableStream);
/**
* When assigned, the `request` will fail with the `error` number . For the
* available error numbers you can use, please see the net error list.
*/
error?: number;
/**
* An object containing the response headers. The keys must be String, and values
* must be either String or Array of String.
*/
headers?: Record<string, (string) | (string[])>;
/**
* The HTTP `method`. This is only used for file and URL responses.
*/
method?: string;
/**
* The MIME type of response body, default is `"text/html"`. Setting `mimeType`
* would implicitly set the `content-type` header in response, but if
* `content-type` is already set in `headers`, the `mimeType` would be ignored.
*/
mimeType?: string;
/**
* Path to the file which would be sent as response body. This is only used for
* file responses.
*/
path?: string;
/**
* The `referrer` URL. This is only used for file and URL responses.
*/
referrer?: string;
/**
* The session used for requesting URL, by default the HTTP request will reuse the
* current session. Setting `session` to `null` would use a random independent
* session. This is only used for URL responses.
*/
session?: Session;
/**
* The HTTP response code, default is 200.
*/
statusCode?: number;
/**
* The data used as upload data. This is only used for URL responses when `method`
* is `"POST"`.
*/
uploadData?: ProtocolResponseUploadData;
/**
* Download the `url` and pipe the result as response body. This is only used for
* URL responses.
*/
url?: string;
}
interface ProtocolResponseUploadData {
// Docs: https://electronjs.org/docs/api/structures/protocol-response-upload-data
/**
* MIME type of the content.
*/
contentType: string;
/**
* Content to be sent.
*/
data: (string) | (Buffer);
}
方案三:代理
安全过不去 弃
由于安全过不去,基本没做调研,只知道有个setProxy
ts
ses.setProxy()
终
以上方案目前都不太满足,调研了近3天,依旧没有一个很好的解决方案。
在开始调研前的方案
graph TD
renderer发起网络请求 --> main拦截
main拦截 --> 调用离线缓存sdk --> q1{命中缓存}
q1 --是--> 返回本地文件
q1 --否--> 交还给electron原生请求
我是真没想到!我查了这么久,愣是没找到能对webContent的网络请求完全拦截的api!!!