// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. var util = require('util'); var net = require('net'); var EventEmitter = require('events').EventEmitter; var HTTPParser = process.binding('http_parser').HTTPParser; var assert = require('assert').ok; // an empty buffer for UPGRADE/CONNECT bodyHead compatibility var emptyBuffer = new Buffer(0); var common = require('_http_common'); var parsers = common.parsers; var freeParser = common.freeParser; var debug = common.debug; var CRLF = common.CRLF; var continueExpression = common.continueExpression; var chunkExpression = common.chunkExpression; var httpSocketSetup = common.httpSocketSetup; var OutgoingMessage = require('_http_outgoing').OutgoingMessage; var STATUS_CODES = exports.STATUS_CODES = { 100 : 'Continue', 101 : 'Switching Protocols', 102 : 'Processing', // RFC 2518, obsoleted by RFC 4918 200 : 'OK', 201 : 'Created', 202 : 'Accepted', 203 : 'Non-Authoritative Information', 204 : 'No Content', 205 : 'Reset Content', 206 : 'Partial Content', 207 : 'Multi-Status', // RFC 4918 300 : 'Multiple Choices', 301 : 'Moved Permanently', 302 : 'Moved Temporarily', 303 : 'See Other', 304 : 'Not Modified', 305 : 'Use Proxy', 307 : 'Temporary Redirect', 400 : 'Bad Request', 401 : 'Unauthorized', 402 : 'Payment Required', 403 : 'Forbidden', 404 : 'Not Found', 405 : 'Method Not Allowed', 406 : 'Not Acceptable', 407 : 'Proxy Authentication Required', 408 : 'Request Time-out', 409 : 'Conflict', 410 : 'Gone', 411 : 'Length Required', 412 : 'Precondition Failed', 413 : 'Request Entity Too Large', 414 : 'Request-URI Too Large', 415 : 'Unsupported Media Type', 416 : 'Requested Range Not Satisfiable', 417 : 'Expectation Failed', 418 : 'I\'m a teapot', // RFC 2324 422 : 'Unprocessable Entity', // RFC 4918 423 : 'Locked', // RFC 4918 424 : 'Failed Dependency', // RFC 4918 425 : 'Unordered Collection', // RFC 4918 426 : 'Upgrade Required', // RFC 2817 428 : 'Precondition Required', // RFC 6585 429 : 'Too Many Requests', // RFC 6585 431 : 'Request Header Fields Too Large',// RFC 6585 500 : 'Internal Server Error', 501 : 'Not Implemented', 502 : 'Bad Gateway', 503 : 'Service Unavailable', 504 : 'Gateway Time-out', 505 : 'HTTP Version Not Supported', 506 : 'Variant Also Negotiates', // RFC 2295 507 : 'Insufficient Storage', // RFC 4918 509 : 'Bandwidth Limit Exceeded', 510 : 'Not Extended', // RFC 2774 511 : 'Network Authentication Required' // RFC 6585 }; function ServerResponse(req) { OutgoingMessage.call(this); if (req.method === 'HEAD') this._hasBody = false; this.sendDate = true; if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te); this.shouldKeepAlive = false; } } util.inherits(ServerResponse, OutgoingMessage); exports.ServerResponse = ServerResponse; ServerResponse.prototype.statusCode = 200; function onServerResponseClose() { // EventEmitter.emit makes a copy of the 'close' listeners array before // calling the listeners. detachSocket() unregisters onServerResponseClose // but if detachSocket() is called, directly or indirectly, by a 'close' // listener, onServerResponseClose is still in that copy of the listeners // array. That is, in the example below, b still gets called even though // it's been removed by a: // // var obj = new events.EventEmitter; // obj.on('event', a); // obj.on('event', b); // function a() { obj.removeListener('event', b) } // function b() { throw "BAM!" } // obj.emit('event'); // throws // // Ergo, we need to deal with stale 'close' events and handle the case // where the ServerResponse object has already been deconstructed. // Fortunately, that requires only a single if check. :-) if (this._httpMessage) this._httpMessage.emit('close'); } ServerResponse.prototype.assignSocket = function(socket) { assert(!socket._httpMessage); socket._httpMessage = this; socket.on('close', onServerResponseClose); this.socket = socket; this.connection = socket; this.emit('socket', socket); this._flush(); }; ServerResponse.prototype.detachSocket = function(socket) { assert(socket._httpMessage == this); socket.removeListener('close', onServerResponseClose); socket._httpMessage = null; this.socket = this.connection = null; }; ServerResponse.prototype.writeContinue = function() { this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii'); this._sent100 = true; }; ServerResponse.prototype._implicitHeader = function() { this.writeHead(this.statusCode); }; ServerResponse.prototype.writeHead = function(statusCode) { var reasonPhrase, headers, headerIndex; if (typeof arguments[1] == 'string') { reasonPhrase = arguments[1]; headerIndex = 2; } else { reasonPhrase = STATUS_CODES[statusCode] || 'unknown'; headerIndex = 1; } this.statusCode = statusCode; var obj = arguments[headerIndex]; if (obj && this._headers) { // Slow-case: when progressive API and header fields are passed. headers = this._renderHeaders(); if (Array.isArray(obj)) { // handle array case // TODO: remove when array is no longer accepted var field; for (var i = 0, len = obj.length; i < len; ++i) { field = obj[i][0]; if (headers[field] !== undefined) { obj.push([field, headers[field]]); } } headers = obj; } else { // handle object case var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { var k = keys[i]; if (k) headers[k] = obj[k]; } } } else if (this._headers) { // only progressive api is used headers = this._renderHeaders(); } else { // only writeHead() called headers = obj; } var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + reasonPhrase + CRLF; if (statusCode === 204 || statusCode === 304 || (100 <= statusCode && statusCode <= 199)) { // RFC 2616, 10.2.5: // The 204 response MUST NOT include a message-body, and thus is always // terminated by the first empty line after the header fields. // RFC 2616, 10.3.5: // The 304 response MUST NOT contain a message-body, and thus is always // terminated by the first empty line after the header fields. // RFC 2616, 10.1 Informational 1xx: // This class of status code indicates a provisional response, // consisting only of the Status-Line and optional headers, and is // terminated by an empty line. this._hasBody = false; } // don't keep alive connections where the client expects 100 Continue // but we sent a final status; they may put extra bytes on the wire. if (this._expect_continue && !this._sent100) { this.shouldKeepAlive = false; } this._storeHeader(statusLine, headers); }; ServerResponse.prototype.writeHeader = function() { this.writeHead.apply(this, arguments); }; function Server(requestListener) { if (!(this instanceof Server)) return new Server(requestListener); net.Server.call(this, { allowHalfOpen: true }); if (requestListener) { this.addListener('request', requestListener); } // Similar option to this. Too lazy to write my own docs. // http://www.squid-cache.org/Doc/config/half_closed_clients/ // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F this.httpAllowHalfOpen = false; this.addListener('connection', connectionListener); this.addListener('clientError', function(err, conn) { conn.destroy(err); }); this.timeout = 2 * 60 * 1000; } util.inherits(Server, net.Server); Server.prototype.setTimeout = function(msecs, callback) { this.timeout = msecs; if (callback) this.on('timeout', callback); }; exports.Server = Server; function connectionListener(socket) { var self = this; var outgoing = []; var incoming = []; function abortIncoming() { while (incoming.length) { var req = incoming.shift(); req.emit('aborted'); req.emit('close'); } // abort socket._httpMessage ? } function serverSocketCloseListener() { debug('server socket close'); // mark this parser as reusable if (this.parser) freeParser(this.parser); abortIncoming(); } debug('SERVER new http connection'); httpSocketSetup(socket); // If the user has added a listener to the server, // request, or response, then it's their responsibility. // otherwise, destroy on timeout by default if (self.timeout) socket.setTimeout(self.timeout); socket.on('timeout', function() { var req = socket.parser && socket.parser.incoming; var reqTimeout = req && !req.complete && req.emit('timeout', socket); var res = socket._httpMessage; var resTimeout = res && res.emit('timeout', socket); var serverTimeout = self.emit('timeout', socket); if (!reqTimeout && !resTimeout && !serverTimeout) socket.destroy(); }); var parser = parsers.alloc(); parser.reinitialize(HTTPParser.REQUEST); parser.socket = socket; socket.parser = parser; parser.incoming = null; // Propagate headers limit from server instance to parser if (typeof this.maxHeadersCount === 'number') { parser.maxHeaderPairs = this.maxHeadersCount << 1; } else { // Set default value because parser may be reused from FreeList parser.maxHeaderPairs = 2000; } socket.addListener('error', function(e) { self.emit('clientError', e, this); }); socket.ondata = function(d, start, end) { var ret = parser.execute(d, start, end - start); if (ret instanceof Error) { debug('parse error'); socket.destroy(ret); } else if (parser.incoming && parser.incoming.upgrade) { // Upgrade or CONNECT var bytesParsed = ret; var req = parser.incoming; socket.ondata = null; socket.onend = null; socket.removeListener('close', serverSocketCloseListener); parser.finish(); freeParser(parser, req); var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; if (EventEmitter.listenerCount(self, eventName) > 0) { // This is start + byteParsed var bodyHead = d.slice(start + bytesParsed, end); socket.unshift(bodyHead); self.emit(eventName, req, req.socket, emptyBuffer); } else { // Got upgrade header or CONNECT method, but have no handler. socket.destroy(); } } }; socket.onend = function() { var ret = parser.finish(); if (ret instanceof Error) { debug('parse error'); socket.destroy(ret); return; } if (!self.httpAllowHalfOpen) { abortIncoming(); if (socket.writable) socket.end(); } else if (outgoing.length) { outgoing[outgoing.length - 1]._last = true; } else if (socket._httpMessage) { socket._httpMessage._last = true; } else { if (socket.writable) socket.end(); } }; socket.addListener('close', serverSocketCloseListener); // The following callback is issued after the headers have been read on a // new message. In this callback we setup the response object and pass it // to the user. parser.onIncoming = function(req, shouldKeepAlive) { incoming.push(req); var res = new ServerResponse(req); res.shouldKeepAlive = shouldKeepAlive; DTRACE_HTTP_SERVER_REQUEST(req, socket); COUNTER_HTTP_SERVER_REQUEST(); if (socket._httpMessage) { // There are already pending outgoing res, append. outgoing.push(res); } else { res.assignSocket(socket); } // When we're finished writing the response, check if this is the last // respose, if so destroy the socket. res.on('finish', function() { // Usually the first incoming element should be our request. it may // be that in the case abortIncoming() was called that the incoming // array will be empty. assert(incoming.length == 0 || incoming[0] === req); incoming.shift(); // if the user never called req.read(), and didn't pipe() or // .resume() or .on('data'), then we call req._dump() so that the // bytes will be pulled off the wire. if (!req._consuming) req._dump(); res.detachSocket(socket); if (res._last) { socket.destroySoon(); } else { // start sending the next message var m = outgoing.shift(); if (m) { m.assignSocket(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); } else { res.writeContinue(); self.emit('request', req, res); } } else { self.emit('request', req, res); } return false; // Not a HEAD response. (Not even a response!) }; } exports._connectionListener = connectionListener;