diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a4fbad..a3e8fe4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,8 +6,9 @@ on: jobs: test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: + fail-fast: false matrix: name: - Node.js 0.8 @@ -72,11 +73,11 @@ jobs: - name: Node.js 6.x node-version: "6.17" - npm-i: mocha@6.2.2 nyc@14.1.1 + npm-i: mocha@6.2.2 nyc@14.1.1 supertest@6.1.6 - name: Node.js 7.x node-version: "7.10" - npm-i: mocha@6.2.2 nyc@14.1.1 + npm-i: mocha@6.2.2 nyc@14.1.1 supertest@6.1.6 - name: Node.js 8.x node-version: "8.17" diff --git a/index.js b/index.js index 7db6375..32c605f 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,27 @@ function createWriteHead (prevWriteHead, listener) { } } +function createRespond (prevRespond, listener) { + var fired = false + + var headers = { 0: {}, 1: {} } + + return function writeRespond () { + headers['0'] = { ...headers['0'], ...arguments[0] } + headers['1'] = { ...headers['1'], ...arguments[1] } + + // fire listener + if (!fired) { + fired = true + listener.call(this) + } + + if (!this.headersSent) { + return prevRespond.apply(this, [headers['0'], headers['1']]) + } + } +} + /** * Execute a listener when a response is about to write headers. * @@ -62,7 +83,13 @@ function onHeaders (res, listener) { throw new TypeError('argument listener must be a function') } - res.writeHead = createWriteHead(res.writeHead, listener) + if (res.writeHead) { + res.writeHead = createWriteHead(res.writeHead, listener) + } + + if (res.respond) { + res.respond = createRespond(res.respond, listener) + } } /** diff --git a/package.json b/package.json index 03fb940..527b348 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "event", "headers", "http", + "http2", "onheaders" ], "repository": "jshttp/on-headers", @@ -21,7 +22,7 @@ "eslint-plugin-standard": "4.0.1", "mocha": "10.2.0", "nyc": "15.1.0", - "supertest": "4.0.2" + "supertest": "6.3.4" }, "files": [ "LICENSE", @@ -34,7 +35,7 @@ }, "scripts": { "lint": "eslint --plugin markdown --ext js,md .", - "test": "mocha --reporter spec --bail --check-leaks test/", + "test": "mocha --reporter spec --exit --check-leaks test/", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test", "version": "node scripts/version-history.js && git add HISTORY.md" diff --git a/test/test.js b/test/http.js similarity index 79% rename from test/test.js rename to test/http.js index 6159695..f39a449 100644 --- a/test/test.js +++ b/test/http.js @@ -1,14 +1,35 @@ - var assert = require('assert') var http = require('http') +var http2 var onHeaders = require('..') var request = require('supertest') +var server = require('./support/servers') -describe('onHeaders(res, listener)', function () { - it('should fire after setHeader', function (done) { - var server = createServer(echoListener) +try { + http2 = require('http2') +} catch (e) {} + +var createHTTPServer = server.createHTTPServer +var createHTTP2Server = server.createHTTP2ServerCompatibilityLayer + +var topDescribe = function (type, createServer) { + var options - request(server) + if (type === 'http2') { + options = { http2: true } + } + + describe('onHeaders(res, listener)', function () { + it('should fire after setHeader', function (done) { + request(createServer(echoListener), options) + .get('/') + .expect('X-Outgoing-Echo', 'test') + .expect(200, done) + }) + }) + + it('should fire after setHeader', function (done) { + request(createServer(echoListener), options) .get('/') .expect('X-Outgoing-Echo', 'test') .expect(200, done) @@ -22,7 +43,7 @@ describe('onHeaders(res, listener)', function () { res.write('1') } - request(server) + request(server, options) .get('/') .expect('X-Outgoing-Echo', 'test') .expect(200, '1', done) @@ -37,7 +58,7 @@ describe('onHeaders(res, listener)', function () { this.setHeader('X-Headers', getAllHeaderNames(this).join(',')) } - request(server) + request(server, options) .get('/') .expect('X-Headers', '') .expect(200, done) @@ -57,7 +78,7 @@ describe('onHeaders(res, listener)', function () { count++ } - request(server) + request(server, options) .get('/') .expect(200, function (err) { if (err) return done(err) @@ -76,7 +97,7 @@ describe('onHeaders(res, listener)', function () { res.setHeader('X-Outgoing', 'test') } - request(server) + request(server, options) .get('/') .expect('X-Outgoing-Echo', 'test,3,2,1') .expect(200, done) @@ -93,7 +114,7 @@ describe('onHeaders(res, listener)', function () { it('should be required', function (done) { var server = createServer() - request(server) + request(server, options) .get('/') .expect(500, /listener.*function/, done) }) @@ -101,7 +122,7 @@ describe('onHeaders(res, listener)', function () { it('should only accept function', function (done) { var server = createServer(42) - request(server) + request(server, options) .get('/') .expect(500, /listener.*function/, done) }) @@ -112,7 +133,7 @@ describe('onHeaders(res, listener)', function () { it('should be available in listener', function (done) { var server = createServer(echoListener) - request(server) + request(server, options) .get('/') .expect('X-Outgoing-Echo', 'test') .expect(200, done) @@ -131,7 +152,7 @@ describe('onHeaders(res, listener)', function () { this.setHeader('X-Status', this.statusCode) } - request(server) + request(server, options) .get('/') .expect('X-Status', '201') .expect(201, done) @@ -149,7 +170,7 @@ describe('onHeaders(res, listener)', function () { this.statusCode = 202 } - request(server) + request(server, options) .get('/') .expect('X-Status', '201') .expect(202, done) @@ -162,25 +183,33 @@ describe('onHeaders(res, listener)', function () { res.writeHead() // error } - request(server) + request(server, options) .get('/') .expect(500, done) }) it('should retain return value', function (done) { - var server = http.createServer(function (req, res) { + function callbackServer (req, res) { if (req.url === '/attach') { onHeaders(res, appendHeader(1)) } res.end(typeof res.writeHead(200)) - }) + } - request(server) + var server + if (type === 'http') { + server = http.createServer(callbackServer) + } else { + server = http2.createServer(callbackServer) + } + + request(server, options) .get('/') .expect(200, function (err, res) { if (err) return done(err) - request(server) + + request(server, options) .get('/attach') .expect(200, res.text, done) }) @@ -196,7 +225,7 @@ describe('onHeaders(res, listener)', function () { res.writeHead(200, 'OK') } - request(server) + request(server, options) .get('/') .expect('X-Outgoing-Echo', 'test') .expect(200, done) @@ -211,7 +240,7 @@ describe('onHeaders(res, listener)', function () { res.writeHead(200, 'OK', { 'X-Outgoing': 'test' }) } - request(server) + request(server, options) .get('/') .expect('X-Outgoing-Echo', 'test') .expect(200, done) @@ -231,7 +260,7 @@ describe('onHeaders(res, listener)', function () { this.setHeader('X-Outgoing-Echo', this.getHeader('X-Outgoing')) } - request(server) + request(server, options) .get('/') .expect('X-Status', '201') .expect('X-Outgoing-Echo', 'test') @@ -250,7 +279,7 @@ describe('onHeaders(res, listener)', function () { this.setHeader('X-Outgoing-Echo', this.getHeader('X-Outgoing')) } - request(server) + request(server, options) .get('/') .expect('X-Status', '201') .expect('X-Outgoing-Echo', 'test') @@ -271,30 +300,13 @@ describe('onHeaders(res, listener)', function () { this.setHeader('X-Outgoing-Echo', this.getHeader('X-Outgoing')) } - request(server) + request(server, options) .get('/') .expect('X-Status', '201') .expect('X-Outgoing-Echo', 'test') .expect(201, done) }) }) -}) - -function createServer (listener, handler) { - var fn = handler || echoHandler - - return http.createServer(function (req, res) { - try { - onHeaders(res, listener) - fn(req, res) - res.statusCode = 200 - } catch (err) { - res.statusCode = 500 - res.write(err.message) - } finally { - res.end() - } - }) } function appendHeader (num) { @@ -303,10 +315,6 @@ function appendHeader (num) { } } -function echoHandler (req, res) { - res.setHeader('X-Outgoing', 'test') -} - function echoListener () { this.setHeader('X-Outgoing-Echo', this.getHeader('X-Outgoing')) } @@ -316,3 +324,20 @@ function getAllHeaderNames (res) { ? Object.keys(this._headers || {}) : res.getHeaderNames() } + +var servers = [ + ['http', createHTTPServer] +] + +var nodeVersion = process.versions.node.split('.').map(Number) + +// `superagent` only supports `http2` since Node.js@10 +if (http2 && nodeVersion[0] >= 10) { + servers.push(['http2', createHTTP2Server]) +} + +for (var i = 0; i < servers.length; i++) { + var tests = topDescribe.bind(undefined, servers[i][0], servers[i][1]) + + describe(servers[i][0], tests) +} diff --git a/test/http2.js b/test/http2.js new file mode 100644 index 0000000..ed38245 --- /dev/null +++ b/test/http2.js @@ -0,0 +1,20 @@ +var server = require('./support/servers') + +var createServer = server.createHTTP2Server + +var request = require('supertest') + +describe('http2', function () { + describe('onHeaders(stream, listener)', function () { + it('should fire after respond', function (done) { + request(createServer(echoListener), { http2: true }) + .get('/') + .expect('x-outgoing-echo', 'test') + .expect(200, done) + }) + }) +}) + +function echoListener () { + this.respond({ 'x-Outgoing-echo': 'test' }) +} diff --git a/test/support/servers.js b/test/support/servers.js new file mode 100644 index 0000000..2327998 --- /dev/null +++ b/test/support/servers.js @@ -0,0 +1,72 @@ +var http = require('http') +var http2 + +var onHeaders = require('../..') + +try { + http2 = require('http2') +} catch (e) {} + +exports.createHTTPServer = createHTTPServer +exports.createHTTP2Server = createHTTP2Server +exports.createHTTP2ServerCompatibilityLayer = createHTTP2ServerCompatibilityLayer + +function createHTTPServer (listener, handler) { + var fn = handler || echoHandler + + return http.createServer(function (req, res) { + try { + onHeaders(res, listener) + fn(req, res) + res.statusCode = 200 + } catch (err) { + res.statusCode = 500 + res.write(err.message) + } finally { + res.end() + } + }) +} + +function createHTTP2ServerCompatibilityLayer (listener, handler) { + var fn = handler || echoHandler + + return http2.createServer(function (req, res) { + try { + onHeaders(res, listener) + fn(req, res) + res.statusCode = 200 + } catch (err) { + res.statusCode = 500 + res.write(err.message) + } finally { + res.end() + } + }) +} + +function createHTTP2Server (listener) { + var server = http2.createServer() + + server.on('stream', function (stream) { + try { + onHeaders(stream, listener) + stream.respond({ + ':status': 200 + }) + } catch (err) { + stream.respond({ + ':status': 500 + }) + stream.write(err.message) + } finally { + stream.end() + } + }) + + return server +} + +function echoHandler (req, res) { + res.setHeader('X-Outgoing', 'test') +}