Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit d59512f

Browse files
mnotry
authored andcommitted
Add support for handling Expect: 100-continue
HTTP/1.1 requests, either with an event (check_continue) or automatically, if no event handler is present. Add client-side expect/continue support, tests. Expound upon client requirements for expect/continue.
1 parent 4a7562d commit d59512f

3 files changed

Lines changed: 153 additions & 5 deletions

File tree

doc/api.markdown

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,22 @@ This is an `EventEmitter` with the following events:
16591659
Emitted each time there is request. Note that there may be multiple requests
16601660
per connection (in the case of keep-alive connections).
16611661

1662+
### Event: 'checkContinue'
1663+
1664+
`function (request, response) {}`
1665+
1666+
Emitted each time a request with an http Expect: 100-continue is received.
1667+
If this event isn't listened for, the server will automatically respond
1668+
with a 100 Continue as appropriate.
1669+
1670+
Handling this event involves calling `response.writeContinue` if the client
1671+
should continue to send the request body, or generating an appropriate HTTP
1672+
response (e.g., 400 Bad Request) if the client should not continue to send the
1673+
request body.
1674+
1675+
Note that when this event is emitted and handled, the `request` event will
1676+
not be emitted.
1677+
16621678
### Event: 'upgrade'
16631679

16641680
`function (request, socket, head)`
@@ -1834,6 +1850,11 @@ authentication details.
18341850
This object is created internally by a HTTP server--not by the user. It is
18351851
passed as the second parameter to the `'request'` event. It is a `Writable Stream`.
18361852

1853+
### response.writeContinue()
1854+
1855+
Sends a HTTP/1.1 100 Continue message to the client, indicating that
1856+
the request body should be sent. See the the `checkContinue` event on
1857+
`Server`.
18371858

18381859
### response.writeHead(statusCode, [reasonPhrase], [headers])
18391860

@@ -1936,6 +1957,11 @@ There are a few special headers that should be noted.
19361957

19371958
* Sending a 'Content-length' header will disable the default chunked encoding.
19381959

1960+
* Sending an 'Expect' header will immediately send the request headers.
1961+
Usually, when sending 'Expect: 100-continue', you should both set a timeout
1962+
and listen for the `continue` event. See RFC2616 Section 8.2.3 for more
1963+
information.
1964+
19391965

19401966
### Event: 'upgrade'
19411967

@@ -1947,6 +1973,15 @@ connections closed.
19471973

19481974
See the description of the `upgrade` event for `http.Server` for further details.
19491975

1976+
### Event: 'continue'
1977+
1978+
`function ()`
1979+
1980+
Emitted when the server sends a '100 Continue' HTTP response, usually because
1981+
the request contained 'Expect: 100-continue'. This is an instruction that
1982+
the client should send the request body.
1983+
1984+
19501985
### http.createClient(port, host='localhost', secure=false, [credentials])
19511986

19521987
Constructs a new HTTP client. `port` and

lib/http.js

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ var parsers = new FreeList('parsers', 1000, function () {
7575
parser.incoming.method = info.method;
7676
} else {
7777
// client only
78-
parser.incoming.statusCode = info.statusCode;
78+
parser.incoming.statusCode = info.statusCode;
7979
}
8080

8181
parser.incoming.upgrade = info.upgrade;
@@ -178,6 +178,8 @@ var transferEncodingExpression = /Transfer-Encoding/i;
178178
var closeExpression = /close/i;
179179
var chunkExpression = /chunk/i;
180180
var contentLengthExpression = /Content-Length/i;
181+
var expectExpression = /Expect/i;
182+
var continueExpression = /100-continue/i;
181183

182184

183185
/* Abstract base class for ServerRequest and ClientResponse. */
@@ -302,7 +304,6 @@ sys.inherits(OutgoingMessage, events.EventEmitter);
302304
exports.OutgoingMessage = OutgoingMessage;
303305

304306
// This abstract either writing directly to the socket or buffering it.
305-
// Rename to _writeRaw() ?
306307
OutgoingMessage.prototype._send = function (data, encoding) {
307308
// This is a shameful hack to get the headers and first body chunk onto
308309
// the same packet. Future versions of Node are going to take care of
@@ -316,7 +317,10 @@ OutgoingMessage.prototype._send = function (data, encoding) {
316317
}
317318
this._headerSent = true;
318319
}
320+
this._writeRaw(data, encoding)
321+
}
319322

