UNPKG

node-fastcgi

Version:

Create FastCGI applications in node. Near drop-in replacement for node's http module.

283 lines (223 loc) 7.52 kB
/** * Copyright (c) 2016 Fabio Massaioli and other contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 'use strict'; var fcgi = require('fastcgi-stream'), http = require('http'), util = require('util'), streams = require('./streams.js'), response = require('./response.js'); var InputStream = streams.InputStream, OutputStream = streams.OutputStream, IOStream = streams.IOStream; var Response = response.Response, AuthorizerResponse = response.AuthorizerResponse; exports.Request = Request; /** * function Request(conn, id, role, keepalive) * Represents a FastCGI request. Emulates net.Socket API. */ function Request(conn, id, role, keepalive) { IOStream.call(this, conn, id); this._role = role; this._keepAlive = keepalive; this._stdinComplete = false; this._dataComplete = false; this._req = null; this._res = null; this.params = {}; this.dataStream = new InputStream(); // NOTE: data stream for filter requests this.errorStream = new OutputStream(conn, id, fcgi.records.StdErr); this.on('drain', function () { if (this._res) { this._res.emit('drain'); } }); this.once('finish', function () { if (this._res) { this._res.detachSocket(this); this._res = null; } this.destroy(); }); this.on('close', this.errorStream.destroy.bind(this.errorStream, null)); } util.inherits(Request, IOStream); Request.prototype._param = function (name, value) { this.params[name] = value; }; var HEADER_EXPR = /^HTTP_/; var UNDERSCORE_EXPR = /_/g; function makeUrl(params) { if (params.REQUEST_URI && params.REQUEST_URI.length) { return params.REQUEST_URI; } else { var url = ''; if (params.SCRIPT_NAME) { url += params.SCRIPT_NAME; } if (params.PATH_INFO) { url += params.PATH_INFO; } if (params.QUERY_STRING) { url += '?' + params.QUERY_STRING; } return url; } } Request.prototype._createReqRes = function () { this._req = new http.IncomingMessage(this); if (this.params.SERVER_PROTOCOL) { this._req.httpVersion = this.params.SERVER_PROTOCOL.slice(5); var numbers = this._req.httpVersion.split('.'); this._req.httpVersionMajor = parseInt(numbers[0]); this._req.httpVersionMinor = parseInt(numbers[1]); } this._req.url = makeUrl(this.params); var raw = this._req.rawHeaders, dest = this._req.headers; for (var param in this.params) { if (HEADER_EXPR.test(param) || param === 'CONTENT_LENGTH' || param === 'CONTENT_TYPE') { var name = param.replace(HEADER_EXPR, '').replace(UNDERSCORE_EXPR, '-'); // Ignore HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH if (HEADER_EXPR.test(param) && (name.toLowerCase() === 'content-type' || name.toLowerCase() === 'content-length')) continue; var value = this.params[param]; raw.push(name, value); this._req._addHeaderLine(name, value, dest); } } this._req.method = this.params.REQUEST_METHOD || 'GET'; this._req.complete = this._stdinComplete && this._dataComplete; this.pause(); this.on('data', function (data) { if (this._req) { if (!this._req.push(data)) { this.pause(); } } }); this.on('end', function () { if (this._req) { this._req.push(null); } }); if (this._role === fcgi.records.BeginRequest.roles.AUTHORIZER) { this._res = new AuthorizerResponse(this._req); } else { this._res = new Response(this._req); } this._res.shouldKeepAlive = this.params.HTTP_CONNECTION === 'keep-alive'; this._res.assignSocket(this); var self = this; this._res.on('finish', function () { self.end(); }); // NOTE: Backward compatibility this._req.data = this.dataStream; this._req.cgiParams = this.params; this._res.stdout = this; this._res.stderr = this.errorStream; if (this._req.complete) { process.nextTick(this._req.emit.bind(this._req, 'complete')); } return { req: this._req, res: this._res }; } Request.prototype._abort = function (err) { if (this._req) { this._req.emit('aborted') } this.destroy(err); }; // net.Socket API emulation Object.defineProperties(Request.prototype, { "bufferSize": { get: function () { return this._conn.bufferSize; } }, "bytesRead": { get: function () { return this._conn.bytesRead; } }, "bytesWritten": { get: function () { return this._conn.bytesWritten } }, "localAddress": { get: function () { return this.params.SERVER_ADDR || '::'; } }, "localPort": { get: function () { return parseInt(this.params.SERVER_PORT) || 0; } }, "remoteAddress": { get: function () { return this.params.REMOTE_ADDR || '::'; } }, "remoteFamily": { get: function() { return this.remoteAddress.indexOf('.') !== -1 ? 'IPv4' : 'IPv6'; } }, "remotePort": { get: function () { return parseInt(this.params.REMOTE_PORT) || 0; } } }); Request.prototype.address = function () { return { address: this.localAddress, family: this.localAddress.indexOf('.') !== -1 ? 'IPv4' : 'IPv6', port: this.localPort }; }; Request.prototype._destroy = function (err, callback) { var self = this; IOStream.prototype._destroy.call(this, err, function () { self._conn.endRequest(self._id, err ? 1 : 0, undefined, function () { callback(err); }); }); } Request.prototype.ref = function () { return this._conn.ref(); } Request.prototype.unref = function () { return this._conn.unref(); } Request.prototype.setKeepAlive = function () { return this._conn.setKeepAlive.apply(this._conn, arguments); } Request.prototype.setNoDelay = function () { return this._conn.setNoDelay.apply(this._conn, arguments); } Request.prototype.setTimeout = function (msecs, callback) { if (callback) this.once('timeout', callback); return this._conn.setTimeout(msecs); };