HTTP 请求的生命周期¶
Firefox 中的 HTTP 请求会经历几个步骤。请求消息和响应消息的各个部分会在特定的时间点变得可用。提取这些信息是一项挑战。
何时可用¶
数据 |
可用时间 |
示例 JS 代码 |
接口 |
测试代码 |
---|---|---|---|---|
HTTP 请求方法 |
http-on-modify-request 观察者通知 |
channel.requestMethod |
||
HTTP 请求 URI |
http-on-modify-request 观察者通知 |
channel.URI |
||
HTTP 请求头 |
http-on-modify-request 观察者通知 |
channel.visitRequestHeaders(visitor) |
||
HTTP 请求体 |
http-on-modify-request 观察者通知 |
channel.uploadStream |
||
HTTP 响应状态
|
http-on-examine-response 观察者通知
|
channel.responseStatus
channel.responseStatusText
|
||
HTTP 响应头 |
http-on-examine-response 观察者通知 |
channel.visitResponseHeaders(visitor) |
||
HTTP 响应体
|
onStopRequest 通过流监听器分流
|
见下文
|
请求:http-on-modify-request¶
在发送 HTTP 请求之前,Firefox 会触发“http-on-modify-request”观察者通知,并且会在所有观察者退出之前阻塞请求的发送。这通常是您可以修改 HTTP 请求头的地方(因此得名)。
为请求附加监听器非常简单
const obs = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe: function(channel, topic, data) {
if (!(channel instanceof Ci.nsIHttpChannel))
return;
// process the channel's data
}
}
Services.obs.addObserver(observer, "http-on-modify-request", false);
有关详细信息,请参阅 nsIObserverService。
此时,请求方法和 URI 会立即可用。请求头也很容易获取
/**
* HTTP header visitor.
*/
class HeaderVisitor {
#targetObject;
constructor(targetObject) {
this.#targetObject = targetObject;
}
// nsIHttpHeaderVisitor
visitHeader(header, value) {
this.#targetObject[header] = value;
}
QueryInterface = ChromeUtils.generateQI(["nsIHttpHeaderVisitor"]);
}
// ...
const requestHeaders = {};
const visitor = new HeaderVisitor(requestHeaders);
channel.visitRequestHeaders(visitor);
如果需要,您也可以在此处设置请求头。在 nsIHttpChannel 接口上的方法是 channel.setRequestHeader(header, value);
大多数 HTTP 请求没有主体,因为它们是 GET 请求。但是,POST 请求通常有主体。正如 nsIUploadChannel 文档所述,大多数 HTTP 请求的主体可以通过可寻址流 (nsISeekableStream) 获取。因此,您可以简单地捕获主体流及其当前位置,以便稍后重新访问它。network-helper.js 包含读取请求体的代码。
响应:http-on-examine-response¶
在解析 HTTP 响应状态和头之后,但在读取响应体之前,Firefox 会触发“http-on-examine-response”观察者通知。为这个阶段附加监听器也非常简单
Services.obs.addObserver(observer, "http-on-examine-response", false);
如果您对“http-on-modify-request”和“http-on-examine-response”使用相同的观察者,请确保在与通道交互之前检查主题参数。
响应状态可通过 responseStatus 和 responseStatusText 属性获取。响应头可通过 visitResponseHeaders 方法获取,并且需要相同的接口。
响应体:onStopRequest,流监听器分流¶
在“http-on-examine-response”通知期间,响应体是不可用的。但是,您可以使用流监听器分流来复制流,以便原始流数据继续传递,并且您有一个单独的输入流,您可以从中读取相同的数据。
以下是一些示例代码,说明您需要什么
const Pipe = Components.Constructor(
"@mozilla.org/pipe;1",
"nsIPipe",
"init"
);
const StreamListenerTee = Components.Constructor(
"@mozilla.org/network/stream-listener-tee;1",
"nsIStreamListenerTee"
);
const ScriptableStream = Components.Constructor(
"@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream",
"init"
);
const obs = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver", "nsIRequestObserver"]),
/** @typedef {WeakMap<nsIHttpChannel, nsIPipe>} */
requestToTeePipe: new WeakMap,
// nsIObserver
observe: function(channel, topic, data) {
if (!(channel instanceof Ci.nsIHttpChannel))
return;
/* Create input and output streams to take the new data.
The 0xffffffff argument is the segment count.
It has to be this high because you don't know how much data is coming in the response body.
As for why these are blocking streams: I believe this is because there's no actual need to make them non-blocking.
The stream processing happens during onStopRequest(), so we have all the data then and the operation can be synchronous.
But I could be very wrong on this.
*/
const pipe = new Pipe(false, false, 0, 0xffffffff);
// Install the stream listener tee to intercept the HTTP body.
const tee = new StreamListenerTee;
const originalListener = channel.setNewListener(tee);
tee.init(originalListener, pipe.outputStream, this);
this.requestToTeePipe.set(channel, pipe);
}
// nsIRequestObserver
onStartRequest: function() {
// do nothing
}
// nsIRequestObserver
onStopRequest: function(channel, statusCode) {
const pipe = this.requestToTeePipe.get(channel);
// No more data coming in anyway.
pipe.outputStream.close();
this.requestToTeePipe.delete(channel);
let length = 0;
try {
length = pipe.inputStream.available();
}
catch (e) {
if (e.result === Components.results.NS_BASE_STREAM_CLOSED)
throw e;
}
let responseBody = "";
if (length) {
// C++ code doesn't need the scriptable input stream.
const sin = new ScriptableStream(pipe.inputStream);
responseBody = sin.read(length);
sin.close();
}
void(responseBody); // do something with the body
}
}
test_traceable_channel.js 基本上就是这么做的。
字符编码和压缩¶
取消请求¶
HTTP 活动分发器说明¶
URIContentLoader 说明¶
操作顺序¶
构建 HTTP 通道。
触发“http-on-modify-request”观察者服务通知。
如果请求已取消,则在此步骤退出。
将 HTTP 通道的请求提交到服务器。时间流逝。
HTTP 通道的响应从服务器传入。
HTTP 通道解析响应状态和头。
触发“http-on-examine-response”观察者服务通知。
有用的代码示例和参考¶
nsIHttpProtocolHandler 定义了许多观察者主题,并包含许多详细信息。