323+
OutgoingMessage.prototype._writeRaw = function(data, encoding) {
320324
if (this.connection._outgoing[0] === this && this.connection.writable) {
321325
// There might be pending data in the this.output buffer.
322326
while (this.output.length) {
@@ -371,6 +375,7 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) {
371375
var sentConnectionHeader = false;
372376
var sentContentLengthHeader = false;
373377
var sentTransferEncodingHeader = false;
378+
var sentExpect = false;
374379

375380
// firstLine in the case of request is: "GET /index.html HTTP/1.1\r\n"
376381
// in the case of response it is: "HTTP/1.1 200 OK\r\n"
@@ -396,6 +401,9 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) {
396401
} else if (contentLengthExpression.test(field)) {
397402
sentContentLengthHeader = true;
398403

404+
} else if (expectExpression.test(field)) {
405+
sentExpect = true;
406+
399407
}
400408
}
401409

@@ -451,7 +459,11 @@ OutgoingMessage.prototype._storeHeader = function (firstLine, headers) {
451459

452460
this._header = messageHeader + CRLF;
453461
this._headerSent = false;
454-
// wait until the first body chunk, or close(), is sent to flush.
462+
// wait until the first body chunk, or close(), is sent to flush,
463+
// UNLESS we're sending Expect: 100-continue.
464+
if (sentExpect) {
465+
this._send("");
466+
}
455467
};
456468

457469

@@ -592,6 +604,10 @@ function ServerResponse (req) {
592604
sys.inherits(ServerResponse, OutgoingMessage);
593605
exports.ServerResponse = ServerResponse;
594606

607+
ServerResponse.prototype.writeContinue = function () {
608+
this._writeRaw("HTTP/1.1 100 Continue" + CRLF + CRLF, 'ascii');
609+
this._sent100 = true;
610+
}
595611

596612
ServerResponse.prototype.writeHead = function (statusCode) {
597613
var reasonPhrase, headers, headerIndex;
@@ -613,16 +629,28 @@ ServerResponse.prototype.writeHead = function (statusCode) {
613629
var statusLine = "HTTP/1.1 " + statusCode.toString() + " "
614630
+ reasonPhrase + CRLF;
615631

616-
if (statusCode === 204 || statusCode === 304) {
632+
if ( statusCode === 204
633+
|| statusCode === 304
634+
|| (statusCode >= 100 && statusCode <= 199)
635+
) {
617636
// RFC 2616, 10.2.5:
618637
// The 204 response MUST NOT include a message-body, and thus is always
619638
// terminated by the first empty line after the header fields.
620639
// RFC 2616, 10.3.5:
621640
// The 304 response MUST NOT contain a message-body, and thus is always
622641
// terminated by the first empty line after the header fields.
642+
// RFC 2616, 10.1 Informational 1xx:
643+
// This class of status code indicates a provisional response,
644+
// consisting only of the Status-Line and optional headers, and is
645+
// terminated by an empty line.
623646
this._hasBody = false;
624647
}
625648

649+
// don't keep alive connections where the client expects 100 Continue
650+
// but we sent a final status; they may put extra bytes on the wire.
651+
if (this._expect_continue && ! this._sent100) {
652+
this._shouldKeepAlive = false;
653+
}
626654

627655
this._storeHeader(statusLine, headers);
628656
};
@@ -843,7 +871,19 @@ function connectionListener (socket) {
843871
res.shouldKeepAlive = shouldKeepAlive;
844872
socket._outgoing.push(res);
845873

846-
self.emit('request', req, res);
874+
if ('expect' in req.headers
875+
&& (req.httpVersionMajor == 1 && req.httpVersionMinor == 1)
876+
&& continueExpression.test(req.headers['expect'])) {
877+
res._expect_continue = true;
878+
if (self.listeners("checkContinue").length) {
879+
self.emit("checkContinue", req, res)
880+
} else {
881+
res.writeContinue();
882+
self.emit('request', req, res);
883+
}
884+
} else {
885+
self.emit('request', req, res);
886+
}
847887
return false; // Not a HEAD response. (Not even a response!)
848888
};
849889
}
@@ -952,6 +992,12 @@ Client.prototype._initParser = function () {
952992
// A major oversight in HTTP. Hence this nastiness.
953993
var isHeadResponse = req.method == "HEAD";
954994
debug('isHeadResponse ' + isHeadResponse);
995+
996+
if (res.statusCode == 100) {
997+
// restart the parser, as this is a continue message.
998+
req.emit("continue");
999+
return true;
1000+
}
9551001

9561002
if (req.shouldKeepAlive && res.headers.connection === 'close') {
9571003
req.shouldKeepAlive = false;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
var common = require("../common");
2+
var assert = common.assert;
3+
var sys = require("sys");
4+
var http = require("http");
5+
6+
var outstanding_reqs = 0;
7+
var test_req_body = "some stuff...\n";
8+
var test_res_body = "other stuff!\n";
9+
var sent_continue = false;
10+
var got_continue = false;
11+
12+
function handler(req, res) {
13+
assert.equal(sent_continue, true, "Full response sent before 100 Continue");
14+
common.debug("Server sending full response...");
15+
res.writeHead(200, {
16+
'Content-Type' : 'text/plain',
17+
"ABCD" : "1"
18+
});
19+
res.end(test_res_body);
20+
}
21+
22+
var server = http.createServer(handler);
23+
server.addListener("checkContinue", function(req, res) {
24+
common.debug("Server got Expect: 100-continue...");
25+
res.writeContinue();
26+
sent_continue = true;
27+
handler(req, res);
28+
});
29+
server.listen(common.PORT);
30+
31+
32+
33+
server.addListener("listening", function() {
34+
var client = http.createClient(common.PORT);
35+
req = client.request("POST", "/world", {
36+
"Expect": "100-continue",
37+
});
38+
common.debug("Client sending request...");
39+
outstanding_reqs++;
40+
body = "";
41+
req.addListener('continue', function () {
42+
common.debug("Client got 100 Continue...");
43+
got_continue = true;
44+
req.end(test_req_body);
45+
});
46+
req.addListener('response', function (res) {
47+
assert.equal(got_continue, true,
48+
"Full response received before 100 Continue"
49+
);
50+
assert.equal(200, res.statusCode,
51+
"Final status code was " + res.statusCode + ", not 200."
52+
);
53+
res.setEncoding("utf8");
54+
res.addListener('data', function (chunk) { body += chunk; });
55+
res.addListener('end', function () {
56+
common.debug("Got full response.");
57+
assert.equal(body, test_res_body, "Response body doesn't match.");
58+
// common.debug(sys.inspect(res.headers));
59+
assert.ok("abcd" in res.headers, "Response headers missing.");
60+
outstanding_reqs--;
61+
if (outstanding_reqs == 0) {
62+
server.close();
63+
process.exit();
64+
}
65+
});
66+
});
67+
});

0 commit comments

Comments
 (0)