diff --git a/doc/api/http.markdown b/doc/api/http.markdown index a5f05fdf0e5bea..f12e0805870bf8 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -192,6 +192,17 @@ The request implements the [Writable Stream][] interface. This is an Emitted when the request has been aborted by the client. This event is only emitted on the first call to `abort()`. +### Event: 'checkExpectation' + +`function (request, response) { }` + +Emitted each time a request with an http Expect header is received, where the +value is not 100-continue. If this event isn't listened for, the server will +automatically respond with a 417 Expectation Failed as appropriate. + +Note that when this event is emitted and handled, the `request` event will +not be emitted. + ### Event: 'connect' `function (response, socket, head) { }` diff --git a/lib/_http_server.js b/lib/_http_server.js index 7534ceba95e9e1..7527bffe7fa07d 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -2,7 +2,6 @@ const util = require('util'); const net = require('net'); -const EventEmitter = require('events'); const HTTPParser = process.binding('http_parser').HTTPParser; const assert = require('assert').ok; const common = require('_http_common'); @@ -392,7 +391,7 @@ function connectionListener(socket) { parser = null; var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; - if (EventEmitter.listenerCount(self, eventName) > 0) { + if (self.listenerCount(eventName) > 0) { debug('SERVER have listener for %s', eventName); var bodyHead = d.slice(bytesParsed, d.length); @@ -517,14 +516,23 @@ function connectionListener(socket) { } if (req.headers.expect !== undefined && - (req.httpVersionMajor == 1 && req.httpVersionMinor == 1) && - continueExpression.test(req.headers['expect'])) { - res._expect_continue = true; - if (EventEmitter.listenerCount(self, 'checkContinue') > 0) { - self.emit('checkContinue', req, res); + (req.httpVersionMajor == 1 && req.httpVersionMinor == 1)) { + if (continueExpression.test(req.headers.expect)) { + res._expect_continue = true; + + if (self.listenerCount('checkContinue') > 0) { + self.emit('checkContinue', req, res); + } else { + res.writeContinue(); + self.emit('request', req, res); + } } else { - res.writeContinue(); - self.emit('request', req, res); + if (self.listenerCount('checkExpectation') > 0) { + self.emit('checkExpectation', req, res); + } else { + res.writeHead(417); + res.end(); + } } } else { self.emit('request', req, res); diff --git a/test/parallel/test-http-expect-handling.js b/test/parallel/test-http-expect-handling.js new file mode 100644 index 00000000000000..fd88ae0ded6074 --- /dev/null +++ b/test/parallel/test-http-expect-handling.js @@ -0,0 +1,55 @@ +// Spec documentation http://httpwg.github.io/specs/rfc7231.html#header.expect +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +const tests = [417, 417]; + +let testsComplete = 0; +let testIdx = 0; + +const s = http.createServer(function(req, res) { + throw new Error('this should never be executed'); +}); + +s.listen(common.PORT, nextTest); + +function nextTest() { + const options = { + port: common.PORT, + headers: { 'Expect': 'meoww' } + }; + + if (testIdx === tests.length) { + return s.close(); + } + + const test = tests[testIdx]; + + if (testIdx > 0) { + s.on('checkExpectation', common.mustCall((req, res) => { + res.statusCode = 417; + res.end(); + })); + } + + http.get(options, function(response) { + console.log('client: expected status: ' + test); + console.log('client: statusCode: ' + response.statusCode); + assert.equal(response.statusCode, test); + assert.equal(response.statusMessage, 'Expectation Failed'); + + response.on('end', function() { + testsComplete++; + testIdx++; + nextTest(); + }); + response.resume(); + }); +} + + +process.on('exit', function() { + assert.equal(2, testsComplete); +});