mirror of https://github.com/lukechilds/node.git
Browse Source
Extensive re-work of http1 compatibility layer based on tests in express, on-finished and finalhandler. Fix handling of HEAD method to match http1. Adjust write, end, etc. to call writeHead as in http1 and as expected by user-land modules. Add socket proxy that instead uses the Http2Stream for the vast majority of socket interactions. Add and change tests to closer represent http1 behaviour. Refs: https://github.com/nodejs/node/pull/15633 Refs: https://github.com/expressjs/express/tree/master/test Refs: https://github.com/jshttp/on-finished/blob/master/test/test.js Refs: https://github.com/pillarjs/finalhandler/blob/master/test/test.js PR-URL: https://github.com/nodejs/node/pull/15702 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>v9.x-staging
committed by
Matteo Collina
21 changed files with 863 additions and 179 deletions
@ -0,0 +1,47 @@ |
|||
'use strict'; |
|||
|
|||
const common = require('../common'); |
|||
if (!common.hasCrypto) |
|||
common.skip('missing crypto'); |
|||
const assert = require('assert'); |
|||
const h2 = require('http2'); |
|||
|
|||
// makes sure that Http2ServerResponse setHeader & removeHeader, do not throw
|
|||
// any errors if the stream was destroyed before headers were sent
|
|||
|
|||
const server = h2.createServer(); |
|||
server.listen(0, common.mustCall(function() { |
|||
const port = server.address().port; |
|||
server.once('request', common.mustCall(function(request, response) { |
|||
response.destroy(); |
|||
|
|||
response.on('finish', common.mustCall(() => { |
|||
assert.strictEqual(response.headersSent, false); |
|||
assert.doesNotThrow(() => response.setHeader('test', 'value')); |
|||
assert.doesNotThrow(() => response.removeHeader('test', 'value')); |
|||
|
|||
process.nextTick(() => { |
|||
assert.doesNotThrow(() => response.setHeader('test', 'value')); |
|||
assert.doesNotThrow(() => response.removeHeader('test', 'value')); |
|||
|
|||
server.close(); |
|||
}); |
|||
})); |
|||
})); |
|||
|
|||
const url = `http://localhost:${port}`; |
|||
const client = h2.connect(url, common.mustCall(function() { |
|||
const headers = { |
|||
':path': '/', |
|||
':method': 'GET', |
|||
':scheme': 'http', |
|||
':authority': `localhost:${port}` |
|||
}; |
|||
const request = client.request(headers); |
|||
request.on('end', common.mustCall(function() { |
|||
client.destroy(); |
|||
})); |
|||
request.end(); |
|||
request.resume(); |
|||
})); |
|||
})); |
@ -0,0 +1,107 @@ |
|||
'use strict'; |
|||
|
|||
const common = require('../common'); |
|||
if (!common.hasCrypto) |
|||
common.skip('missing crypto'); |
|||
const assert = require('assert'); |
|||
const h2 = require('http2'); |
|||
|
|||
// Tests behaviour of the proxied socket in Http2ServerRequest
|
|||
// & Http2ServerResponse - specifically property setters
|
|||
|
|||
const errMsg = { |
|||
code: 'ERR_HTTP2_NO_SOCKET_MANIPULATION', |
|||
type: Error, |
|||
message: 'HTTP/2 sockets should not be directly read from, written to, ' + |
|||
'paused and/or resumed.' |
|||
}; |
|||
|
|||
const server = h2.createServer(); |
|||
|
|||
server.on('request', common.mustCall(function(request, response) { |
|||
const noop = () => {}; |
|||
|
|||
assert.strictEqual(request.stream.destroyed, false); |
|||
request.socket.destroyed = true; |
|||
assert.strictEqual(request.stream.destroyed, true); |
|||
request.socket.destroyed = false; |
|||
|
|||
assert.strictEqual(request.stream.readable, false); |
|||
request.socket.readable = true; |
|||
assert.strictEqual(request.stream.readable, true); |
|||
|
|||
assert.strictEqual(request.stream.writable, true); |
|||
request.socket.writable = false; |
|||
assert.strictEqual(request.stream.writable, false); |
|||
|
|||
const realOn = request.stream.on; |
|||
request.socket.on = noop; |
|||
assert.strictEqual(request.stream.on, noop); |
|||
request.stream.on = realOn; |
|||
|
|||
const realOnce = request.stream.once; |
|||
request.socket.once = noop; |
|||
assert.strictEqual(request.stream.once, noop); |
|||
request.stream.once = realOnce; |
|||
|
|||
const realEnd = request.stream.end; |
|||
request.socket.end = noop; |
|||
assert.strictEqual(request.stream.end, noop); |
|||
request.socket.end = common.mustCall(); |
|||
request.socket.end(); |
|||
request.stream.end = realEnd; |
|||
|
|||
const realEmit = request.stream.emit; |
|||
request.socket.emit = noop; |
|||
assert.strictEqual(request.stream.emit, noop); |
|||
request.stream.emit = realEmit; |
|||
|
|||
const realDestroy = request.stream.destroy; |
|||
request.socket.destroy = noop; |
|||
assert.strictEqual(request.stream.destroy, noop); |
|||
request.stream.destroy = realDestroy; |
|||
|
|||
request.socket.setTimeout = noop; |
|||
assert.strictEqual(request.stream.session.setTimeout, noop); |
|||
|
|||
assert.strictEqual(request.stream.session.socket._isProcessing, undefined); |
|||
request.socket._isProcessing = true; |
|||
assert.strictEqual(request.stream.session.socket._isProcessing, true); |
|||
|
|||
common.expectsError(() => request.socket.read = noop, errMsg); |
|||
common.expectsError(() => request.socket.write = noop, errMsg); |
|||
common.expectsError(() => request.socket.pause = noop, errMsg); |
|||
common.expectsError(() => request.socket.resume = noop, errMsg); |
|||
|
|||
request.stream.on('finish', common.mustCall(() => { |
|||
setImmediate(() => { |
|||
request.socket.setTimeout = noop; |
|||
assert.strictEqual(request.stream.setTimeout, noop); |
|||
|
|||
assert.strictEqual(request.stream._isProcessing, undefined); |
|||
request.socket._isProcessing = true; |
|||
assert.strictEqual(request.stream._isProcessing, true); |
|||
}); |
|||
})); |
|||
response.stream.destroy(); |
|||
})); |
|||
|
|||
server.listen(0, common.mustCall(function() { |
|||
const port = server.address().port; |
|||
const url = `http://localhost:${port}`; |
|||
const client = h2.connect(url, common.mustCall(function() { |
|||
const headers = { |
|||
':path': '/', |
|||
':method': 'GET', |
|||
':scheme': 'http', |
|||
':authority': `localhost:${port}` |
|||
}; |
|||
const request = client.request(headers); |
|||
request.on('end', common.mustCall(() => { |
|||
client.destroy(); |
|||
server.close(); |
|||
})); |
|||
request.end(); |
|||
request.resume(); |
|||
})); |
|||
})); |
@ -0,0 +1,89 @@ |
|||
'use strict'; |
|||
|
|||
const common = require('../common'); |
|||
if (!common.hasCrypto) |
|||
common.skip('missing crypto'); |
|||
const assert = require('assert'); |
|||
const h2 = require('http2'); |
|||
const net = require('net'); |
|||
|
|||
// Tests behaviour of the proxied socket in Http2ServerRequest
|
|||
// & Http2ServerResponse - this proxy socket should mimic the
|
|||
// behaviour of http1 but against the http2 api & model
|
|||
|
|||
const errMsg = { |
|||
code: 'ERR_HTTP2_NO_SOCKET_MANIPULATION', |
|||
type: Error, |
|||
message: 'HTTP/2 sockets should not be directly read from, written to, ' + |
|||
'paused and/or resumed.' |
|||
}; |
|||
|
|||
const server = h2.createServer(); |
|||
|
|||
server.on('request', common.mustCall(function(request, response) { |
|||
assert.ok(request.socket instanceof net.Socket); |
|||
assert.ok(response.socket instanceof net.Socket); |
|||
assert.strictEqual(request.socket, response.socket); |
|||
|
|||
assert.ok(request.socket.readable); |
|||
request.resume(); |
|||
assert.ok(request.socket.writable); |
|||
assert.strictEqual(request.socket.destroyed, false); |
|||
|
|||
request.socket.setTimeout(987); |
|||
assert.strictEqual(request.stream.session._idleTimeout, 987); |
|||
request.socket.setTimeout(0); |
|||
|
|||
common.expectsError(() => request.socket.read(), errMsg); |
|||
common.expectsError(() => request.socket.write(), errMsg); |
|||
common.expectsError(() => request.socket.pause(), errMsg); |
|||
common.expectsError(() => request.socket.resume(), errMsg); |
|||
|
|||
// should have correct this context for socket methods & getters
|
|||
assert.ok(request.socket.address() != null); |
|||
assert.ok(request.socket.remotePort); |
|||
|
|||
request.on('end', common.mustCall(() => { |
|||
assert.strictEqual(request.socket.readable, false); |
|||
assert.doesNotThrow(() => response.socket.destroy()); |
|||
})); |
|||
response.on('finish', common.mustCall(() => { |
|||
assert.ok(request.socket); |
|||
assert.strictEqual(response.socket, undefined); |
|||
assert.ok(request.socket.destroyed); |
|||
assert.strictEqual(request.socket.readable, false); |
|||
process.nextTick(() => { |
|||
assert.strictEqual(request.socket.writable, false); |
|||
server.close(); |
|||
}); |
|||
})); |
|||
|
|||
// properties that do not exist on the proxy are retrieved from the socket
|
|||
assert.ok(request.socket._server); |
|||
assert.strictEqual(request.socket.connecting, false); |
|||
|
|||
// socket events are bound and emitted on Http2Stream
|
|||
request.socket.on('streamClosed', common.mustCall()); |
|||
request.socket.once('streamClosed', common.mustCall()); |
|||
request.socket.on('testEvent', common.mustCall()); |
|||
request.socket.emit('testEvent'); |
|||
})); |
|||
|
|||
server.listen(0, common.mustCall(function() { |
|||
const port = server.address().port; |
|||
const url = `http://localhost:${port}`; |
|||
const client = h2.connect(url, common.mustCall(() => { |
|||
const headers = { |
|||
':path': '/', |
|||
':method': 'GET', |
|||
':scheme': 'http', |
|||
':authority': `localhost:${port}` |
|||
}; |
|||
const request = client.request(headers); |
|||
request.on('end', common.mustCall(() => { |
|||
client.destroy(); |
|||
})); |
|||
request.end(); |
|||
request.resume(); |
|||
})); |
|||
})); |
@ -0,0 +1,28 @@ |
|||
'use strict'; |
|||
|
|||
const common = require('../common'); |
|||
if (!common.hasCrypto) |
|||
common.skip('missing crypto'); |
|||
const assert = require('assert'); |
|||
const http2 = require('http2'); |
|||
|
|||
let client; |
|||
let req; |
|||
const server = http2.createServer(); |
|||
server.on('stream', common.mustCall((stream) => { |
|||
stream.on('error', common.mustCall(() => { |
|||
stream.on('streamClosed', common.mustCall((code) => { |
|||
assert.strictEqual(code, 2); |
|||
client.destroy(); |
|||
server.close(); |
|||
})); |
|||
})); |
|||
|
|||
req.rstStream(2); |
|||
})); |
|||
server.listen(0, common.mustCall(() => { |
|||
client = http2.connect(`http://localhost:${server.address().port}`); |
|||
req = client.request(); |
|||
req.resume(); |
|||
req.on('error', common.mustCall()); |
|||
})); |
Loading…
Reference in new issue