Open WVmf opened 7 years ago
Update regarding the first question:
By making a small modification to the callback function of _checkPush()
in the pushFrame()
method of spdy-transport/protocol/http2/framer.js, I was able to conditionally prevent the response headers from being sent immediately. The change is as follows:
this._checkPush(function (err) {
if (err) {
return callback(err)
}
var pairs = {
promise: [],
response: []
}
self._defaultHeaders(frame, pairs.promise)
pairs.response.push({ name: ':status', value: (frame.status || 200) + '' })
compress(frame.headers, pairs.promise, function (promiseChunks) {
sendPromise(promiseChunks)
+ // Don't send push response headers if 'response' was explicitly set to 'false'.
+ if (frame.response === false)
+ return callback(null)
compress(frame.response, pairs.response, function (responseChunks) {
sendResponse(responseChunks, callback)
})
})
})
By also updating my own code as shown below, I was able to get push working (with the correct headers) without the need for an extra "header collection"-pass through the Express stack.
let pushPath = "/path/to/file.txt";
let pushStream = res.push(pushPath, {
status: 200, // default 200
method: "GET",
request: { "accept": "*/*" },
- response: { "content-type": mime.lookup(pushPath) }
+ response: false
});
let pushFakeReq = new http.IncomingMessage();
pushFakeReq.url = pushPath;
pushFakeReq.method = pushStream.method;
Object.assign(pushFakeReq.headers, pushStream.headers);
pushFakeReq.headers["host"] = pushStream.host;
let pushFakeRes = new http.ServerResponse({
"method": pushStream.method,
"httpVersionMajor": 1,
"httpVersionMinor": 1
});
// Make the ServerResponse object "inherit" the pushStream's properties.
for ( let k in pushStream ) {
pushFakeRes[k] = pushStream[k];
}
+pushFakeRes.orgWrite = pushFakeRes.write;
+pushFakeRes.write = function (chunk, encoding, cb) {
+ if ( !this._headerSent ) {
+ this.writeHead(this.statusCode);
+ }
+ this.orgWrite(chunk, encoding, cb);
+}
pushFakeRes.writeHead = writeHeadOverride;
pushFakeRes.writeContinue = writeContinueOverride;
pushFakeRes.orgEnd = pushFakeRes.end;
pushFakeRes.end = endOverride;
let pushOutCb = function (err) {
if ( err ) {
console.error("Error for: " + pushPath, err);
} else {
console.log("No error seemed to have occurred for " + pushPath);
}
};
app._router.handle(pushFakeReq, pushFakeRes, pushOutCb);
Since the change to framer.js solves my first question, would it be possible to integrate the provided change into the official _spdytransport module?
The other question (i.e. what is the proper way to construct ServerResponse
from a push stream) still remains though.
Hi,
This is probably more of a (double) question than an issue, but I'm trying to run a push stream (created with
Response.push(path, opts)
through an existing stack of Express middlewares and route handlers. This way I would be able to use the existing stack (which for example handles compression) instead of having to manually create a file stream and pipe it (i.e.fs.createReadStream("somePathToFile").pipe(pushStream);
).But there are two problems that I've encountered in trying to get this to work:
Firstly the
Response.push()
method expects both the request and response headers, and will send both of them out immediately. But this means that I would need to know which headers will be set by the middlewares and route handlers before actually passing the push stream throughapp._router.handle()
. In other words since the http/2 headers frame was already sent byResponse.push()
, the headers set (and sent?) by the Express middlewares and route handlers will no longer be "seen". One way to circumvent this issue is to go through the Express stack twice, once for collecting the headers (without outputting any data), and once to actually send the data. But this is far from optimal, as for example Express's static middleware will read the file from disk twice (resulting in extra delay). So my question here is: Can I somehow tell theResponse.push()
method to not send the response headers, and let them be sent by the Express middlewares and route handlers.Secondly, the
app._router.handle()
expects a request and response object. Constructing a fake request object was fairly straightforward. But what is the proper way to construct aServerResponse
object using the push stream object that is returned by spdy'sResponse.push()
? Currently I try to do this as shown below by making theServerResponse
"inherit" the push stream object and overriding some methods. But this approach isn't really "clean"and only partially works (e.g., piping doesn't work yet). _Minor edit: Piping etc. did work, but I forgot to disable sending the headers in mywriteHeadOverride()
which caused the headers to be sent twice. The second header frame was interpreted by Chrome as a (malformed, e.g., it doesn't have the END_STREAM flag) trailing header, which caused an HTTP2_STREAMERROR (which didn't seem to propagate to my Node code?). My two questions still remain though...The override functions:
Click to expand
Any feedback would be greatly appreciated.