├── package.json ├── README.md ├── LICENSE ├── http.js ├── _http_incoming.js ├── _http_agent.js ├── _http_common.js ├── _http_server.js ├── _http_client.js └── _http_outgoing.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-node", 3 | "version": "1.2.0", 4 | "description": "Node.js http as a standalone package", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/jscissr/http-node.git" 8 | }, 9 | "main": "http.js", 10 | "license": "MIT", 11 | "dependencies": { 12 | "freelist": "^1.0.3", 13 | "http-parser-js": "^0.4.3" 14 | }, 15 | "author": { 16 | "name": "Jan Schär", 17 | "email": "jscissr@gmail.com" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # http-node 2 | This module is a standalone package of [http](https://nodejs.org/api/http.html) from Node.js v6.3.0. 3 | Unlike [http-browserify](https://github.com/substack/http-browserify), this is not a shim but the original code of Node.js, so it requires the `net` module. 4 | This is useful for having the Node.js core APIs on JavaScript platforms other than Node.js, where TCP sockets are available (which can be wrapped in a `net` module). 5 | One example of this is [Chrome Apps](https://developer.chrome.com/apps/sockets_tcp) with [chrome-net](https://github.com/feross/chrome-net). 6 | 7 | ## install / usage with browserify 8 | 9 | ```bash 10 | npm install http-node 11 | ``` 12 | 13 | To use it with browserify, you have to use the JS API of browserify; 14 | the command line API does not support changing builtins. 15 | 16 | Example: 17 | 18 | ```js 19 | const browserify = require('browserify'); 20 | 21 | const builtins = require('browserify/lib/builtins.js'); 22 | var customBuiltins = Object.assign({}, builtins); 23 | customBuiltins.http = require.resolve('http-node'); 24 | 25 | var b = browserify({builtins: customBuiltins}); 26 | 27 | b.add(... 28 | ``` 29 | 30 | ## differences to original Node.js code 31 | 32 | - `require` calls of `_http_*` modules prefixed with `./` 33 | - `require('internal/util').deprecate` replaced by `require('util').deprecate` 34 | - uses [http-parser-js](https://github.com/creationix/http-parser-js) 35 | - commented out calls to `DTRACE_HTTP_*`, `LTTNG_HTTP_*` and `COUNTER_HTTP_*` 36 | - does not presume that sockets have a `_handle` 37 | 38 | ## credit 39 | 40 | The code is taken from the [Node.js](https://nodejs.org) project: 41 | 42 | Copyright Node.js contributors. All rights reserved. 43 | 44 | ## license 45 | 46 | MIT 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project exposes parts of Node.js as a standalone module. 2 | 3 | Node.js is licensed for use as follows: 4 | 5 | """ 6 | Copyright Node.js contributors. All rights reserved. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to 10 | deal in the Software without restriction, including without limitation the 11 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 12 | sell copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 24 | IN THE SOFTWARE. 25 | """ 26 | 27 | This license applies to parts of Node.js originating from the 28 | https://github.com/joyent/node repository: 29 | 30 | """ 31 | Copyright Joyent, Inc. and other Node contributors. All rights reserved. 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to 34 | deal in the Software without restriction, including without limitation the 35 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 36 | sell copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 47 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 48 | IN THE SOFTWARE. 49 | """ 50 | -------------------------------------------------------------------------------- /http.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const internalUtil = util; 5 | const EventEmitter = require('events'); 6 | 7 | 8 | exports.IncomingMessage = require('./_http_incoming').IncomingMessage; 9 | 10 | 11 | const common = require('./_http_common'); 12 | exports.METHODS = common.methods.slice().sort(); 13 | 14 | 15 | exports.OutgoingMessage = require('./_http_outgoing').OutgoingMessage; 16 | 17 | 18 | const server = require('./_http_server'); 19 | exports.ServerResponse = server.ServerResponse; 20 | exports.STATUS_CODES = server.STATUS_CODES; 21 | 22 | 23 | const agent = require('./_http_agent'); 24 | const Agent = exports.Agent = agent.Agent; 25 | exports.globalAgent = agent.globalAgent; 26 | 27 | const client = require('./_http_client'); 28 | const ClientRequest = exports.ClientRequest = client.ClientRequest; 29 | 30 | exports.request = function(options, cb) { 31 | return new ClientRequest(options, cb); 32 | }; 33 | 34 | exports.get = function(options, cb) { 35 | var req = exports.request(options, cb); 36 | req.end(); 37 | return req; 38 | }; 39 | 40 | exports._connectionListener = server._connectionListener; 41 | const Server = exports.Server = server.Server; 42 | 43 | exports.createServer = function(requestListener) { 44 | return new Server(requestListener); 45 | }; 46 | 47 | 48 | // Legacy Interface 49 | 50 | function Client(port, host) { 51 | if (!(this instanceof Client)) return new Client(port, host); 52 | EventEmitter.call(this); 53 | 54 | host = host || 'localhost'; 55 | port = port || 80; 56 | this.host = host; 57 | this.port = port; 58 | this.agent = new Agent({ host: host, port: port, maxSockets: 1 }); 59 | } 60 | util.inherits(Client, EventEmitter); 61 | Client.prototype.request = function(method, path, headers) { 62 | var self = this; 63 | var options = {}; 64 | options.host = self.host; 65 | options.port = self.port; 66 | if (method[0] === '/') { 67 | headers = path; 68 | path = method; 69 | method = 'GET'; 70 | } 71 | options.method = method; 72 | options.path = path; 73 | options.headers = headers; 74 | options.agent = self.agent; 75 | var c = new ClientRequest(options); 76 | c.on('error', function(e) { 77 | self.emit('error', e); 78 | }); 79 | // The old Client interface emitted 'end' on socket end. 80 | // This doesn't map to how we want things to operate in the future 81 | // but it will get removed when we remove this legacy interface. 82 | c.on('socket', function(s) { 83 | s.on('end', function() { 84 | if (self._decoder) { 85 | var ret = self._decoder.end(); 86 | if (ret) 87 | self.emit('data', ret); 88 | } 89 | self.emit('end'); 90 | }); 91 | }); 92 | return c; 93 | }; 94 | 95 | exports.Client = internalUtil.deprecate(Client, 'http.Client is deprecated.'); 96 | 97 | exports.createClient = internalUtil.deprecate(function(port, host) { 98 | return new Client(port, host); 99 | }, 'http.createClient is deprecated. Use http.request instead.'); 100 | -------------------------------------------------------------------------------- /_http_incoming.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const Stream = require('stream'); 5 | 6 | function readStart(socket) { 7 | if (socket && !socket._paused && socket.readable) 8 | socket.resume(); 9 | } 10 | exports.readStart = readStart; 11 | 12 | function readStop(socket) { 13 | if (socket) 14 | socket.pause(); 15 | } 16 | exports.readStop = readStop; 17 | 18 | 19 | /* Abstract base class for ServerRequest and ClientResponse. */ 20 | function IncomingMessage(socket) { 21 | Stream.Readable.call(this); 22 | 23 | // Set this to `true` so that stream.Readable won't attempt to read more 24 | // data on `IncomingMessage#push` (see `maybeReadMore` in 25 | // `_stream_readable.js`). This is important for proper tracking of 26 | // `IncomingMessage#_consuming` which is used to dump requests that users 27 | // haven't attempted to read. 28 | this._readableState.readingMore = true; 29 | 30 | this.socket = socket; 31 | this.connection = socket; 32 | 33 | this.httpVersionMajor = null; 34 | this.httpVersionMinor = null; 35 | this.httpVersion = null; 36 | this.complete = false; 37 | this.headers = {}; 38 | this.rawHeaders = []; 39 | this.trailers = {}; 40 | this.rawTrailers = []; 41 | 42 | this.readable = true; 43 | 44 | this.upgrade = null; 45 | 46 | // request (server) only 47 | this.url = ''; 48 | this.method = null; 49 | 50 | // response (client) only 51 | this.statusCode = null; 52 | this.statusMessage = null; 53 | this.client = socket; 54 | 55 | // flag for backwards compatibility grossness. 56 | this._consuming = false; 57 | 58 | // flag for when we decide that this message cannot possibly be 59 | // read by the user, so there's no point continuing to handle it. 60 | this._dumped = false; 61 | } 62 | util.inherits(IncomingMessage, Stream.Readable); 63 | 64 | 65 | exports.IncomingMessage = IncomingMessage; 66 | 67 | 68 | IncomingMessage.prototype.setTimeout = function(msecs, callback) { 69 | if (callback) 70 | this.on('timeout', callback); 71 | this.socket.setTimeout(msecs); 72 | return this; 73 | }; 74 | 75 | 76 | IncomingMessage.prototype.read = function(n) { 77 | if (!this._consuming) 78 | this._readableState.readingMore = false; 79 | this._consuming = true; 80 | this.read = Stream.Readable.prototype.read; 81 | return this.read(n); 82 | }; 83 | 84 | 85 | IncomingMessage.prototype._read = function(n) { 86 | // We actually do almost nothing here, because the parserOnBody 87 | // function fills up our internal buffer directly. However, we 88 | // do need to unpause the underlying socket so that it flows. 89 | if (this.socket.readable) 90 | readStart(this.socket); 91 | }; 92 | 93 | 94 | // It's possible that the socket will be destroyed, and removed from 95 | // any messages, before ever calling this. In that case, just skip 96 | // it, since something else is destroying this connection anyway. 97 | IncomingMessage.prototype.destroy = function(error) { 98 | if (this.socket) 99 | this.socket.destroy(error); 100 | }; 101 | 102 | 103 | IncomingMessage.prototype._addHeaderLines = function(headers, n) { 104 | if (headers && headers.length) { 105 | var raw, dest; 106 | if (this.complete) { 107 | raw = this.rawTrailers; 108 | dest = this.trailers; 109 | } else { 110 | raw = this.rawHeaders; 111 | dest = this.headers; 112 | } 113 | 114 | for (var i = 0; i < n; i += 2) { 115 | var k = headers[i]; 116 | var v = headers[i + 1]; 117 | raw.push(k); 118 | raw.push(v); 119 | this._addHeaderLine(k, v, dest); 120 | } 121 | } 122 | }; 123 | 124 | 125 | // Add the given (field, value) pair to the message 126 | // 127 | // Per RFC2616, section 4.2 it is acceptable to join multiple instances of the 128 | // same header with a ', ' if the header in question supports specification of 129 | // multiple values this way. If not, we declare the first instance the winner 130 | // and drop the second. Extended header fields (those beginning with 'x-') are 131 | // always joined. 132 | IncomingMessage.prototype._addHeaderLine = function(field, value, dest) { 133 | field = field.toLowerCase(); 134 | switch (field) { 135 | // Array headers: 136 | case 'set-cookie': 137 | if (dest[field] !== undefined) { 138 | dest[field].push(value); 139 | } else { 140 | dest[field] = [value]; 141 | } 142 | break; 143 | 144 | /* eslint-disable max-len */ 145 | // list is taken from: 146 | // https://mxr.mozilla.org/mozilla/source/netwerk/protocol/http/src/nsHttpHeaderArray.cpp 147 | /* eslint-enable max-len */ 148 | case 'content-type': 149 | case 'content-length': 150 | case 'user-agent': 151 | case 'referer': 152 | case 'host': 153 | case 'authorization': 154 | case 'proxy-authorization': 155 | case 'if-modified-since': 156 | case 'if-unmodified-since': 157 | case 'from': 158 | case 'location': 159 | case 'max-forwards': 160 | case 'retry-after': 161 | case 'etag': 162 | case 'last-modified': 163 | case 'server': 164 | case 'age': 165 | case 'expires': 166 | // drop duplicates 167 | if (dest[field] === undefined) 168 | dest[field] = value; 169 | break; 170 | 171 | default: 172 | // make comma-separated list 173 | if (typeof dest[field] === 'string') { 174 | dest[field] += ', ' + value; 175 | } else { 176 | dest[field] = value; 177 | } 178 | } 179 | }; 180 | 181 | 182 | // Call this instead of resume() if we want to just 183 | // dump all the data to /dev/null 184 | IncomingMessage.prototype._dump = function() { 185 | if (!this._dumped) { 186 | this._dumped = true; 187 | this.resume(); 188 | } 189 | }; 190 | -------------------------------------------------------------------------------- /_http_agent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const util = require('util'); 5 | const EventEmitter = require('events'); 6 | const debug = util.debuglog('http'); 7 | 8 | // New Agent code. 9 | 10 | // The largest departure from the previous implementation is that 11 | // an Agent instance holds connections for a variable number of host:ports. 12 | // Surprisingly, this is still API compatible as far as third parties are 13 | // concerned. The only code that really notices the difference is the 14 | // request object. 15 | 16 | // Another departure is that all code related to HTTP parsing is in 17 | // ClientRequest.onSocket(). The Agent is now *strictly* 18 | // concerned with managing a connection pool. 19 | 20 | function Agent(options) { 21 | if (!(this instanceof Agent)) 22 | return new Agent(options); 23 | 24 | EventEmitter.call(this); 25 | 26 | var self = this; 27 | 28 | self.defaultPort = 80; 29 | self.protocol = 'http:'; 30 | 31 | self.options = util._extend({}, options); 32 | 33 | // don't confuse net and make it think that we're connecting to a pipe 34 | self.options.path = null; 35 | self.requests = {}; 36 | self.sockets = {}; 37 | self.freeSockets = {}; 38 | self.keepAliveMsecs = self.options.keepAliveMsecs || 1000; 39 | self.keepAlive = self.options.keepAlive || false; 40 | self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets; 41 | self.maxFreeSockets = self.options.maxFreeSockets || 256; 42 | 43 | self.on('free', function(socket, options) { 44 | var name = self.getName(options); 45 | debug('agent.on(free)', name); 46 | 47 | if (socket.writable && 48 | self.requests[name] && self.requests[name].length) { 49 | self.requests[name].shift().onSocket(socket); 50 | if (self.requests[name].length === 0) { 51 | // don't leak 52 | delete self.requests[name]; 53 | } 54 | } else { 55 | // If there are no pending requests, then put it in 56 | // the freeSockets pool, but only if we're allowed to do so. 57 | var req = socket._httpMessage; 58 | if (req && 59 | req.shouldKeepAlive && 60 | socket.writable && 61 | self.keepAlive) { 62 | var freeSockets = self.freeSockets[name]; 63 | var freeLen = freeSockets ? freeSockets.length : 0; 64 | var count = freeLen; 65 | if (self.sockets[name]) 66 | count += self.sockets[name].length; 67 | 68 | if (count > self.maxSockets || freeLen >= self.maxFreeSockets) { 69 | socket.destroy(); 70 | } else { 71 | freeSockets = freeSockets || []; 72 | self.freeSockets[name] = freeSockets; 73 | socket.setKeepAlive(true, self.keepAliveMsecs); 74 | socket.unref(); 75 | socket._httpMessage = null; 76 | self.removeSocket(socket, options); 77 | freeSockets.push(socket); 78 | } 79 | } else { 80 | socket.destroy(); 81 | } 82 | } 83 | }); 84 | } 85 | 86 | util.inherits(Agent, EventEmitter); 87 | exports.Agent = Agent; 88 | 89 | Agent.defaultMaxSockets = Infinity; 90 | 91 | Agent.prototype.createConnection = net.createConnection; 92 | 93 | // Get the key for a given set of request options 94 | Agent.prototype.getName = function(options) { 95 | var name = options.host || 'localhost'; 96 | 97 | name += ':'; 98 | if (options.port) 99 | name += options.port; 100 | 101 | name += ':'; 102 | if (options.localAddress) 103 | name += options.localAddress; 104 | 105 | // Pacify parallel/test-http-agent-getname by only appending 106 | // the ':' when options.family is set. 107 | if (options.family === 4 || options.family === 6) 108 | name += ':' + options.family; 109 | 110 | return name; 111 | }; 112 | 113 | Agent.prototype.addRequest = function(req, options) { 114 | // Legacy API: addRequest(req, host, port, localAddress) 115 | if (typeof options === 'string') { 116 | options = { 117 | host: options, 118 | port: arguments[2], 119 | localAddress: arguments[3] 120 | }; 121 | } 122 | 123 | options = util._extend({}, options); 124 | options = util._extend(options, this.options); 125 | 126 | var name = this.getName(options); 127 | if (!this.sockets[name]) { 128 | this.sockets[name] = []; 129 | } 130 | 131 | var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0; 132 | var sockLen = freeLen + this.sockets[name].length; 133 | 134 | if (freeLen) { 135 | // we have a free socket, so use that. 136 | var socket = this.freeSockets[name].shift(); 137 | debug('have free socket'); 138 | 139 | // don't leak 140 | if (!this.freeSockets[name].length) 141 | delete this.freeSockets[name]; 142 | 143 | socket.ref(); 144 | req.onSocket(socket); 145 | this.sockets[name].push(socket); 146 | } else if (sockLen < this.maxSockets) { 147 | debug('call onSocket', sockLen, freeLen); 148 | // If we are under maxSockets create a new one. 149 | this.createSocket(req, options, function(err, newSocket) { 150 | if (err) { 151 | process.nextTick(function() { 152 | req.emit('error', err); 153 | }); 154 | return; 155 | } 156 | req.onSocket(newSocket); 157 | }); 158 | } else { 159 | debug('wait for socket'); 160 | // We are over limit so we'll add it to the queue. 161 | if (!this.requests[name]) { 162 | this.requests[name] = []; 163 | } 164 | this.requests[name].push(req); 165 | } 166 | }; 167 | 168 | Agent.prototype.createSocket = function(req, options, cb) { 169 | var self = this; 170 | options = util._extend({}, options); 171 | options = util._extend(options, self.options); 172 | 173 | if (!options.servername) { 174 | options.servername = options.host; 175 | const hostHeader = req.getHeader('host'); 176 | if (hostHeader) { 177 | options.servername = hostHeader.replace(/:.*$/, ''); 178 | } 179 | } 180 | 181 | var name = self.getName(options); 182 | options._agentKey = name; 183 | 184 | debug('createConnection', name, options); 185 | options.encoding = null; 186 | var called = false; 187 | const newSocket = self.createConnection(options, oncreate); 188 | if (newSocket) 189 | oncreate(null, newSocket); 190 | function oncreate(err, s) { 191 | if (called) 192 | return; 193 | called = true; 194 | if (err) 195 | return cb(err); 196 | if (!self.sockets[name]) { 197 | self.sockets[name] = []; 198 | } 199 | self.sockets[name].push(s); 200 | debug('sockets', name, self.sockets[name].length); 201 | 202 | function onFree() { 203 | self.emit('free', s, options); 204 | } 205 | s.on('free', onFree); 206 | 207 | function onClose(err) { 208 | debug('CLIENT socket onClose'); 209 | // This is the only place where sockets get removed from the Agent. 210 | // If you want to remove a socket from the pool, just close it. 211 | // All socket errors end in a close event anyway. 212 | self.removeSocket(s, options); 213 | } 214 | s.on('close', onClose); 215 | 216 | function onRemove() { 217 | // We need this function for cases like HTTP 'upgrade' 218 | // (defined by WebSockets) where we need to remove a socket from the 219 | // pool because it'll be locked up indefinitely 220 | debug('CLIENT socket onRemove'); 221 | self.removeSocket(s, options); 222 | s.removeListener('close', onClose); 223 | s.removeListener('free', onFree); 224 | s.removeListener('agentRemove', onRemove); 225 | } 226 | s.on('agentRemove', onRemove); 227 | cb(null, s); 228 | } 229 | }; 230 | 231 | Agent.prototype.removeSocket = function(s, options) { 232 | var name = this.getName(options); 233 | debug('removeSocket', name, 'writable:', s.writable); 234 | var sets = [this.sockets]; 235 | 236 | // If the socket was destroyed, remove it from the free buffers too. 237 | if (!s.writable) 238 | sets.push(this.freeSockets); 239 | 240 | for (var sk = 0; sk < sets.length; sk++) { 241 | var sockets = sets[sk]; 242 | 243 | if (sockets[name]) { 244 | var index = sockets[name].indexOf(s); 245 | if (index !== -1) { 246 | sockets[name].splice(index, 1); 247 | // Don't leak 248 | if (sockets[name].length === 0) 249 | delete sockets[name]; 250 | } 251 | } 252 | } 253 | 254 | if (this.requests[name] && this.requests[name].length) { 255 | debug('removeSocket, have a request, make a socket'); 256 | var req = this.requests[name][0]; 257 | // If we have pending requests and a socket gets closed make a new one 258 | this.createSocket(req, options, function(err, newSocket) { 259 | if (err) { 260 | process.nextTick(function() { 261 | req.emit('error', err); 262 | }); 263 | return; 264 | } 265 | newSocket.emit('free'); 266 | }); 267 | } 268 | }; 269 | 270 | Agent.prototype.destroy = function() { 271 | var sets = [this.freeSockets, this.sockets]; 272 | for (var s = 0; s < sets.length; s++) { 273 | var set = sets[s]; 274 | var keys = Object.keys(set); 275 | for (var v = 0; v < keys.length; v++) { 276 | var setName = set[keys[v]]; 277 | for (var n = 0; n < setName.length; n++) { 278 | setName[n].destroy(); 279 | } 280 | } 281 | } 282 | }; 283 | 284 | exports.globalAgent = new Agent(); 285 | -------------------------------------------------------------------------------- /_http_common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const binding = require('http-parser-js'); 4 | const methods = binding.methods; 5 | const HTTPParser = binding.HTTPParser; 6 | 7 | const FreeList = require('freelist').FreeList; 8 | const incoming = require('./_http_incoming'); 9 | const IncomingMessage = incoming.IncomingMessage; 10 | const readStart = incoming.readStart; 11 | const readStop = incoming.readStop; 12 | 13 | const debug = require('util').debuglog('http'); 14 | exports.debug = debug; 15 | 16 | exports.CRLF = '\r\n'; 17 | exports.chunkExpression = /chunk/i; 18 | exports.continueExpression = /100-continue/i; 19 | exports.methods = methods; 20 | 21 | const kOnHeaders = HTTPParser.kOnHeaders | 0; 22 | const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0; 23 | const kOnBody = HTTPParser.kOnBody | 0; 24 | const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0; 25 | const kOnExecute = HTTPParser.kOnExecute | 0; 26 | 27 | // Only called in the slow case where slow means 28 | // that the request headers were either fragmented 29 | // across multiple TCP packets or too large to be 30 | // processed in a single run. This method is also 31 | // called to process trailing HTTP headers. 32 | function parserOnHeaders(headers, url) { 33 | // Once we exceeded headers limit - stop collecting them 34 | if (this.maxHeaderPairs <= 0 || 35 | this._headers.length < this.maxHeaderPairs) { 36 | this._headers = this._headers.concat(headers); 37 | } 38 | this._url += url; 39 | } 40 | 41 | // `headers` and `url` are set only if .onHeaders() has not been called for 42 | // this request. 43 | // `url` is not set for response parsers but that's not applicable here since 44 | // all our parsers are request parsers. 45 | function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, 46 | url, statusCode, statusMessage, upgrade, 47 | shouldKeepAlive) { 48 | var parser = this; 49 | 50 | if (!headers) { 51 | headers = parser._headers; 52 | parser._headers = []; 53 | } 54 | 55 | if (!url) { 56 | url = parser._url; 57 | parser._url = ''; 58 | } 59 | 60 | parser.incoming = new IncomingMessage(parser.socket); 61 | parser.incoming.httpVersionMajor = versionMajor; 62 | parser.incoming.httpVersionMinor = versionMinor; 63 | parser.incoming.httpVersion = versionMajor + '.' + versionMinor; 64 | parser.incoming.url = url; 65 | 66 | var n = headers.length; 67 | 68 | // If parser.maxHeaderPairs <= 0 assume that there's no limit. 69 | if (parser.maxHeaderPairs > 0) 70 | n = Math.min(n, parser.maxHeaderPairs); 71 | 72 | parser.incoming._addHeaderLines(headers, n); 73 | 74 | if (typeof method === 'number') { 75 | // server only 76 | parser.incoming.method = methods[method]; 77 | } else { 78 | // client only 79 | parser.incoming.statusCode = statusCode; 80 | parser.incoming.statusMessage = statusMessage; 81 | } 82 | 83 | // The client made non-upgrade request, and server is just advertising 84 | // supported protocols. 85 | // 86 | // See RFC7230 Section 6.7 87 | // 88 | // NOTE: RegExp below matches `upgrade` in `Connection: abc, upgrade, def` 89 | // header. 90 | if (upgrade && 91 | parser.outgoing !== null && 92 | (parser.outgoing._headers.upgrade === undefined || 93 | !/(^|\W)upgrade(\W|$)/i.test(parser.outgoing._headers.connection))) { 94 | upgrade = false; 95 | } 96 | 97 | parser.incoming.upgrade = upgrade; 98 | 99 | var skipBody = 0; // response to HEAD or CONNECT 100 | 101 | if (!upgrade) { 102 | // For upgraded connections and CONNECT method request, we'll emit this 103 | // after parser.execute so that we can capture the first part of the new 104 | // protocol. 105 | skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive); 106 | } 107 | 108 | if (typeof skipBody !== 'number') 109 | return skipBody ? 1 : 0; 110 | else 111 | return skipBody; 112 | } 113 | 114 | // XXX This is a mess. 115 | // TODO: http.Parser should be a Writable emits request/response events. 116 | function parserOnBody(b, start, len) { 117 | var parser = this; 118 | var stream = parser.incoming; 119 | 120 | // if the stream has already been removed, then drop it. 121 | if (!stream) 122 | return; 123 | 124 | var socket = stream.socket; 125 | 126 | // pretend this was the result of a stream._read call. 127 | if (len > 0 && !stream._dumped) { 128 | var slice = b.slice(start, start + len); 129 | var ret = stream.push(slice); 130 | if (!ret) 131 | readStop(socket); 132 | } 133 | } 134 | 135 | function parserOnMessageComplete() { 136 | var parser = this; 137 | var stream = parser.incoming; 138 | 139 | if (stream) { 140 | stream.complete = true; 141 | // Emit any trailing headers. 142 | var headers = parser._headers; 143 | if (headers) { 144 | parser.incoming._addHeaderLines(headers, headers.length); 145 | parser._headers = []; 146 | parser._url = ''; 147 | } 148 | 149 | // For emit end event 150 | stream.push(null); 151 | } 152 | 153 | // force to read the next incoming message 154 | readStart(parser.socket); 155 | } 156 | 157 | 158 | var parsers = new FreeList('parsers', 1000, function() { 159 | var parser = new HTTPParser(HTTPParser.REQUEST); 160 | 161 | parser._headers = []; 162 | parser._url = ''; 163 | parser._consumed = false; 164 | 165 | parser.socket = null; 166 | parser.incoming = null; 167 | parser.outgoing = null; 168 | 169 | // Only called in the slow case where slow means 170 | // that the request headers were either fragmented 171 | // across multiple TCP packets or too large to be 172 | // processed in a single run. This method is also 173 | // called to process trailing HTTP headers. 174 | parser[kOnHeaders] = parserOnHeaders; 175 | parser[kOnHeadersComplete] = parserOnHeadersComplete; 176 | parser[kOnBody] = parserOnBody; 177 | parser[kOnMessageComplete] = parserOnMessageComplete; 178 | parser[kOnExecute] = null; 179 | 180 | return parser; 181 | }); 182 | exports.parsers = parsers; 183 | 184 | 185 | // Free the parser and also break any links that it 186 | // might have to any other things. 187 | // TODO: All parser data should be attached to a 188 | // single object, so that it can be easily cleaned 189 | // up by doing `parser.data = {}`, which should 190 | // be done in FreeList.free. `parsers.free(parser)` 191 | // should be all that is needed. 192 | function freeParser(parser, req, socket) { 193 | if (parser) { 194 | parser._headers = []; 195 | parser.onIncoming = null; 196 | if (parser._consumed) 197 | parser.unconsume(); 198 | parser._consumed = false; 199 | if (parser.socket) 200 | parser.socket.parser = null; 201 | parser.socket = null; 202 | parser.incoming = null; 203 | parser.outgoing = null; 204 | parser[kOnExecute] = null; 205 | if (parsers.free(parser) === false) 206 | parser.close(); 207 | parser = null; 208 | } 209 | if (req) { 210 | req.parser = null; 211 | } 212 | if (socket) { 213 | socket.parser = null; 214 | } 215 | } 216 | exports.freeParser = freeParser; 217 | 218 | 219 | function ondrain() { 220 | if (this._httpMessage) this._httpMessage.emit('drain'); 221 | } 222 | 223 | 224 | function httpSocketSetup(socket) { 225 | socket.removeListener('drain', ondrain); 226 | socket.on('drain', ondrain); 227 | } 228 | exports.httpSocketSetup = httpSocketSetup; 229 | 230 | /** 231 | * Verifies that the given val is a valid HTTP token 232 | * per the rules defined in RFC 7230 233 | * See https://tools.ietf.org/html/rfc7230#section-3.2.6 234 | * 235 | * Allowed characters in an HTTP token: 236 | * ^_`a-z 94-122 237 | * A-Z 65-90 238 | * - 45 239 | * 0-9 48-57 240 | * ! 33 241 | * #$%&' 35-39 242 | * *+ 42-43 243 | * . 46 244 | * | 124 245 | * ~ 126 246 | * 247 | * This implementation of checkIsHttpToken() loops over the string instead of 248 | * using a regular expression since the former is up to 180% faster with v8 4.9 249 | * depending on the string length (the shorter the string, the larger the 250 | * performance difference) 251 | * 252 | * Additionally, checkIsHttpToken() is currently designed to be inlinable by v8, 253 | * so take care when making changes to the implementation so that the source 254 | * code size does not exceed v8's default max_inlined_source_size setting. 255 | **/ 256 | function isValidTokenChar(ch) { 257 | if (ch >= 94 && ch <= 122) 258 | return true; 259 | if (ch >= 65 && ch <= 90) 260 | return true; 261 | if (ch === 45) 262 | return true; 263 | if (ch >= 48 && ch <= 57) 264 | return true; 265 | if (ch === 34 || ch === 40 || ch === 41 || ch === 44) 266 | return false; 267 | if (ch >= 33 && ch <= 46) 268 | return true; 269 | if (ch === 124 || ch === 126) 270 | return true; 271 | return false; 272 | } 273 | function checkIsHttpToken(val) { 274 | if (typeof val !== 'string' || val.length === 0) 275 | return false; 276 | if (!isValidTokenChar(val.charCodeAt(0))) 277 | return false; 278 | const len = val.length; 279 | if (len > 1) { 280 | if (!isValidTokenChar(val.charCodeAt(1))) 281 | return false; 282 | if (len > 2) { 283 | if (!isValidTokenChar(val.charCodeAt(2))) 284 | return false; 285 | if (len > 3) { 286 | if (!isValidTokenChar(val.charCodeAt(3))) 287 | return false; 288 | for (var i = 4; i < len; i++) { 289 | if (!isValidTokenChar(val.charCodeAt(i))) 290 | return false; 291 | } 292 | } 293 | } 294 | } 295 | return true; 296 | } 297 | exports._checkIsHttpToken = checkIsHttpToken; 298 | 299 | /** 300 | * True if val contains an invalid field-vchar 301 | * field-value = *( field-content / obs-fold ) 302 | * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] 303 | * field-vchar = VCHAR / obs-text 304 | * 305 | * checkInvalidHeaderChar() is currently designed to be inlinable by v8, 306 | * so take care when making changes to the implementation so that the source 307 | * code size does not exceed v8's default max_inlined_source_size setting. 308 | **/ 309 | function checkInvalidHeaderChar(val) { 310 | val += ''; 311 | if (val.length < 1) 312 | return false; 313 | var c = val.charCodeAt(0); 314 | if ((c <= 31 && c !== 9) || c > 255 || c === 127) 315 | return true; 316 | if (val.length < 2) 317 | return false; 318 | c = val.charCodeAt(1); 319 | if ((c <= 31 && c !== 9) || c > 255 || c === 127) 320 | return true; 321 | if (val.length < 3) 322 | return false; 323 | c = val.charCodeAt(2); 324 | if ((c <= 31 && c !== 9) || c > 255 || c === 127) 325 | return true; 326 | for (var i = 3; i < val.length; ++i) { 327 | c = val.charCodeAt(i); 328 | if ((c <= 31 && c !== 9) || c > 255 || c === 127) 329 | return true; 330 | } 331 | return false; 332 | } 333 | exports._checkInvalidHeaderChar = checkInvalidHeaderChar; 334 | -------------------------------------------------------------------------------- /_http_server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const net = require('net'); 5 | const HTTPParser = require('http-parser-js').HTTPParser; 6 | const assert = require('assert').ok; 7 | const common = require('./_http_common'); 8 | const parsers = common.parsers; 9 | const freeParser = common.freeParser; 10 | const debug = common.debug; 11 | const CRLF = common.CRLF; 12 | const continueExpression = common.continueExpression; 13 | const chunkExpression = common.chunkExpression; 14 | const httpSocketSetup = common.httpSocketSetup; 15 | const OutgoingMessage = require('./_http_outgoing').OutgoingMessage; 16 | 17 | const STATUS_CODES = exports.STATUS_CODES = { 18 | 100: 'Continue', 19 | 101: 'Switching Protocols', 20 | 102: 'Processing', // RFC 2518, obsoleted by RFC 4918 21 | 200: 'OK', 22 | 201: 'Created', 23 | 202: 'Accepted', 24 | 203: 'Non-Authoritative Information', 25 | 204: 'No Content', 26 | 205: 'Reset Content', 27 | 206: 'Partial Content', 28 | 207: 'Multi-Status', // RFC 4918 29 | 208: 'Already Reported', 30 | 226: 'IM Used', 31 | 300: 'Multiple Choices', 32 | 301: 'Moved Permanently', 33 | 302: 'Found', 34 | 303: 'See Other', 35 | 304: 'Not Modified', 36 | 305: 'Use Proxy', 37 | 307: 'Temporary Redirect', 38 | 308: 'Permanent Redirect', // RFC 7238 39 | 400: 'Bad Request', 40 | 401: 'Unauthorized', 41 | 402: 'Payment Required', 42 | 403: 'Forbidden', 43 | 404: 'Not Found', 44 | 405: 'Method Not Allowed', 45 | 406: 'Not Acceptable', 46 | 407: 'Proxy Authentication Required', 47 | 408: 'Request Timeout', 48 | 409: 'Conflict', 49 | 410: 'Gone', 50 | 411: 'Length Required', 51 | 412: 'Precondition Failed', 52 | 413: 'Payload Too Large', 53 | 414: 'URI Too Long', 54 | 415: 'Unsupported Media Type', 55 | 416: 'Range Not Satisfiable', 56 | 417: 'Expectation Failed', 57 | 418: 'I\'m a teapot', // RFC 2324 58 | 421: 'Misdirected Request', 59 | 422: 'Unprocessable Entity', // RFC 4918 60 | 423: 'Locked', // RFC 4918 61 | 424: 'Failed Dependency', // RFC 4918 62 | 425: 'Unordered Collection', // RFC 4918 63 | 426: 'Upgrade Required', // RFC 2817 64 | 428: 'Precondition Required', // RFC 6585 65 | 429: 'Too Many Requests', // RFC 6585 66 | 431: 'Request Header Fields Too Large', // RFC 6585 67 | 451: 'Unavailable For Legal Reasons', 68 | 500: 'Internal Server Error', 69 | 501: 'Not Implemented', 70 | 502: 'Bad Gateway', 71 | 503: 'Service Unavailable', 72 | 504: 'Gateway Timeout', 73 | 505: 'HTTP Version Not Supported', 74 | 506: 'Variant Also Negotiates', // RFC 2295 75 | 507: 'Insufficient Storage', // RFC 4918 76 | 508: 'Loop Detected', 77 | 509: 'Bandwidth Limit Exceeded', 78 | 510: 'Not Extended', // RFC 2774 79 | 511: 'Network Authentication Required' // RFC 6585 80 | }; 81 | 82 | const kOnExecute = HTTPParser.kOnExecute | 0; 83 | 84 | 85 | function ServerResponse(req) { 86 | OutgoingMessage.call(this); 87 | 88 | if (req.method === 'HEAD') this._hasBody = false; 89 | 90 | this.sendDate = true; 91 | 92 | if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) { 93 | this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te); 94 | this.shouldKeepAlive = false; 95 | } 96 | } 97 | util.inherits(ServerResponse, OutgoingMessage); 98 | 99 | ServerResponse.prototype._finish = function() { 100 | // DTRACE_HTTP_SERVER_RESPONSE(this.connection); 101 | // LTTNG_HTTP_SERVER_RESPONSE(this.connection); 102 | // COUNTER_HTTP_SERVER_RESPONSE(); 103 | OutgoingMessage.prototype._finish.call(this); 104 | }; 105 | 106 | 107 | exports.ServerResponse = ServerResponse; 108 | 109 | ServerResponse.prototype.statusCode = 200; 110 | ServerResponse.prototype.statusMessage = undefined; 111 | 112 | function onServerResponseClose() { 113 | // EventEmitter.emit makes a copy of the 'close' listeners array before 114 | // calling the listeners. detachSocket() unregisters onServerResponseClose 115 | // but if detachSocket() is called, directly or indirectly, by a 'close' 116 | // listener, onServerResponseClose is still in that copy of the listeners 117 | // array. That is, in the example below, b still gets called even though 118 | // it's been removed by a: 119 | // 120 | // var EventEmitter = require('events'); 121 | // var obj = new EventEmitter(); 122 | // obj.on('event', a); 123 | // obj.on('event', b); 124 | // function a() { obj.removeListener('event', b) } 125 | // function b() { throw "BAM!" } 126 | // obj.emit('event'); // throws 127 | // 128 | // Ergo, we need to deal with stale 'close' events and handle the case 129 | // where the ServerResponse object has already been deconstructed. 130 | // Fortunately, that requires only a single if check. :-) 131 | if (this._httpMessage) this._httpMessage.emit('close'); 132 | } 133 | 134 | ServerResponse.prototype.assignSocket = function(socket) { 135 | assert(!socket._httpMessage); 136 | socket._httpMessage = this; 137 | socket.on('close', onServerResponseClose); 138 | this.socket = socket; 139 | this.connection = socket; 140 | this.emit('socket', socket); 141 | this._flush(); 142 | }; 143 | 144 | ServerResponse.prototype.detachSocket = function(socket) { 145 | assert(socket._httpMessage === this); 146 | socket.removeListener('close', onServerResponseClose); 147 | socket._httpMessage = null; 148 | this.socket = this.connection = null; 149 | }; 150 | 151 | ServerResponse.prototype.writeContinue = function(cb) { 152 | this._writeRaw('HTTP/1.1 100 Continue' + CRLF + CRLF, 'ascii', cb); 153 | this._sent100 = true; 154 | }; 155 | 156 | ServerResponse.prototype._implicitHeader = function() { 157 | this.writeHead(this.statusCode); 158 | }; 159 | 160 | ServerResponse.prototype.writeHead = function(statusCode, reason, obj) { 161 | var headers; 162 | 163 | if (typeof reason === 'string') { 164 | // writeHead(statusCode, reasonPhrase[, headers]) 165 | this.statusMessage = reason; 166 | } else { 167 | // writeHead(statusCode[, headers]) 168 | this.statusMessage = 169 | this.statusMessage || STATUS_CODES[statusCode] || 'unknown'; 170 | obj = reason; 171 | } 172 | this.statusCode = statusCode; 173 | 174 | if (this._headers) { 175 | // Slow-case: when progressive API and header fields are passed. 176 | if (obj) { 177 | var keys = Object.keys(obj); 178 | for (var i = 0; i < keys.length; i++) { 179 | var k = keys[i]; 180 | if (k) this.setHeader(k, obj[k]); 181 | } 182 | } 183 | // only progressive api is used 184 | headers = this._renderHeaders(); 185 | } else { 186 | // only writeHead() called 187 | headers = obj; 188 | } 189 | 190 | statusCode |= 0; 191 | if (statusCode < 100 || statusCode > 999) 192 | throw new RangeError(`Invalid status code: ${statusCode}`); 193 | 194 | var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + 195 | this.statusMessage + CRLF; 196 | 197 | if (statusCode === 204 || statusCode === 304 || 198 | (100 <= statusCode && statusCode <= 199)) { 199 | // RFC 2616, 10.2.5: 200 | // The 204 response MUST NOT include a message-body, and thus is always 201 | // terminated by the first empty line after the header fields. 202 | // RFC 2616, 10.3.5: 203 | // The 304 response MUST NOT contain a message-body, and thus is always 204 | // terminated by the first empty line after the header fields. 205 | // RFC 2616, 10.1 Informational 1xx: 206 | // This class of status code indicates a provisional response, 207 | // consisting only of the Status-Line and optional headers, and is 208 | // terminated by an empty line. 209 | this._hasBody = false; 210 | } 211 | 212 | // don't keep alive connections where the client expects 100 Continue 213 | // but we sent a final status; they may put extra bytes on the wire. 214 | if (this._expect_continue && !this._sent100) { 215 | this.shouldKeepAlive = false; 216 | } 217 | 218 | this._storeHeader(statusLine, headers); 219 | }; 220 | 221 | ServerResponse.prototype.writeHeader = function() { 222 | this.writeHead.apply(this, arguments); 223 | }; 224 | 225 | 226 | function Server(requestListener) { 227 | if (!(this instanceof Server)) return new Server(requestListener); 228 | net.Server.call(this, { allowHalfOpen: true }); 229 | 230 | if (requestListener) { 231 | this.addListener('request', requestListener); 232 | } 233 | 234 | /* eslint-disable max-len */ 235 | // Similar option to this. Too lazy to write my own docs. 236 | // http://www.squid-cache.org/Doc/config/half_closed_clients/ 237 | // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F 238 | /* eslint-enable max-len */ 239 | this.httpAllowHalfOpen = false; 240 | 241 | this.addListener('connection', connectionListener); 242 | 243 | this.timeout = 2 * 60 * 1000; 244 | 245 | this._pendingResponseData = 0; 246 | } 247 | util.inherits(Server, net.Server); 248 | 249 | 250 | Server.prototype.setTimeout = function(msecs, callback) { 251 | this.timeout = msecs; 252 | if (callback) 253 | this.on('timeout', callback); 254 | return this; 255 | }; 256 | 257 | 258 | exports.Server = Server; 259 | 260 | 261 | function connectionListener(socket) { 262 | var self = this; 263 | var outgoing = []; 264 | var incoming = []; 265 | var outgoingData = 0; 266 | 267 | function updateOutgoingData(delta) { 268 | // `outgoingData` is an approximate amount of bytes queued through all 269 | // inactive responses. If more data than the high watermark is queued - we 270 | // need to pause TCP socket/HTTP parser, and wait until the data will be 271 | // sent to the client. 272 | outgoingData += delta; 273 | if (socket._paused && outgoingData < socket._writableState.highWaterMark) 274 | return socketOnDrain(); 275 | } 276 | 277 | function abortIncoming() { 278 | while (incoming.length) { 279 | var req = incoming.shift(); 280 | req.emit('aborted'); 281 | req.emit('close'); 282 | } 283 | // abort socket._httpMessage ? 284 | } 285 | 286 | function serverSocketCloseListener() { 287 | debug('server socket close'); 288 | // mark this parser as reusable 289 | if (this.parser) { 290 | freeParser(this.parser, null, this); 291 | } 292 | 293 | abortIncoming(); 294 | } 295 | 296 | debug('SERVER new http connection'); 297 | 298 | httpSocketSetup(socket); 299 | 300 | // If the user has added a listener to the server, 301 | // request, or response, then it's their responsibility. 302 | // otherwise, destroy on timeout by default 303 | if (self.timeout) 304 | socket.setTimeout(self.timeout); 305 | socket.on('timeout', function() { 306 | var req = socket.parser && socket.parser.incoming; 307 | var reqTimeout = req && !req.complete && req.emit('timeout', socket); 308 | var res = socket._httpMessage; 309 | var resTimeout = res && res.emit('timeout', socket); 310 | var serverTimeout = self.emit('timeout', socket); 311 | 312 | if (!reqTimeout && !resTimeout && !serverTimeout) 313 | socket.destroy(); 314 | }); 315 | 316 | var parser = parsers.alloc(); 317 | parser.reinitialize(HTTPParser.REQUEST); 318 | parser.socket = socket; 319 | socket.parser = parser; 320 | parser.incoming = null; 321 | 322 | // Propagate headers limit from server instance to parser 323 | if (typeof this.maxHeadersCount === 'number') { 324 | parser.maxHeaderPairs = this.maxHeadersCount << 1; 325 | } else { 326 | // Set default value because parser may be reused from FreeList 327 | parser.maxHeaderPairs = 2000; 328 | } 329 | 330 | socket.addListener('error', socketOnError); 331 | socket.addListener('close', serverSocketCloseListener); 332 | parser.onIncoming = parserOnIncoming; 333 | socket.on('end', socketOnEnd); 334 | socket.on('data', socketOnData); 335 | 336 | // We are consuming socket, so it won't get any actual data 337 | socket.on('resume', onSocketResume); 338 | socket.on('pause', onSocketPause); 339 | 340 | socket.on('drain', socketOnDrain); 341 | 342 | // Override on to unconsume on `data`, `readable` listeners 343 | socket.on = socketOnWrap; 344 | 345 | var external = socket._handle && socket._handle._externalStream; 346 | if (external) { 347 | parser._consumed = true; 348 | parser.consume(external); 349 | } 350 | external = null; 351 | parser[kOnExecute] = onParserExecute; 352 | 353 | // TODO(isaacs): Move all these functions out of here 354 | function socketOnError(e) { 355 | // Ignore further errors 356 | this.removeListener('error', socketOnError); 357 | this.on('error', () => {}); 358 | 359 | if (!self.emit('clientError', e, this)) 360 | this.destroy(e); 361 | } 362 | 363 | function socketOnData(d) { 364 | assert(!socket._paused); 365 | debug('SERVER socketOnData %d', d.length); 366 | var ret = parser.execute(d); 367 | 368 | onParserExecuteCommon(ret, d); 369 | } 370 | 371 | function onParserExecute(ret, d) { 372 | socket._unrefTimer(); 373 | debug('SERVER socketOnParserExecute %d', ret); 374 | onParserExecuteCommon(ret, undefined); 375 | } 376 | 377 | function onParserExecuteCommon(ret, d) { 378 | if (ret instanceof Error) { 379 | debug('parse error'); 380 | socketOnError.call(socket, ret); 381 | } else if (parser.incoming && parser.incoming.upgrade) { 382 | // Upgrade or CONNECT 383 | var bytesParsed = ret; 384 | var req = parser.incoming; 385 | debug('SERVER upgrade or connect', req.method); 386 | 387 | if (!d) 388 | d = parser.getCurrentBuffer(); 389 | 390 | socket.removeListener('data', socketOnData); 391 | socket.removeListener('end', socketOnEnd); 392 | socket.removeListener('close', serverSocketCloseListener); 393 | unconsume(parser, socket); 394 | parser.finish(); 395 | freeParser(parser, req, null); 396 | parser = null; 397 | 398 | var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; 399 | if (self.listenerCount(eventName) > 0) { 400 | debug('SERVER have listener for %s', eventName); 401 | var bodyHead = d.slice(bytesParsed, d.length); 402 | 403 | // TODO(isaacs): Need a way to reset a stream to fresh state 404 | // IE, not flowing, and not explicitly paused. 405 | socket._readableState.flowing = null; 406 | self.emit(eventName, req, socket, bodyHead); 407 | } else { 408 | // Got upgrade header or CONNECT method, but have no handler. 409 | socket.destroy(); 410 | } 411 | } 412 | 413 | if (socket._paused && socket.parser) { 414 | // onIncoming paused the socket, we should pause the parser as well 415 | debug('pause parser'); 416 | socket.parser.pause(); 417 | } 418 | } 419 | 420 | function socketOnEnd() { 421 | var socket = this; 422 | var ret = parser.finish(); 423 | 424 | if (ret instanceof Error) { 425 | debug('parse error'); 426 | socketOnError.call(socket, ret); 427 | return; 428 | } 429 | 430 | if (!self.httpAllowHalfOpen) { 431 | abortIncoming(); 432 | if (socket.writable) socket.end(); 433 | } else if (outgoing.length) { 434 | outgoing[outgoing.length - 1]._last = true; 435 | } else if (socket._httpMessage) { 436 | socket._httpMessage._last = true; 437 | } else { 438 | if (socket.writable) socket.end(); 439 | } 440 | } 441 | 442 | 443 | // The following callback is issued after the headers have been read on a 444 | // new message. In this callback we setup the response object and pass it 445 | // to the user. 446 | 447 | socket._paused = false; 448 | function socketOnDrain() { 449 | var needPause = outgoingData > socket._writableState.highWaterMark; 450 | 451 | // If we previously paused, then start reading again. 452 | if (socket._paused && !needPause) { 453 | socket._paused = false; 454 | if (socket.parser) 455 | socket.parser.resume(); 456 | socket.resume(); 457 | } 458 | } 459 | 460 | function parserOnIncoming(req, shouldKeepAlive) { 461 | incoming.push(req); 462 | 463 | // If the writable end isn't consuming, then stop reading 464 | // so that we don't become overwhelmed by a flood of 465 | // pipelined requests that may never be resolved. 466 | if (!socket._paused) { 467 | var needPause = socket._writableState.needDrain || 468 | outgoingData >= socket._writableState.highWaterMark; 469 | if (needPause) { 470 | socket._paused = true; 471 | // We also need to pause the parser, but don't do that until after 472 | // the call to execute, because we may still be processing the last 473 | // chunk. 474 | socket.pause(); 475 | } 476 | } 477 | 478 | var res = new ServerResponse(req); 479 | res._onPendingData = updateOutgoingData; 480 | 481 | res.shouldKeepAlive = shouldKeepAlive; 482 | // DTRACE_HTTP_SERVER_REQUEST(req, socket); 483 | // LTTNG_HTTP_SERVER_REQUEST(req, socket); 484 | // COUNTER_HTTP_SERVER_REQUEST(); 485 | 486 | if (socket._httpMessage) { 487 | // There are already pending outgoing res, append. 488 | outgoing.push(res); 489 | } else { 490 | res.assignSocket(socket); 491 | } 492 | 493 | // When we're finished writing the response, check if this is the last 494 | // response, if so destroy the socket. 495 | res.on('finish', resOnFinish); 496 | function resOnFinish() { 497 | // Usually the first incoming element should be our request. it may 498 | // be that in the case abortIncoming() was called that the incoming 499 | // array will be empty. 500 | assert(incoming.length === 0 || incoming[0] === req); 501 | 502 | incoming.shift(); 503 | 504 | // if the user never called req.read(), and didn't pipe() or 505 | // .resume() or .on('data'), then we call req._dump() so that the 506 | // bytes will be pulled off the wire. 507 | if (!req._consuming && !req._readableState.resumeScheduled) 508 | req._dump(); 509 | 510 | res.detachSocket(socket); 511 | 512 | if (res._last) { 513 | socket.destroySoon(); 514 | } else { 515 | // start sending the next message 516 | var m = outgoing.shift(); 517 | if (m) { 518 | m.assignSocket(socket); 519 | } 520 | } 521 | } 522 | 523 | if (req.headers.expect !== undefined && 524 | (req.httpVersionMajor == 1 && req.httpVersionMinor == 1)) { 525 | if (continueExpression.test(req.headers.expect)) { 526 | res._expect_continue = true; 527 | 528 | if (self.listenerCount('checkContinue') > 0) { 529 | self.emit('checkContinue', req, res); 530 | } else { 531 | res.writeContinue(); 532 | self.emit('request', req, res); 533 | } 534 | } else { 535 | if (self.listenerCount('checkExpectation') > 0) { 536 | self.emit('checkExpectation', req, res); 537 | } else { 538 | res.writeHead(417); 539 | res.end(); 540 | } 541 | } 542 | } else { 543 | self.emit('request', req, res); 544 | } 545 | return false; // Not a HEAD response. (Not even a response!) 546 | } 547 | } 548 | exports._connectionListener = connectionListener; 549 | 550 | function onSocketResume() { 551 | // It may seem that the socket is resumed, but this is an enemy's trick to 552 | // deceive us! `resume` is emitted asynchronously, and may be called from 553 | // `incoming.readStart()`. Stop the socket again here, just to preserve the 554 | // state. 555 | // 556 | // We don't care about stream semantics for the consumed socket anyway. 557 | if (this._paused) { 558 | this.pause(); 559 | return; 560 | } 561 | 562 | if (this._handle && !this._handle.reading) { 563 | this._handle.reading = true; 564 | this._handle.readStart(); 565 | } 566 | } 567 | 568 | function onSocketPause() { 569 | if (this._handle && this._handle.reading) { 570 | this._handle.reading = false; 571 | this._handle.readStop(); 572 | } 573 | } 574 | 575 | function unconsume(parser, socket) { 576 | if (socket._handle) { 577 | if (parser._consumed) 578 | parser.unconsume(socket._handle._externalStream); 579 | parser._consumed = false; 580 | socket.removeListener('pause', onSocketPause); 581 | socket.removeListener('resume', onSocketResume); 582 | } 583 | } 584 | 585 | function socketOnWrap(ev, fn) { 586 | var res = net.Socket.prototype.on.call(this, ev, fn); 587 | if (!this.parser) { 588 | this.on = net.Socket.prototype.on; 589 | return res; 590 | } 591 | 592 | if (ev === 'data' || ev === 'readable') 593 | unconsume(this.parser, this); 594 | 595 | return res; 596 | } 597 | -------------------------------------------------------------------------------- /_http_client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const net = require('net'); 5 | const url = require('url'); 6 | const HTTPParser = require('http-parser-js').HTTPParser; 7 | const assert = require('assert').ok; 8 | const common = require('./_http_common'); 9 | const httpSocketSetup = common.httpSocketSetup; 10 | const parsers = common.parsers; 11 | const freeParser = common.freeParser; 12 | const debug = common.debug; 13 | const OutgoingMessage = require('./_http_outgoing').OutgoingMessage; 14 | const Agent = require('./_http_agent'); 15 | const Buffer = require('buffer').Buffer; 16 | 17 | 18 | function ClientRequest(options, cb) { 19 | var self = this; 20 | OutgoingMessage.call(self); 21 | 22 | if (typeof options === 'string') { 23 | options = url.parse(options); 24 | if (!options.hostname) { 25 | throw new Error('Unable to determine the domain name'); 26 | } 27 | } else { 28 | options = util._extend({}, options); 29 | } 30 | 31 | var agent = options.agent; 32 | var defaultAgent = options._defaultAgent || Agent.globalAgent; 33 | if (agent === false) { 34 | agent = new defaultAgent.constructor(); 35 | } else if ((agent === null || agent === undefined) && 36 | typeof options.createConnection !== 'function') { 37 | agent = defaultAgent; 38 | } 39 | self.agent = agent; 40 | 41 | var protocol = options.protocol || defaultAgent.protocol; 42 | var expectedProtocol = defaultAgent.protocol; 43 | if (self.agent && self.agent.protocol) 44 | expectedProtocol = self.agent.protocol; 45 | 46 | if (options.path && / /.test(options.path)) { 47 | // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/ 48 | // with an additional rule for ignoring percentage-escaped characters 49 | // but that's a) hard to capture in a regular expression that performs 50 | // well, and b) possibly too restrictive for real-world usage. That's 51 | // why it only scans for spaces because those are guaranteed to create 52 | // an invalid request. 53 | throw new TypeError('Request path contains unescaped characters'); 54 | } else if (protocol !== expectedProtocol) { 55 | throw new Error('Protocol "' + protocol + '" not supported. ' + 56 | 'Expected "' + expectedProtocol + '"'); 57 | } 58 | 59 | const defaultPort = options.defaultPort || 60 | self.agent && self.agent.defaultPort; 61 | 62 | var port = options.port = options.port || defaultPort || 80; 63 | var host = options.host = options.hostname || options.host || 'localhost'; 64 | 65 | if (options.setHost === undefined) { 66 | var setHost = true; 67 | } 68 | 69 | self.socketPath = options.socketPath; 70 | 71 | var method = self.method = (options.method || 'GET').toUpperCase(); 72 | if (!common._checkIsHttpToken(method)) { 73 | throw new TypeError('Method must be a valid HTTP token'); 74 | } 75 | self.path = options.path || '/'; 76 | if (cb) { 77 | self.once('response', cb); 78 | } 79 | 80 | if (!Array.isArray(options.headers)) { 81 | if (options.headers) { 82 | var keys = Object.keys(options.headers); 83 | for (var i = 0, l = keys.length; i < l; i++) { 84 | var key = keys[i]; 85 | self.setHeader(key, options.headers[key]); 86 | } 87 | } 88 | if (host && !this.getHeader('host') && setHost) { 89 | var hostHeader = host; 90 | var posColon = -1; 91 | 92 | // For the Host header, ensure that IPv6 addresses are enclosed 93 | // in square brackets, as defined by URI formatting 94 | // https://tools.ietf.org/html/rfc3986#section-3.2.2 95 | if (-1 !== (posColon = hostHeader.indexOf(':')) && 96 | -1 !== (posColon = hostHeader.indexOf(':', posColon)) && 97 | '[' !== hostHeader[0]) { 98 | hostHeader = `[${hostHeader}]`; 99 | } 100 | 101 | if (port && +port !== defaultPort) { 102 | hostHeader += ':' + port; 103 | } 104 | this.setHeader('Host', hostHeader); 105 | } 106 | } 107 | 108 | if (options.auth && !this.getHeader('Authorization')) { 109 | //basic auth 110 | this.setHeader('Authorization', 'Basic ' + 111 | Buffer.from(options.auth).toString('base64')); 112 | } 113 | 114 | if (method === 'GET' || 115 | method === 'HEAD' || 116 | method === 'DELETE' || 117 | method === 'OPTIONS' || 118 | method === 'CONNECT') { 119 | self.useChunkedEncodingByDefault = false; 120 | } else { 121 | self.useChunkedEncodingByDefault = true; 122 | } 123 | 124 | if (Array.isArray(options.headers)) { 125 | self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n', 126 | options.headers); 127 | } else if (self.getHeader('expect')) { 128 | self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n', 129 | self._renderHeaders()); 130 | } 131 | 132 | var called = false; 133 | if (self.socketPath) { 134 | self._last = true; 135 | self.shouldKeepAlive = false; 136 | const optionsPath = { 137 | path: self.socketPath 138 | }; 139 | const newSocket = self.agent.createConnection(optionsPath, oncreate); 140 | if (newSocket && !called) { 141 | called = true; 142 | self.onSocket(newSocket); 143 | } else { 144 | return; 145 | } 146 | } else if (self.agent) { 147 | // If there is an agent we should default to Connection:keep-alive, 148 | // but only if the Agent will actually reuse the connection! 149 | // If it's not a keepAlive agent, and the maxSockets==Infinity, then 150 | // there's never a case where this socket will actually be reused 151 | if (!self.agent.keepAlive && !Number.isFinite(self.agent.maxSockets)) { 152 | self._last = true; 153 | self.shouldKeepAlive = false; 154 | } else { 155 | self._last = false; 156 | self.shouldKeepAlive = true; 157 | } 158 | self.agent.addRequest(self, options); 159 | } else { 160 | // No agent, default to Connection:close. 161 | self._last = true; 162 | self.shouldKeepAlive = false; 163 | if (typeof options.createConnection === 'function') { 164 | const newSocket = options.createConnection(options, oncreate); 165 | if (newSocket && !called) { 166 | called = true; 167 | self.onSocket(newSocket); 168 | } else { 169 | return; 170 | } 171 | } else { 172 | debug('CLIENT use net.createConnection', options); 173 | self.onSocket(net.createConnection(options)); 174 | } 175 | } 176 | 177 | function oncreate(err, socket) { 178 | if (called) 179 | return; 180 | called = true; 181 | if (err) { 182 | process.nextTick(function() { 183 | self.emit('error', err); 184 | }); 185 | return; 186 | } 187 | self.onSocket(socket); 188 | self._deferToConnect(null, null, function() { 189 | self._flush(); 190 | self = null; 191 | }); 192 | } 193 | 194 | self._deferToConnect(null, null, function() { 195 | self._flush(); 196 | self = null; 197 | }); 198 | 199 | this._ended = false; 200 | } 201 | 202 | util.inherits(ClientRequest, OutgoingMessage); 203 | 204 | exports.ClientRequest = ClientRequest; 205 | 206 | ClientRequest.prototype.aborted = undefined; 207 | 208 | ClientRequest.prototype._finish = function() { 209 | // DTRACE_HTTP_CLIENT_REQUEST(this, this.connection); 210 | // LTTNG_HTTP_CLIENT_REQUEST(this, this.connection); 211 | // COUNTER_HTTP_CLIENT_REQUEST(); 212 | OutgoingMessage.prototype._finish.call(this); 213 | }; 214 | 215 | ClientRequest.prototype._implicitHeader = function() { 216 | this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n', 217 | this._renderHeaders()); 218 | }; 219 | 220 | ClientRequest.prototype.abort = function() { 221 | if (this.aborted === undefined) { 222 | process.nextTick(emitAbortNT, this); 223 | } 224 | // Mark as aborting so we can avoid sending queued request data 225 | // This is used as a truthy flag elsewhere. The use of Date.now is for 226 | // debugging purposes only. 227 | this.aborted = Date.now(); 228 | 229 | // If we're aborting, we don't care about any more response data. 230 | if (this.res) 231 | this.res._dump(); 232 | else 233 | this.once('response', function(res) { 234 | res._dump(); 235 | }); 236 | 237 | // In the event that we don't have a socket, we will pop out of 238 | // the request queue through handling in onSocket. 239 | if (this.socket) { 240 | // in-progress 241 | this.socket.destroy(); 242 | } 243 | }; 244 | 245 | 246 | function emitAbortNT(self) { 247 | self.emit('abort'); 248 | } 249 | 250 | 251 | function createHangUpError() { 252 | var error = new Error('socket hang up'); 253 | error.code = 'ECONNRESET'; 254 | return error; 255 | } 256 | 257 | 258 | function socketCloseListener() { 259 | var socket = this; 260 | var req = socket._httpMessage; 261 | debug('HTTP socket close'); 262 | 263 | // Pull through final chunk, if anything is buffered. 264 | // the ondata function will handle it properly, and this 265 | // is a no-op if no final chunk remains. 266 | socket.read(); 267 | 268 | // NOTE: It's important to get parser here, because it could be freed by 269 | // the `socketOnData`. 270 | var parser = socket.parser; 271 | req.emit('close'); 272 | if (req.res && req.res.readable) { 273 | // Socket closed before we emitted 'end' below. 274 | req.res.emit('aborted'); 275 | var res = req.res; 276 | res.on('end', function() { 277 | res.emit('close'); 278 | }); 279 | res.push(null); 280 | } else if (!req.res && !req.socket._hadError) { 281 | // This socket error fired before we started to 282 | // receive a response. The error needs to 283 | // fire on the request. 284 | req.emit('error', createHangUpError()); 285 | req.socket._hadError = true; 286 | } 287 | 288 | // Too bad. That output wasn't getting written. 289 | // This is pretty terrible that it doesn't raise an error. 290 | // Fixed better in v0.10 291 | if (req.output) 292 | req.output.length = 0; 293 | if (req.outputEncodings) 294 | req.outputEncodings.length = 0; 295 | 296 | if (parser) { 297 | parser.finish(); 298 | freeParser(parser, req, socket); 299 | } 300 | } 301 | 302 | function socketErrorListener(err) { 303 | var socket = this; 304 | var req = socket._httpMessage; 305 | debug('SOCKET ERROR:', err.message, err.stack); 306 | 307 | if (req) { 308 | req.emit('error', err); 309 | // For Safety. Some additional errors might fire later on 310 | // and we need to make sure we don't double-fire the error event. 311 | req.socket._hadError = true; 312 | } 313 | 314 | // Handle any pending data 315 | socket.read(); 316 | 317 | var parser = socket.parser; 318 | if (parser) { 319 | parser.finish(); 320 | freeParser(parser, req, socket); 321 | } 322 | 323 | // Ensure that no further data will come out of the socket 324 | socket.removeListener('data', socketOnData); 325 | socket.removeListener('end', socketOnEnd); 326 | socket.destroy(); 327 | } 328 | 329 | function freeSocketErrorListener(err) { 330 | var socket = this; 331 | debug('SOCKET ERROR on FREE socket:', err.message, err.stack); 332 | socket.destroy(); 333 | socket.emit('agentRemove'); 334 | } 335 | 336 | function socketOnEnd() { 337 | var socket = this; 338 | var req = this._httpMessage; 339 | var parser = this.parser; 340 | 341 | if (!req.res && !req.socket._hadError) { 342 | // If we don't have a response then we know that the socket 343 | // ended prematurely and we need to emit an error on the request. 344 | req.emit('error', createHangUpError()); 345 | req.socket._hadError = true; 346 | } 347 | if (parser) { 348 | parser.finish(); 349 | freeParser(parser, req, socket); 350 | } 351 | socket.destroy(); 352 | } 353 | 354 | function socketOnData(d) { 355 | var socket = this; 356 | var req = this._httpMessage; 357 | var parser = this.parser; 358 | 359 | assert(parser && parser.socket === socket); 360 | 361 | var ret = parser.execute(d); 362 | if (ret instanceof Error) { 363 | debug('parse error'); 364 | freeParser(parser, req, socket); 365 | socket.destroy(); 366 | req.emit('error', ret); 367 | req.socket._hadError = true; 368 | } else if (parser.incoming && parser.incoming.upgrade) { 369 | // Upgrade or CONNECT 370 | var bytesParsed = ret; 371 | var res = parser.incoming; 372 | req.res = res; 373 | 374 | socket.removeListener('data', socketOnData); 375 | socket.removeListener('end', socketOnEnd); 376 | parser.finish(); 377 | 378 | var bodyHead = d.slice(bytesParsed, d.length); 379 | 380 | var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade'; 381 | if (req.listenerCount(eventName) > 0) { 382 | req.upgradeOrConnect = true; 383 | 384 | // detach the socket 385 | socket.emit('agentRemove'); 386 | socket.removeListener('close', socketCloseListener); 387 | socket.removeListener('error', socketErrorListener); 388 | 389 | // TODO(isaacs): Need a way to reset a stream to fresh state 390 | // IE, not flowing, and not explicitly paused. 391 | socket._readableState.flowing = null; 392 | 393 | req.emit(eventName, res, socket, bodyHead); 394 | req.emit('close'); 395 | } else { 396 | // Got Upgrade header or CONNECT method, but have no handler. 397 | socket.destroy(); 398 | } 399 | freeParser(parser, req, socket); 400 | } else if (parser.incoming && parser.incoming.complete && 401 | // When the status code is 100 (Continue), the server will 402 | // send a final response after this client sends a request 403 | // body. So, we must not free the parser. 404 | parser.incoming.statusCode !== 100) { 405 | socket.removeListener('data', socketOnData); 406 | socket.removeListener('end', socketOnEnd); 407 | freeParser(parser, req, socket); 408 | } 409 | } 410 | 411 | 412 | // client 413 | function parserOnIncomingClient(res, shouldKeepAlive) { 414 | var socket = this.socket; 415 | var req = socket._httpMessage; 416 | 417 | 418 | // propagate "domain" setting... 419 | if (req.domain && !res.domain) { 420 | debug('setting "res.domain"'); 421 | res.domain = req.domain; 422 | } 423 | 424 | debug('AGENT incoming response!'); 425 | 426 | if (req.res) { 427 | // We already have a response object, this means the server 428 | // sent a double response. 429 | socket.destroy(); 430 | return; 431 | } 432 | req.res = res; 433 | 434 | // Responses to CONNECT request is handled as Upgrade. 435 | if (req.method === 'CONNECT') { 436 | res.upgrade = true; 437 | return 2; // skip body, and the rest 438 | } 439 | 440 | // Responses to HEAD requests are crazy. 441 | // HEAD responses aren't allowed to have an entity-body 442 | // but *can* have a content-length which actually corresponds 443 | // to the content-length of the entity-body had the request 444 | // been a GET. 445 | var isHeadResponse = req.method === 'HEAD'; 446 | debug('AGENT isHeadResponse', isHeadResponse); 447 | 448 | if (res.statusCode === 100) { 449 | // restart the parser, as this is a continue message. 450 | delete req.res; // Clear res so that we don't hit double-responses. 451 | req.emit('continue'); 452 | return true; 453 | } 454 | 455 | if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) { 456 | // Server MUST respond with Connection:keep-alive for us to enable it. 457 | // If we've been upgraded (via WebSockets) we also shouldn't try to 458 | // keep the connection open. 459 | req.shouldKeepAlive = false; 460 | } 461 | 462 | 463 | // DTRACE_HTTP_CLIENT_RESPONSE(socket, req); 464 | // LTTNG_HTTP_CLIENT_RESPONSE(socket, req); 465 | // COUNTER_HTTP_CLIENT_RESPONSE(); 466 | req.res = res; 467 | res.req = req; 468 | 469 | // add our listener first, so that we guarantee socket cleanup 470 | res.on('end', responseOnEnd); 471 | req.on('prefinish', requestOnPrefinish); 472 | var handled = req.emit('response', res); 473 | 474 | // If the user did not listen for the 'response' event, then they 475 | // can't possibly read the data, so we ._dump() it into the void 476 | // so that the socket doesn't hang there in a paused state. 477 | if (!handled) 478 | res._dump(); 479 | 480 | return isHeadResponse; 481 | } 482 | 483 | // client 484 | function responseKeepAlive(res, req) { 485 | var socket = req.socket; 486 | 487 | if (!req.shouldKeepAlive) { 488 | if (socket.writable) { 489 | debug('AGENT socket.destroySoon()'); 490 | socket.destroySoon(); 491 | } 492 | assert(!socket.writable); 493 | } else { 494 | debug('AGENT socket keep-alive'); 495 | if (req.timeoutCb) { 496 | socket.setTimeout(0, req.timeoutCb); 497 | req.timeoutCb = null; 498 | } 499 | socket.removeListener('close', socketCloseListener); 500 | socket.removeListener('error', socketErrorListener); 501 | socket.once('error', freeSocketErrorListener); 502 | // Mark this socket as available, AFTER user-added end 503 | // handlers have a chance to run. 504 | process.nextTick(emitFreeNT, socket); 505 | } 506 | } 507 | 508 | function responseOnEnd() { 509 | const res = this; 510 | const req = this.req; 511 | 512 | req._ended = true; 513 | if (!req.shouldKeepAlive || req.finished) 514 | responseKeepAlive(res, req); 515 | } 516 | 517 | function requestOnPrefinish() { 518 | const req = this; 519 | const res = this.res; 520 | 521 | if (!req.shouldKeepAlive) 522 | return; 523 | 524 | if (req._ended) 525 | responseKeepAlive(res, req); 526 | } 527 | 528 | function emitFreeNT(socket) { 529 | socket.emit('free'); 530 | } 531 | 532 | function tickOnSocket(req, socket) { 533 | var parser = parsers.alloc(); 534 | req.socket = socket; 535 | req.connection = socket; 536 | parser.reinitialize(HTTPParser.RESPONSE); 537 | parser.socket = socket; 538 | parser.incoming = null; 539 | parser.outgoing = req; 540 | req.parser = parser; 541 | 542 | socket.parser = parser; 543 | socket._httpMessage = req; 544 | 545 | // Setup "drain" propagation. 546 | httpSocketSetup(socket); 547 | 548 | // Propagate headers limit from request object to parser 549 | if (typeof req.maxHeadersCount === 'number') { 550 | parser.maxHeaderPairs = req.maxHeadersCount << 1; 551 | } else { 552 | // Set default value because parser may be reused from FreeList 553 | parser.maxHeaderPairs = 2000; 554 | } 555 | 556 | parser.onIncoming = parserOnIncomingClient; 557 | socket.removeListener('error', freeSocketErrorListener); 558 | socket.on('error', socketErrorListener); 559 | socket.on('data', socketOnData); 560 | socket.on('end', socketOnEnd); 561 | socket.on('close', socketCloseListener); 562 | req.emit('socket', socket); 563 | } 564 | 565 | ClientRequest.prototype.onSocket = function(socket) { 566 | process.nextTick(onSocketNT, this, socket); 567 | }; 568 | 569 | function onSocketNT(req, socket) { 570 | if (req.aborted) { 571 | // If we were aborted while waiting for a socket, skip the whole thing. 572 | socket.emit('free'); 573 | } else { 574 | tickOnSocket(req, socket); 575 | } 576 | } 577 | 578 | ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) { 579 | // This function is for calls that need to happen once the socket is 580 | // connected and writable. It's an important promisy thing for all the socket 581 | // calls that happen either now (when a socket is assigned) or 582 | // in the future (when a socket gets assigned out of the pool and is 583 | // eventually writable). 584 | var self = this; 585 | 586 | function callSocketMethod() { 587 | if (method) 588 | self.socket[method].apply(self.socket, arguments_); 589 | 590 | if (typeof cb === 'function') 591 | cb(); 592 | } 593 | 594 | var onSocket = function() { 595 | if (self.socket.writable) { 596 | callSocketMethod(); 597 | } else { 598 | self.socket.once('connect', callSocketMethod); 599 | } 600 | }; 601 | 602 | if (!self.socket) { 603 | self.once('socket', onSocket); 604 | } else { 605 | onSocket(); 606 | } 607 | }; 608 | 609 | ClientRequest.prototype.setTimeout = function(msecs, callback) { 610 | if (callback) this.once('timeout', callback); 611 | 612 | var self = this; 613 | function emitTimeout() { 614 | self.emit('timeout'); 615 | } 616 | 617 | if (this.socket && this.socket.writable) { 618 | if (this.timeoutCb) 619 | this.socket.setTimeout(0, this.timeoutCb); 620 | this.timeoutCb = emitTimeout; 621 | this.socket.setTimeout(msecs, emitTimeout); 622 | return this; 623 | } 624 | 625 | // Set timeoutCb so that it'll get cleaned up on request end 626 | this.timeoutCb = emitTimeout; 627 | if (this.socket) { 628 | var sock = this.socket; 629 | this.socket.once('connect', function() { 630 | sock.setTimeout(msecs, emitTimeout); 631 | }); 632 | return this; 633 | } 634 | 635 | this.once('socket', function(sock) { 636 | sock.setTimeout(msecs, emitTimeout); 637 | }); 638 | 639 | return this; 640 | }; 641 | 642 | ClientRequest.prototype.setNoDelay = function() { 643 | const argsLen = arguments.length; 644 | const args = new Array(argsLen); 645 | for (var i = 0; i < argsLen; i++) 646 | args[i] = arguments[i]; 647 | this._deferToConnect('setNoDelay', args); 648 | }; 649 | ClientRequest.prototype.setSocketKeepAlive = function() { 650 | const argsLen = arguments.length; 651 | const args = new Array(argsLen); 652 | for (var i = 0; i < argsLen; i++) 653 | args[i] = arguments[i]; 654 | this._deferToConnect('setKeepAlive', args); 655 | }; 656 | 657 | ClientRequest.prototype.clearTimeout = function(cb) { 658 | this.setTimeout(0, cb); 659 | }; 660 | -------------------------------------------------------------------------------- /_http_outgoing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert').ok; 4 | const Stream = require('stream'); 5 | const timers = require('timers'); 6 | const util = require('util'); 7 | const internalUtil = util; 8 | const Buffer = require('buffer').Buffer; 9 | const common = require('./_http_common'); 10 | 11 | const CRLF = common.CRLF; 12 | const chunkExpression = common.chunkExpression; 13 | const debug = common.debug; 14 | 15 | const connectionExpression = /^Connection$/i; 16 | const transferEncodingExpression = /^Transfer-Encoding$/i; 17 | const closeExpression = /close/i; 18 | const contentLengthExpression = /^Content-Length$/i; 19 | const dateExpression = /^Date$/i; 20 | const expectExpression = /^Expect$/i; 21 | const trailerExpression = /^Trailer$/i; 22 | 23 | const automaticHeaders = { 24 | connection: true, 25 | 'content-length': true, 26 | 'transfer-encoding': true, 27 | date: true 28 | }; 29 | 30 | 31 | var dateCache; 32 | function utcDate() { 33 | if (!dateCache) { 34 | var d = new Date(); 35 | dateCache = d.toUTCString(); 36 | timers.enroll(utcDate, 1000 - d.getMilliseconds()); 37 | timers._unrefActive(utcDate); 38 | } 39 | return dateCache; 40 | } 41 | utcDate._onTimeout = function() { 42 | dateCache = undefined; 43 | }; 44 | 45 | 46 | function OutgoingMessage() { 47 | Stream.call(this); 48 | 49 | // Queue that holds all currently pending data, until the response will be 50 | // assigned to the socket (until it will its turn in the HTTP pipeline). 51 | this.output = []; 52 | this.outputEncodings = []; 53 | this.outputCallbacks = []; 54 | 55 | // `outputSize` is an approximate measure of how much data is queued on this 56 | // response. `_onPendingData` will be invoked to update similar global 57 | // per-connection counter. That counter will be used to pause/unpause the 58 | // TCP socket and HTTP Parser and thus handle the backpressure. 59 | this.outputSize = 0; 60 | 61 | this.writable = true; 62 | 63 | this._last = false; 64 | this.chunkedEncoding = false; 65 | this.shouldKeepAlive = true; 66 | this.useChunkedEncodingByDefault = true; 67 | this.sendDate = false; 68 | this._removedHeader = {}; 69 | 70 | this._contentLength = null; 71 | this._hasBody = true; 72 | this._trailer = ''; 73 | 74 | this.finished = false; 75 | this._headerSent = false; 76 | 77 | this.socket = null; 78 | this.connection = null; 79 | this._header = null; 80 | this._headers = null; 81 | this._headerNames = {}; 82 | 83 | this._onPendingData = null; 84 | } 85 | util.inherits(OutgoingMessage, Stream); 86 | 87 | 88 | exports.OutgoingMessage = OutgoingMessage; 89 | 90 | 91 | OutgoingMessage.prototype.setTimeout = function(msecs, callback) { 92 | 93 | if (callback) { 94 | this.on('timeout', callback); 95 | } 96 | 97 | if (!this.socket) { 98 | this.once('socket', function(socket) { 99 | socket.setTimeout(msecs); 100 | }); 101 | } else { 102 | this.socket.setTimeout(msecs); 103 | } 104 | return this; 105 | }; 106 | 107 | 108 | // It's possible that the socket will be destroyed, and removed from 109 | // any messages, before ever calling this. In that case, just skip 110 | // it, since something else is destroying this connection anyway. 111 | OutgoingMessage.prototype.destroy = function(error) { 112 | if (this.socket) 113 | this.socket.destroy(error); 114 | else 115 | this.once('socket', function(socket) { 116 | socket.destroy(error); 117 | }); 118 | }; 119 | 120 | 121 | // This abstract either writing directly to the socket or buffering it. 122 | OutgoingMessage.prototype._send = function(data, encoding, callback) { 123 | // This is a shameful hack to get the headers and first body chunk onto 124 | // the same packet. Future versions of Node are going to take care of 125 | // this at a lower level and in a more general way. 126 | if (!this._headerSent) { 127 | if (typeof data === 'string' && 128 | encoding !== 'hex' && 129 | encoding !== 'base64') { 130 | data = this._header + data; 131 | } else { 132 | this.output.unshift(this._header); 133 | this.outputEncodings.unshift('binary'); 134 | this.outputCallbacks.unshift(null); 135 | this.outputSize += this._header.length; 136 | if (typeof this._onPendingData === 'function') 137 | this._onPendingData(this._header.length); 138 | } 139 | this._headerSent = true; 140 | } 141 | return this._writeRaw(data, encoding, callback); 142 | }; 143 | 144 | 145 | OutgoingMessage.prototype._writeRaw = function(data, encoding, callback) { 146 | if (typeof encoding === 'function') { 147 | callback = encoding; 148 | encoding = null; 149 | } 150 | 151 | var connection = this.connection; 152 | if (connection && 153 | connection._httpMessage === this && 154 | connection.writable && 155 | !connection.destroyed) { 156 | // There might be pending data in the this.output buffer. 157 | var outputLength = this.output.length; 158 | if (outputLength > 0) { 159 | this._flushOutput(connection); 160 | } else if (data.length === 0) { 161 | if (typeof callback === 'function') 162 | process.nextTick(callback); 163 | return true; 164 | } 165 | 166 | // Directly write to socket. 167 | return connection.write(data, encoding, callback); 168 | } else if (connection && connection.destroyed) { 169 | // The socket was destroyed. If we're still trying to write to it, 170 | // then we haven't gotten the 'close' event yet. 171 | return false; 172 | } else { 173 | // buffer, as long as we're not destroyed. 174 | return this._buffer(data, encoding, callback); 175 | } 176 | }; 177 | 178 | 179 | OutgoingMessage.prototype._buffer = function(data, encoding, callback) { 180 | this.output.push(data); 181 | this.outputEncodings.push(encoding); 182 | this.outputCallbacks.push(callback); 183 | this.outputSize += data.length; 184 | if (typeof this._onPendingData === 'function') 185 | this._onPendingData(data.length); 186 | return false; 187 | }; 188 | 189 | 190 | OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { 191 | // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n' 192 | // in the case of response it is: 'HTTP/1.1 200 OK\r\n' 193 | var state = { 194 | sentConnectionHeader: false, 195 | sentContentLengthHeader: false, 196 | sentTransferEncodingHeader: false, 197 | sentDateHeader: false, 198 | sentExpect: false, 199 | sentTrailer: false, 200 | messageHeader: firstLine 201 | }; 202 | 203 | if (headers) { 204 | var keys = Object.keys(headers); 205 | var isArray = Array.isArray(headers); 206 | var field, value; 207 | 208 | for (var i = 0, l = keys.length; i < l; i++) { 209 | var key = keys[i]; 210 | if (isArray) { 211 | field = headers[key][0]; 212 | value = headers[key][1]; 213 | } else { 214 | field = key; 215 | value = headers[key]; 216 | } 217 | 218 | if (Array.isArray(value)) { 219 | for (var j = 0; j < value.length; j++) { 220 | storeHeader(this, state, field, value[j]); 221 | } 222 | } else { 223 | storeHeader(this, state, field, value); 224 | } 225 | } 226 | } 227 | 228 | // Date header 229 | if (this.sendDate === true && state.sentDateHeader === false) { 230 | state.messageHeader += 'Date: ' + utcDate() + CRLF; 231 | } 232 | 233 | // Force the connection to close when the response is a 204 No Content or 234 | // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked" 235 | // header. 236 | // 237 | // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but 238 | // node.js used to send out a zero chunk anyway to accommodate clients 239 | // that don't have special handling for those responses. 240 | // 241 | // It was pointed out that this might confuse reverse proxies to the point 242 | // of creating security liabilities, so suppress the zero chunk and force 243 | // the connection to close. 244 | var statusCode = this.statusCode; 245 | if ((statusCode === 204 || statusCode === 304) && 246 | this.chunkedEncoding === true) { 247 | debug(statusCode + ' response should not use chunked encoding,' + 248 | ' closing connection.'); 249 | this.chunkedEncoding = false; 250 | this.shouldKeepAlive = false; 251 | } 252 | 253 | // keep-alive logic 254 | if (this._removedHeader.connection) { 255 | this._last = true; 256 | this.shouldKeepAlive = false; 257 | } else if (state.sentConnectionHeader === false) { 258 | var shouldSendKeepAlive = this.shouldKeepAlive && 259 | (state.sentContentLengthHeader || 260 | this.useChunkedEncodingByDefault || 261 | this.agent); 262 | if (shouldSendKeepAlive) { 263 | state.messageHeader += 'Connection: keep-alive\r\n'; 264 | } else { 265 | this._last = true; 266 | state.messageHeader += 'Connection: close\r\n'; 267 | } 268 | } 269 | 270 | if (state.sentContentLengthHeader === false && 271 | state.sentTransferEncodingHeader === false) { 272 | if (!this._hasBody) { 273 | // Make sure we don't end the 0\r\n\r\n at the end of the message. 274 | this.chunkedEncoding = false; 275 | } else if (!this.useChunkedEncodingByDefault) { 276 | this._last = true; 277 | } else { 278 | if (!state.sentTrailer && 279 | !this._removedHeader['content-length'] && 280 | typeof this._contentLength === 'number') { 281 | state.messageHeader += 'Content-Length: ' + this._contentLength + 282 | '\r\n'; 283 | } else if (!this._removedHeader['transfer-encoding']) { 284 | state.messageHeader += 'Transfer-Encoding: chunked\r\n'; 285 | this.chunkedEncoding = true; 286 | } else { 287 | // We should only be able to get here if both Content-Length and 288 | // Transfer-Encoding are removed by the user. 289 | // See: test/parallel/test-http-remove-header-stays-removed.js 290 | debug('Both Content-Length and Transfer-Encoding are removed'); 291 | } 292 | } 293 | } 294 | 295 | this._header = state.messageHeader + CRLF; 296 | this._headerSent = false; 297 | 298 | // wait until the first body chunk, or close(), is sent to flush, 299 | // UNLESS we're sending Expect: 100-continue. 300 | if (state.sentExpect) this._send(''); 301 | }; 302 | 303 | function storeHeader(self, state, field, value) { 304 | if (!common._checkIsHttpToken(field)) { 305 | throw new TypeError( 306 | 'Header name must be a valid HTTP Token ["' + field + '"]'); 307 | } 308 | if (common._checkInvalidHeaderChar(value) === true) { 309 | throw new TypeError('The header content contains invalid characters'); 310 | } 311 | state.messageHeader += field + ': ' + escapeHeaderValue(value) + CRLF; 312 | 313 | if (connectionExpression.test(field)) { 314 | state.sentConnectionHeader = true; 315 | if (closeExpression.test(value)) { 316 | self._last = true; 317 | } else { 318 | self.shouldKeepAlive = true; 319 | } 320 | 321 | } else if (transferEncodingExpression.test(field)) { 322 | state.sentTransferEncodingHeader = true; 323 | if (chunkExpression.test(value)) self.chunkedEncoding = true; 324 | 325 | } else if (contentLengthExpression.test(field)) { 326 | state.sentContentLengthHeader = true; 327 | } else if (dateExpression.test(field)) { 328 | state.sentDateHeader = true; 329 | } else if (expectExpression.test(field)) { 330 | state.sentExpect = true; 331 | } else if (trailerExpression.test(field)) { 332 | state.sentTrailer = true; 333 | } 334 | } 335 | 336 | 337 | OutgoingMessage.prototype.setHeader = function(name, value) { 338 | if (!common._checkIsHttpToken(name)) 339 | throw new TypeError( 340 | 'Header name must be a valid HTTP Token ["' + name + '"]'); 341 | if (typeof name !== 'string') 342 | throw new TypeError('"name" should be a string in setHeader(name, value)'); 343 | if (value === undefined) 344 | throw new Error('"value" required in setHeader("' + name + '", value)'); 345 | if (this._header) 346 | throw new Error('Can\'t set headers after they are sent.'); 347 | if (common._checkInvalidHeaderChar(value) === true) { 348 | throw new TypeError('The header content contains invalid characters'); 349 | } 350 | if (this._headers === null) 351 | this._headers = {}; 352 | 353 | var key = name.toLowerCase(); 354 | this._headers[key] = value; 355 | this._headerNames[key] = name; 356 | 357 | if (automaticHeaders[key]) 358 | this._removedHeader[key] = false; 359 | }; 360 | 361 | 362 | OutgoingMessage.prototype.getHeader = function(name) { 363 | if (arguments.length < 1) { 364 | throw new Error('"name" argument is required for getHeader(name)'); 365 | } 366 | 367 | if (!this._headers) return; 368 | 369 | var key = name.toLowerCase(); 370 | return this._headers[key]; 371 | }; 372 | 373 | 374 | OutgoingMessage.prototype.removeHeader = function(name) { 375 | if (arguments.length < 1) { 376 | throw new Error('"name" argument is required for removeHeader(name)'); 377 | } 378 | 379 | if (this._header) { 380 | throw new Error('Can\'t remove headers after they are sent'); 381 | } 382 | 383 | var key = name.toLowerCase(); 384 | 385 | if (key === 'date') 386 | this.sendDate = false; 387 | else if (automaticHeaders[key]) 388 | this._removedHeader[key] = true; 389 | 390 | if (this._headers) { 391 | delete this._headers[key]; 392 | delete this._headerNames[key]; 393 | } 394 | }; 395 | 396 | 397 | OutgoingMessage.prototype._renderHeaders = function() { 398 | if (this._header) { 399 | throw new Error('Can\'t render headers after they are sent to the client'); 400 | } 401 | 402 | var headersMap = this._headers; 403 | if (!headersMap) return {}; 404 | 405 | var headers = {}; 406 | var keys = Object.keys(headersMap); 407 | var headerNames = this._headerNames; 408 | 409 | for (var i = 0, l = keys.length; i < l; i++) { 410 | var key = keys[i]; 411 | headers[headerNames[key]] = headersMap[key]; 412 | } 413 | return headers; 414 | }; 415 | 416 | 417 | Object.defineProperty(OutgoingMessage.prototype, 'headersSent', { 418 | configurable: true, 419 | enumerable: true, 420 | get: function() { return !!this._header; } 421 | }); 422 | 423 | 424 | OutgoingMessage.prototype.write = function(chunk, encoding, callback) { 425 | if (this.finished) { 426 | var err = new Error('write after end'); 427 | process.nextTick(writeAfterEndNT, this, err, callback); 428 | 429 | return true; 430 | } 431 | 432 | if (!this._header) { 433 | this._implicitHeader(); 434 | } 435 | 436 | if (!this._hasBody) { 437 | debug('This type of response MUST NOT have a body. ' + 438 | 'Ignoring write() calls.'); 439 | return true; 440 | } 441 | 442 | if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { 443 | throw new TypeError('First argument must be a string or Buffer'); 444 | } 445 | 446 | 447 | // If we get an empty string or buffer, then just do nothing, and 448 | // signal the user to keep writing. 449 | if (chunk.length === 0) return true; 450 | 451 | var len, ret; 452 | if (this.chunkedEncoding) { 453 | if (typeof chunk === 'string' && 454 | encoding !== 'hex' && 455 | encoding !== 'base64' && 456 | encoding !== 'binary') { 457 | len = Buffer.byteLength(chunk, encoding); 458 | chunk = len.toString(16) + CRLF + chunk + CRLF; 459 | ret = this._send(chunk, encoding, callback); 460 | } else { 461 | // buffer, or a non-toString-friendly encoding 462 | if (typeof chunk === 'string') 463 | len = Buffer.byteLength(chunk, encoding); 464 | else 465 | len = chunk.length; 466 | 467 | if (this.connection && !this.connection.corked) { 468 | this.connection.cork(); 469 | process.nextTick(connectionCorkNT, this.connection); 470 | } 471 | this._send(len.toString(16), 'binary', null); 472 | this._send(crlf_buf, null, null); 473 | this._send(chunk, encoding, null); 474 | ret = this._send(crlf_buf, null, callback); 475 | } 476 | } else { 477 | ret = this._send(chunk, encoding, callback); 478 | } 479 | 480 | debug('write ret = ' + ret); 481 | return ret; 482 | }; 483 | 484 | 485 | function writeAfterEndNT(self, err, callback) { 486 | self.emit('error', err); 487 | if (callback) callback(err); 488 | } 489 | 490 | 491 | function connectionCorkNT(conn) { 492 | conn.uncork(); 493 | } 494 | 495 | 496 | function escapeHeaderValue(value) { 497 | // Protect against response splitting. The regex test is there to 498 | // minimize the performance impact in the common case. 499 | return /[\r\n]/.test(value) ? value.replace(/[\r\n]+[ \t]*/g, '') : value; 500 | } 501 | 502 | 503 | OutgoingMessage.prototype.addTrailers = function(headers) { 504 | this._trailer = ''; 505 | var keys = Object.keys(headers); 506 | var isArray = Array.isArray(headers); 507 | var field, value; 508 | for (var i = 0, l = keys.length; i < l; i++) { 509 | var key = keys[i]; 510 | if (isArray) { 511 | field = headers[key][0]; 512 | value = headers[key][1]; 513 | } else { 514 | field = key; 515 | value = headers[key]; 516 | } 517 | if (!common._checkIsHttpToken(field)) { 518 | throw new TypeError( 519 | 'Trailer name must be a valid HTTP Token ["' + field + '"]'); 520 | } 521 | if (common._checkInvalidHeaderChar(value) === true) { 522 | throw new TypeError('The header content contains invalid characters'); 523 | } 524 | this._trailer += field + ': ' + escapeHeaderValue(value) + CRLF; 525 | } 526 | }; 527 | 528 | 529 | const crlf_buf = Buffer.from('\r\n'); 530 | 531 | 532 | OutgoingMessage.prototype.end = function(data, encoding, callback) { 533 | if (typeof data === 'function') { 534 | callback = data; 535 | data = null; 536 | } else if (typeof encoding === 'function') { 537 | callback = encoding; 538 | encoding = null; 539 | } 540 | 541 | if (data && typeof data !== 'string' && !(data instanceof Buffer)) { 542 | throw new TypeError('First argument must be a string or Buffer'); 543 | } 544 | 545 | if (this.finished) { 546 | return false; 547 | } 548 | 549 | if (!this._header) { 550 | if (data) { 551 | if (typeof data === 'string') 552 | this._contentLength = Buffer.byteLength(data, encoding); 553 | else 554 | this._contentLength = data.length; 555 | } else { 556 | this._contentLength = 0; 557 | } 558 | this._implicitHeader(); 559 | } 560 | 561 | if (data && !this._hasBody) { 562 | debug('This type of response MUST NOT have a body. ' + 563 | 'Ignoring data passed to end().'); 564 | data = null; 565 | } 566 | 567 | if (this.connection && data) 568 | this.connection.cork(); 569 | 570 | var ret; 571 | if (data) { 572 | // Normal body write. 573 | this.write(data, encoding); 574 | } 575 | 576 | if (typeof callback === 'function') 577 | this.once('finish', callback); 578 | 579 | const finish = () => { 580 | this.emit('finish'); 581 | }; 582 | 583 | if (this._hasBody && this.chunkedEncoding) { 584 | ret = this._send('0\r\n' + this._trailer + '\r\n', 'binary', finish); 585 | } else { 586 | // Force a flush, HACK. 587 | ret = this._send('', 'binary', finish); 588 | } 589 | 590 | if (this.connection && data) 591 | this.connection.uncork(); 592 | 593 | this.finished = true; 594 | 595 | // There is the first message on the outgoing queue, and we've sent 596 | // everything to the socket. 597 | debug('outgoing message end.'); 598 | if (this.output.length === 0 && 599 | this.connection && 600 | this.connection._httpMessage === this) { 601 | this._finish(); 602 | } 603 | 604 | return ret; 605 | }; 606 | 607 | 608 | OutgoingMessage.prototype._finish = function() { 609 | assert(this.connection); 610 | this.emit('prefinish'); 611 | }; 612 | 613 | 614 | // This logic is probably a bit confusing. Let me explain a bit: 615 | // 616 | // In both HTTP servers and clients it is possible to queue up several 617 | // outgoing messages. This is easiest to imagine in the case of a client. 618 | // Take the following situation: 619 | // 620 | // req1 = client.request('GET', '/'); 621 | // req2 = client.request('POST', '/'); 622 | // 623 | // When the user does 624 | // 625 | // req2.write('hello world\n'); 626 | // 627 | // it's possible that the first request has not been completely flushed to 628 | // the socket yet. Thus the outgoing messages need to be prepared to queue 629 | // up data internally before sending it on further to the socket's queue. 630 | // 631 | // This function, outgoingFlush(), is called by both the Server and Client 632 | // to attempt to flush any pending messages out to the socket. 633 | OutgoingMessage.prototype._flush = function() { 634 | var socket = this.socket; 635 | var ret; 636 | 637 | if (socket && socket.writable) { 638 | // There might be remaining data in this.output; write it out 639 | ret = this._flushOutput(socket); 640 | 641 | if (this.finished) { 642 | // This is a queue to the server or client to bring in the next this. 643 | this._finish(); 644 | } else if (ret) { 645 | // This is necessary to prevent https from breaking 646 | this.emit('drain'); 647 | } 648 | } 649 | }; 650 | 651 | OutgoingMessage.prototype._flushOutput = function _flushOutput(socket) { 652 | var ret; 653 | var outputLength = this.output.length; 654 | if (outputLength <= 0) 655 | return ret; 656 | 657 | var output = this.output; 658 | var outputEncodings = this.outputEncodings; 659 | var outputCallbacks = this.outputCallbacks; 660 | socket.cork(); 661 | for (var i = 0; i < outputLength; i++) { 662 | ret = socket.write(output[i], outputEncodings[i], 663 | outputCallbacks[i]); 664 | } 665 | socket.uncork(); 666 | 667 | this.output = []; 668 | this.outputEncodings = []; 669 | this.outputCallbacks = []; 670 | if (typeof this._onPendingData === 'function') 671 | this._onPendingData(-this.outputSize); 672 | this.outputSize = 0; 673 | 674 | return ret; 675 | }; 676 | 677 | 678 | OutgoingMessage.prototype.flushHeaders = function() { 679 | if (!this._header) { 680 | this._implicitHeader(); 681 | } 682 | 683 | // Force-flush the headers. 684 | this._send(''); 685 | }; 686 | 687 | OutgoingMessage.prototype.flush = internalUtil.deprecate(function() { 688 | this.flushHeaders(); 689 | }, 'OutgoingMessage.flush is deprecated. Use flushHeaders instead.'); 690 | --------------------------------------------------------------------------------