diff options
author | Timothy J Fontaine <tjfontaine@gmail.com> | 2013-04-11 16:00:19 -0700 |
---|---|---|
committer | Ben Noordhuis <info@bnoordhuis.nl> | 2013-04-17 00:08:28 +0200 |
commit | 6717fdccb4a3ebf55d87ae5df1e24ebebd4081f1 (patch) | |
tree | c53e593b3c999773d09d00ee48f4af9ee2046719 /lib/http.js | |
parent | dc9f97b7b99f27eaf21faf29c7df8c9d823ef863 (diff) | |
download | node-new-6717fdccb4a3ebf55d87ae5df1e24ebebd4081f1.tar.gz |
http: move Server and ServerResponse out
Diffstat (limited to 'lib/http.js')
-rw-r--r-- | lib/http.js | 434 |
1 files changed, 6 insertions, 428 deletions
diff --git a/lib/http.js b/lib/http.js index e0d3e339eb..3476e45740 100644 --- a/lib/http.js +++ b/lib/http.js @@ -35,218 +35,15 @@ var common = require('_http_common'); var parsers = exports.parsers = common.parsers; var freeParser = common.freeParser; var debug = common.debug; -var CRLF = common.CRLF; -var continueExpression = common.continueExpression; -var chunkExpression = common.chunkExpression; - - -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 -}; var outgoing = require('_http_outgoing'); var OutgoingMessage = exports.OutgoingMessage = outgoing.OutgoingMessage; - -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); -}; +var server = require('_http_server'); +exports.ServerResponse = server.ServerResponse; +exports.STATUS_CODES = server.STATUS_CODES; var agent = require('_http_agent'); @@ -734,235 +531,16 @@ exports.get = function(options, cb) { }; -function ondrain() { - if (this._httpMessage) this._httpMessage.emit('drain'); -} - - -function httpSocketSetup(socket) { - socket.removeListener('drain', ondrain); - socket.on('drain', ondrain); -} - - -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; +var httpSocketSetup = common.httpSocketSetup; +exports._connectionListener = server._connectionListener; +var Server = exports.Server = server.Server; exports.createServer = function(requestListener) { return new Server(requestListener); }; -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); - - // This is start + byteParsed - var bodyHead = d.slice(start + bytesParsed, end); - - var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; - if (EventEmitter.listenerCount(self, eventName) > 0) { - self.emit(eventName, req, req.socket, bodyHead); - } 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; - // Legacy Interface function Client(port, host) { |