1 2/* 3 * Copyright (C) NGINX, Inc. 4 */ 5 6'use strict'; 7 8const EventEmitter = require('events'); 9const http = require('http'); 10const util = require('util'); 11const unit_lib = require('./build/Release/unit-http'); 12const Socket = require('./socket'); 13const WebSocketFrame = require('./websocket_frame'); 14const Readable = require('stream').Readable; 15 16 17function ServerResponse(req) { 18 EventEmitter.call(this); 19 20 this.headers = {}; 21 22 this.server = req.server; 23 this._request = req; 24 req._response = this; 25 this.socket = req.socket; 26 this.connection = req.connection; 27 this.writable = true; 28} 29util.inherits(ServerResponse, EventEmitter); 30 31ServerResponse.prototype.statusCode = 200; 32ServerResponse.prototype.statusMessage = undefined; 33ServerResponse.prototype.headers_len = 0; 34ServerResponse.prototype.headers_count = 0; 35ServerResponse.prototype.headersSent = false; 36ServerResponse.prototype.destroyed = false; 37ServerResponse.prototype.finished = false; 38 39ServerResponse.prototype.destroy = function destroy(error) { 40 if (!this.destroyed) { 41 this.destroyed = true; 42 } 43 44 return this; 45}; 46 47ServerResponse.prototype._finish = function _finish() { 48 this.headers = {}; 49 this.headers_len = 0; 50 this.headers_count = 0; 51 this.finished = true; 52}; 53 54ServerResponse.prototype.assignSocket = function assignSocket(socket) { 55}; 56 57ServerResponse.prototype.detachSocket = function detachSocket(socket) { 58}; 59 60ServerResponse.prototype.writeContinue = function writeContinue(cb) { 61}; 62 63ServerResponse.prototype.writeProcessing = function writeProcessing(cb) { 64}; 65 66ServerResponse.prototype.setHeader = function setHeader(name, value) { 67 if (typeof name !== 'string') { 68 throw new TypeError('Name argument must be a string'); 69 } 70 71 let value_len = 0 72 let count = 0; 73 74 if (Array.isArray(value)) { 75 count = value.length; 76 77 value.forEach(function(val) { 78 value_len += Buffer.byteLength(val + "", 'latin1'); 79 }); 80 81 } else { 82 count = 1; 83 value_len = Buffer.byteLength(value + "", 'latin1'); 84 } 85 86 let lc_name = name.toLowerCase(); 87 88 if (lc_name in this.headers) { 89 this._removeHeader(lc_name); 90 } 91 92 let name_len = Buffer.byteLength(name, 'latin1'); 93 94 this.headers[lc_name] = [name, value]; 95 this.headers_len += value_len + (name_len * count); 96 this.headers_count += count; 97}; 98 99ServerResponse.prototype.getHeader = function getHeader(name) { 100 const entry = this.headers[name.toLowerCase()]; 101 102 return entry && entry[1]; 103}; 104 105ServerResponse.prototype.getHeaderNames = function getHeaderNames() { 106 return Object.keys(this.headers); 107}; 108 109ServerResponse.prototype.getHeaders = function getHeaders() { 110 const ret = Object.create(null); 111 112 if (this.headers) { 113 const keys = Object.keys(this.headers); 114 115 for (var i = 0; i < keys.length; i++) { 116 const key = keys[i]; 117 118 ret[key] = this.headers[key][1]; 119 } 120 } 121 122 return ret; 123}; 124 125ServerResponse.prototype.hasHeader = function hasHeader(name) { 126 return name.toLowerCase() in this.headers; 127}; 128 129ServerResponse.prototype.removeHeader = function removeHeader(name) { 130 if (typeof name !== 'string') { 131 throw new TypeError('Name argument must be a string'); 132 } 133 134 let lc_name = name.toLowerCase(); 135 136 if (lc_name in this.headers) { 137 this._removeHeader(lc_name); 138 } 139}; 140 141ServerResponse.prototype.flushHeaders = function flushHeaders() { 142 this._sendHeaders(); 143}; 144 145ServerResponse.prototype._removeHeader = function _removeHeader(lc_name) { 146 let entry = this.headers[lc_name]; 147 let name_len = Buffer.byteLength(entry[0] + "", 'latin1'); 148 let value = entry[1]; 149 150 delete this.headers[lc_name]; 151 152 if (Array.isArray(value)) { 153 this.headers_count -= value.length; 154 this.headers_len -= value.length * name_len; 155 156 value.forEach(function(val) { 157 this.headers_len -= Buffer.byteLength(val + "", 'latin1'); 158 }); 159 160 return; 161 } 162 163 this.headers_count--; 164 this.headers_len -= name_len + Buffer.byteLength(value + "", 'latin1'); 165}; 166 167ServerResponse.prototype.sendDate = function sendDate() { 168 throw new Error("Not supported"); 169}; 170 171ServerResponse.prototype.setTimeout = function setTimeout(msecs, callback) { 172 this.timeout = msecs; 173 174 if (callback) { 175 this.on('timeout', callback); 176 } 177 178 return this; 179}; 180 181ServerResponse.prototype.writeHead = writeHead; 182ServerResponse.prototype.writeHeader = ServerResponse.prototype.writeHead; 183 184function writeHead(statusCode, reason, obj) { 185 var originalStatusCode = statusCode; 186 187 statusCode |= 0; 188 189 if (statusCode < 100 || statusCode > 999) { 190 throw new ERR_HTTP_INVALID_STATUS_CODE(originalStatusCode); 191 } 192 193 if (typeof reason === 'string') { 194 this.statusMessage = reason; 195 196 } else { 197 if (!this.statusMessage) { 198 this.statusMessage = http.STATUS_CODES[statusCode] || 'unknown'; 199 } 200 201 obj = reason; 202 } 203 204 this.statusCode = statusCode; 205 206 if (obj) { 207 var k; 208 var keys = Object.keys(obj); 209 210 for (var i = 0; i < keys.length; i++) { 211 k = keys[i]; 212 213 if (k) { 214 this.setHeader(k, obj[k]); 215 } 216 } 217 } 218 219 return this; 220}; 221 222/* 223 * Some Node.js packages are known to be using this undocumented function, 224 * notably "compression" middleware. 225 */ 226ServerResponse.prototype._implicitHeader = function _implicitHeader() { 227 this.writeHead(this.statusCode); 228}; 229 230ServerResponse.prototype._send_headers = unit_lib.response_send_headers; 231 232ServerResponse.prototype._sendHeaders = function _sendHeaders() { 233 if (!this.headersSent) { 234 this._send_headers(this.statusCode, this.headers, this.headers_count, 235 this.headers_len); 236 237 this.headersSent = true; 238 } 239}; 240 241ServerResponse.prototype._write = unit_lib.response_write; 242 243ServerResponse.prototype._writeBody = function(chunk, encoding, callback) { 244 var contentLength = 0; 245 var res, o; 246 247 this._sendHeaders(); 248 249 if (typeof chunk === 'function') { 250 callback = chunk; 251 chunk = null; 252 253 } else if (typeof encoding === 'function') { 254 callback = encoding; 255 encoding = null; 256 } 257 258 if (chunk) { 259 if (typeof chunk !== 'string' && !(chunk instanceof Buffer || 260 chunk instanceof Uint8Array)) { 261 throw new TypeError( 262 'First argument must be a string, Buffer, ' + 263 'or Uint8Array'); 264 } 265 266 if (typeof chunk === 'string') { 267 contentLength = Buffer.byteLength(chunk, encoding); 268 269 if (contentLength > unit_lib.buf_min) { 270 chunk = Buffer.from(chunk, encoding); 271 272 contentLength = chunk.length; 273 } 274 275 } else { 276 contentLength = chunk.length; 277 } 278 279 if (this.server._output.length > 0 || !this.socket.writable) { 280 o = new BufferedOutput(this, 0, chunk, encoding, callback); 281 this.server._output.push(o); 282 283 return false; 284 } 285 286 res = this._write(chunk, 0, contentLength); 287 if (res < contentLength) { 288 this.socket.writable = false; 289 this.writable = false; 290 291 o = new BufferedOutput(this, res, chunk, encoding, callback); 292 this.server._output.push(o); 293 294 return false; 295 } 296 } 297 298 if (typeof callback === 'function') { 299 /* 300 * The callback must be called only when response.write() caller 301 * completes. process.nextTick() postpones the callback execution. 302 * 303 * process.nextTick() is not technically part of the event loop. 304 * Instead, the nextTickQueue will be processed after the current 305 * operation completes, regardless of the current phase of 306 * the event loop. All callbacks passed to process.nextTick() 307 * will be resolved before the event loop continues. 308 */ 309 process.nextTick(callback); 310 } 311 312 return true; 313}; 314 315ServerResponse.prototype.write = function write(chunk, encoding, callback) { 316 if (this.finished) { 317 if (typeof encoding === 'function') { 318 callback = encoding; 319 encoding = null; 320 } 321 322 var err = new Error("Write after end"); 323 process.nextTick(() => { 324 this.emit('error', err); 325 326 if (typeof callback === 'function') { 327 callback(err); 328 } 329 }) 330 } 331 332 return this._writeBody(chunk, encoding, callback); 333}; 334 335ServerResponse.prototype._end = unit_lib.response_end; 336 337ServerResponse.prototype.end = function end(chunk, encoding, callback) { 338 if (!this.finished) { 339 if (typeof encoding === 'function') { 340 callback = encoding; 341 encoding = null; 342 } 343 344 this._writeBody(chunk, encoding, () => { 345 this._end(); 346 347 if (typeof callback === 'function') { 348 callback(); 349 } 350 351 this.emit("finish"); 352 }); 353 354 this.finished = true; 355 } 356 357 return this; 358}; 359 360function ServerRequest(server, socket) { 361 Readable.call(this); 362 363 this.server = server; 364 this.socket = socket; 365 this.connection = socket; 366 this._pushed_eofchunk = false; 367} 368util.inherits(ServerRequest, Readable); 369 370ServerRequest.prototype.setTimeout = function setTimeout(msecs, callback) { 371 this.timeout = msecs; 372 373 if (callback) { 374 this.on('timeout', callback); 375 } 376 377 return this; 378}; 379 380ServerRequest.prototype.statusCode = function statusCode() { 381 /* Only valid for response obtained from http.ClientRequest. */ 382}; 383 384ServerRequest.prototype.statusMessage = function statusMessage() { 385 /* Only valid for response obtained from http.ClientRequest. */ 386}; 387 388ServerRequest.prototype.trailers = function trailers() { 389 throw new Error("Not supported"); 390}; 391 392ServerRequest.prototype.METHODS = function METHODS() { 393 return http.METHODS; 394}; 395 396ServerRequest.prototype.STATUS_CODES = function STATUS_CODES() { 397 return http.STATUS_CODES; 398}; 399 400ServerRequest.prototype._request_read = unit_lib.request_read; 401 402ServerRequest.prototype._read = function _read(n) { 403 const b = this._request_read(n); 404 405 if (b != null) { 406 this.push(b); 407 } 408 409 if (!this._pushed_eofchunk && (b == null || b.length < n)) { 410 this._pushed_eofchunk = true; 411 this.push(null); 412 } 413}; 414 415 416function Server(options, requestListener) { 417 if (typeof options === 'function') { 418 requestListener = options; 419 options = {}; 420 } else { 421 console.warn("http.Server constructor was called with unsupported options, using default settings"); 422 } 423 424 EventEmitter.call(this); 425 426 this.unit = new unit_lib.Unit(); 427 this.unit.server = this; 428 429 this.unit.createServer(); 430 431 this.Socket = Socket; 432 this.ServerRequest = ServerRequest; 433 this.ServerResponse = ServerResponse; 434 this.WebSocketFrame = WebSocketFrame; 435 436 if (requestListener) { 437 this.on('request', requestListener); 438 } 439 440 this._upgradeListenerCount = 0; 441 this.on('newListener', function(ev) { 442 if (ev === 'upgrade'){ 443 this._upgradeListenerCount++; 444 } 445 }).on('removeListener', function(ev) { 446 if (ev === 'upgrade') { 447 this._upgradeListenerCount--; 448 } 449 }); 450 451 this._output = []; 452 this._drain_resp = new Set(); 453} 454 455util.inherits(Server, EventEmitter); 456 457Server.prototype.setTimeout = function setTimeout(msecs, callback) { 458 this.timeout = msecs; 459 460 if (callback) { 461 this.on('timeout', callback); 462 } 463 464 return this; 465}; 466 467Server.prototype.listen = function (...args) { 468 this.unit.listen(); 469 470 if (typeof args[args.length - 1] === 'function') { 471 this.once('listening', args[args.length - 1]); 472 } 473 474 /* 475 * Some express.js apps use the returned server object inside the listening 476 * callback, so we timeout the listening event to occur after this function 477 * returns. 478 */ 479 setImmediate(function() { 480 this.emit('listening') 481 }.bind(this)) 482 483 return this; 484}; 485 486Server.prototype.address = function () { 487 return { 488 family: "IPv4", 489 address: "127.0.0.1", 490 port: 80 491 } 492} 493 494Server.prototype.emit_request = function (req, res) { 495 if (req._websocket_handshake && this._upgradeListenerCount > 0) { 496 this.emit('upgrade', req, req.socket); 497 498 } else { 499 this.emit("request", req, res); 500 } 501}; 502 503Server.prototype.emit_close = function () { 504 this.emit('close'); 505}; 506 507Server.prototype.emit_drain = function () { 508 var res, o, l; 509 510 if (this._output.length <= 0) { 511 return; 512 } 513 514 while (this._output.length > 0) { 515 o = this._output[0]; 516 517 if (typeof o.chunk === 'string') { 518 l = Buffer.byteLength(o.chunk, o.encoding); 519 520 } else { 521 l = o.chunk.length; 522 } 523 524 res = o.resp._write(o.chunk, o.offset, l); 525 526 o.offset += res; 527 if (o.offset < l) { 528 return; 529 } 530 531 this._drain_resp.add(o.resp); 532 533 if (typeof o.callback === 'function') { 534 process.nextTick(o.callback); 535 } 536 537 this._output.shift(); 538 } 539 540 for (var resp of this._drain_resp) { 541 542 if (resp.socket.writable) { 543 continue; 544 } 545 546 resp.socket.writable = true; 547 resp.writable = true; 548 549 process.nextTick(() => { 550 resp.emit("drain"); 551 }); 552 } 553 554 this._drain_resp.clear(); 555}; 556 557function BufferedOutput(resp, offset, chunk, encoding, callback) { 558 this.resp = resp; 559 this.offset = offset; 560 this.chunk = chunk; 561 this.encoding = encoding; 562 this.callback = callback; 563} 564 565function connectionListener(socket) { 566} 567 568module.exports = { 569 Server, 570 ServerResponse, 571 ServerRequest, 572 _connectionListener: connectionListener 573}; 574