it("saves raw header names", (function () { _$jscoverage['test/incoming_message_test.js'][8]++; argo(); _$jscoverage['test/incoming_message_test.js'][10]++; var incomingMessage = new http.IncomingMessage(); _$jscoverage['test/incoming_message_test.js'][11]++; incomingMessage._addHeaderLine("Super-Duper", "funtime"); _$jscoverage['test/incoming_message_test.js'][12]++; assert.equal(incomingMessage._rawHeaderNames["super-duper"], "Super-Duper"); }));
function checkDest(field, result, value) { const dest = {}; const incomingMessage = new IncomingMessage(field); // Dest is changed by IncomingMessage._addHeaderLine if (value) incomingMessage._addHeaderLine(field, 'test', dest); incomingMessage._addHeaderLine(field, value, dest); assert.deepStrictEqual(dest, result); }
exports['handle ServerRequest POST with bodyDecoder'] = function (test) { var f = forms.create({field1: forms.fields.string()}), req = new http.IncomingMessage(); req.body = {field1: 'test'}; req.method = 'POST'; req.resume(); f.handle(req, { success: function (form) { test.equals(form.data.field1, 'test'); test.done(); } }); };
exports['handle ServerRequest GET'] = function (test) { var f = forms.create({field1: forms.fields.string()}), req = new http.IncomingMessage(); req.method = 'GET'; req.url = '/?field1=test'; req.resume(); f.handle(req, { success: function (form) { test.equals(form.data.field1, 'test'); test.done(); } }); };
exports['handle ServerRequest POST'] = function (test) { var f = forms.create({field1: forms.fields.string()}), req = new http.IncomingMessage(); req.method = 'POST'; f.handle(req, { success: function (form) { test.equals(form.data.field1, 'test'); test.done(); } }); req.emit('data', 'field1=test'); req.emit('end'); };
test('handle ServerRequest POST', function (t) { t.plan(1); var f = forms.create({field1: forms.fields.string()}), req = new http.IncomingMessage(); req.body = {field1: 'test'}; req.method = 'POST'; f.handle(req, { success: function (form) { t.equal(form.data.field1, 'test'); t.end(); } }); req.emit('data', 'field1=test'); req.emit('end'); });
test('match by string', function (done) { const req = new http.IncomingMessage() body({ match: 'hello world' })(req, null, next) req.push(new Buffer('hello ')) req.push(new Buffer('world')) req.push(null) function next (err, ignore) { expect(err).to.be.null expect(ignore).to.be.false done() } })
test('cannot match', function (done) { const req = new http.IncomingMessage() body({ match: 'byebye' })(req, null, next) req.push(new Buffer('hello ')) req.push(new Buffer('world')) req.push(null) function next (err, ignore) { expect(err).to.be.null expect(ignore).to.be.true done() } })
var httpRequest = function(socket){ http.IncomingMessage.call(this,socket); this._missingData = 0; };
.on('data', chunk => { if (!Buffer.isBuffer(chunk)) { chunk = Buffer.from(chunk, 'utf-8'); } requestBodyChunks.push(chunk); req.push(chunk); })
test('match by function', function (done) { const req = new http.IncomingMessage() function matcher (body) { return body.indexOf('hello world') !== -1 } body({ match: matcher })(req, null, next) req.push(new Buffer('hello ')) req.push(new Buffer('world')) req.push(null) function next (err, ignore) { expect(err).to.be.null expect(ignore).to.be.false done() } })
function Request(socket) { IncomingMessage.call(this, socket); // expose properties this.url = socket.nsp.name; this.request = socket.request; this.params = socket.params; this.route = socket.route; this.query = socket.request.query; };
topic: function() { var self = this; var req = new http.IncomingMessage(); var user = { id: '1', username: '******' }; req._passport = {}; req._passport.instance = {}; req._passport.session = {}; function logIn() { req.logIn(user, { session: false }); self.callback(null, req); } process.nextTick(function () { logIn(); }); },
test('close', function (done) { const delay = 20 const req = new http.IncomingMessage() slowOpen({ delay: delay })(req, null, next) process.nextTick(function () { req.emit('close') }) setTimeout(function () { done() }, delay + 10) function next (err) { done(new Error('Invalid callback: ' + err)) } })
topic: function() { var self = this; var req = new http.IncomingMessage(); var user = { id: '1', username: '******' }; req._passport = {}; req._passport.instance = {}; req._passport.instance._userProperty = 'currentUser'; req._passport.session = {}; function logIn() { req.logIn(user, { session: false }, function(err) { self.callback(err, req); }) } process.nextTick(function () { logIn(); }); },
topic: function() { var self = this; var req = new http.IncomingMessage(); var user = { id: '1', username: '******' }; req._passport = {}; req._passport.instance = {}; req._passport.session = {}; req._passport.instance.serializeUser = function(user, done) { done(new Error('failed to serialize')); } function logIn() { req.logIn(user, function(err) { self.callback(err, req); }) } process.nextTick(function () { logIn(); }); },
function THTP_Request_toHTTP(packet,link, stream){ // mimic http://nodejs.org/api/http.html#http_http_incomingmessage http.IncomingMessage.call(this); this.method = packet.json[':method']; this.url = packet.json[':path'] || "/"; this.headers = packet.json; this.headers['x-hashname'] = link.hashname; // for any http handler visibility this.hashname = link.hashname; this.connection = { remoteAddress : link.hashname , cork : function(){ //noop } }; this.on = stream.on.bind(stream); this.read = stream.read.bind(stream); this.pipe = stream.pipe.bind(stream); }
var Request = exports.Request = function (props) { http.IncomingMessage.call(this); for (var i in props) this[i] = props[i]; };
var Buffer = require('safe-buffer').Buffer var finalhandler = require('..') var http = require('http') var utils = require('./support/utils') var assert = utils.assert var createError = utils.createError var createServer = utils.createServer var createSlowWriteStream = utils.createSlowWriteStream var rawrequest = utils.rawrequest var request = utils.request var shouldHaveStatusMessage = utils.shouldHaveStatusMessage var shouldNotHaveBody = utils.shouldNotHaveBody var shouldNotHaveHeader = utils.shouldNotHaveHeader var describeStatusMessage = !/statusMessage/.test(http.IncomingMessage.toString()) ? describe.skip : describe describe('finalhandler(req, res)', function () { describe('headers', function () { it('should ignore err.headers without status code', function (done) { request(createServer(createError('oops!', { headers: { 'X-Custom-Header': 'foo' } }))) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should ignore err.headers with invalid res.status', function (done) {
process.nextTick(function () { req.emit('close') })
function logIn() { req.logIn(user, { session: false }, function(err) { self.callback(err, req); }) }
function logIn() { req.logIn(user, { session: false }); self.callback(null, req); }
function logIn() { req.logIn(user, function(err) { self.callback(err, req); }) }
(expect, subject, value) => { if (!subject.handle || !subject.set) { // This check is from the lib/application file in express @ 4.10.2. // If we get inside here, we have something that is not an express app // https://github.com/strongloop/express/blob/661435256384165bb656cb7b6046b4138ca24c9e/lib/application.js#L186 subject = require('express')().use(subject); expect.subjectOutput = function() { this.text('express middleware'); }; } else { expect.subjectOutput = function() { this.text('express app'); }; } const missingProperties = Object.keys(value).filter( key => key !== 'request' && key !== 'response' ); if (missingProperties.length > 0) { throw new Error( 'Property "' + missingProperties[0] + '" does not exist' ); } const context = {}; const nextCalls = []; value = _.extend({}, value); const requestProperties = typeof value.request === 'string' ? { url: value.request } : _.extend({}, value.request); let requestBody = requestProperties.body; let requestForm = requestProperties.form; const httpRequest = new messy.HttpRequest({ method: requestProperties.method, url: requestProperties.url || '/', protocolName: 'HTTP', protocolVersion: requestProperties.httpVersion || '1.1', headers: requestProperties.headers, unchunkedBody: requestProperties.unchunkedBody, rawBody: requestProperties.rawBody }); function updateHttpRequestBody(requestBody) { if (Buffer.isBuffer(requestBody)) { httpRequest.unchunkedBody = requestBody; } else { // string or non-Buffer object (implies JSON) httpRequest.body = requestBody; } } if (requestForm) { if (typeof requestForm !== 'string') { requestForm = qs.stringify(requestForm); } requestBody = requestForm; if (!httpRequest.headers.has('Content-Type')) { httpRequest.headers.set( 'Content-Type', 'application/x-www-form-urlencoded' ); } } if (typeof requestBody !== 'undefined') { updateHttpRequestBody(requestBody); } else if ( 'unchunkedBody' in requestProperties || 'rawBody' in requestProperties ) { requestBody = httpRequest.body; } delete value.request; delete requestProperties.method; delete requestProperties.url; delete requestProperties.httpVersion; delete requestProperties.headers; delete requestProperties.body; delete requestProperties.unchunkedBody; delete requestProperties.rawBody; httpRequest.method = httpRequest.method || 'GET'; if ( httpRequest.encrypted && typeof requestProperties.https === 'undefined' ) { // Warn if conflicting? requestProperties.https = true; } if (requestProperties.formData) { if (requestBody) { throw new Error( 'unexpected-express: The "body" and "formData" options are not supported together' ); } requestBody = new FormData(); Object.keys(requestProperties.formData).forEach(name => { let value = requestProperties.formData[name]; let options = {}; if (isStream.readable(value) && value.path) { options.filename = value.path; } else if (typeof value === 'object' && !Buffer.isBuffer(value)) { options = _.extend({}, value); value = options.value; delete options.value; if (options.fileName) { options.filename = options.fileName; delete options.fileName; } } requestBody.append(name, value, options); }); delete requestProperties.formData; } if (typeof requestProperties.query !== 'undefined') { if ( typeof requestProperties.query === 'object' && requestProperties.query ) { const stringifiedQueryString = qs.stringify( requestProperties.query ); if (stringifiedQueryString) { httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') + stringifiedQueryString; } } else { httpRequest.url += (httpRequest.url.indexOf('?') === -1 ? '?' : '&') + String(requestProperties.query); } delete requestProperties.query; } const responseProperties = value.response; delete value.response; let expectedResponseProperties; if (typeof responseProperties === 'number') { expectedResponseProperties = { statusCode: responseProperties }; } else if ( typeof responseProperties === 'string' || Buffer.isBuffer(responseProperties) ) { expectedResponseProperties = { body: responseProperties }; } else if (Array.isArray(responseProperties)) { throw new Error( 'unexpected-express: Response object must be a number, string, buffer or object.' ); } else { if ( responseProperties && hasKeys(responseProperties) && !validateResponseProperties(responseProperties) ) { throw new Error( 'unexpected-express: Response object specification incomplete.' ); } expectedResponseProperties = _.extend({}, responseProperties); } const expectedMetadata = _.extend( {}, _.pick(expectedResponseProperties, metadataPropertyNames), _.pick(value, metadataPropertyNames) ); expectedResponseProperties = _.omit( expectedResponseProperties, metadataPropertyNames ); const missingResponseProperties = Object.keys( expectedResponseProperties ).filter(key => responsePropertyNames.indexOf(key) === -1); if (missingResponseProperties.length > 0) { throw new Error( 'Property "' + missingResponseProperties[0] + '" does not exist on the response object.' ); } const req = new http.IncomingMessage({ destroy() { requestDestroyed = true; } }); if (typeof requestBody !== 'undefined') { httpRequest.headers.set('Transfer-Encoding', 'chunked'); if (requestBody.pipe) { if ( requestBody.constructor && requestBody.constructor.name === 'FormData' ) { if (!httpRequest.headers.has('Content-Type')) { httpRequest.headers.set( 'Content-Type', 'multipart/form-data; boundary=' + requestBody.getBoundary() ); // form-data pauses its streams by default for some reason: setImmediate(() => { requestBody.resume(); }); } } const requestBodyChunks = []; requestBody .on('data', chunk => { if (!Buffer.isBuffer(chunk)) { chunk = Buffer.from(chunk, 'utf-8'); } requestBodyChunks.push(chunk); req.push(chunk); }) .on('end', () => { updateHttpRequestBody(Buffer.concat(requestBodyChunks)); req.push(null); }) .on('error', err => { req.emit('error', err); }); } else { if ( typeof requestBody === 'object' && !Buffer.isBuffer(requestBody) ) { if (!httpRequest.headers.has('Content-Type')) { httpRequest.headers.set('Content-Type', 'application/json'); } } if ( !httpRequest.headers.has('Content-Length') && !httpRequest.headers.has('Transfer-Encoding') ) { httpRequest.headers.set( 'Content-Length', String(requestBody.length) ); } setImmediate(() => { // To work around nodejs v0.10.x issue with old-style streams, see also https://github.com/stream-utils/raw-body/pull/34 req.push(httpRequest.unchunkedBody); req.push(null); }); } } else { req.push(null); } // Make req.connection.setTimeout a no-op so that req.setTimeout doesn't break // in this mocked state: req.connection.setTimeout = () => {}; req.httpVersion = httpRequest.protocolVersion; const matchProtocolVersion = String(httpRequest.protocolVersion).match( /^(\d+)(?:\.(\d+))$/ ); if (matchProtocolVersion) { req.httpVersionMajor = parseInt(matchProtocolVersion[1], 10); req.httpVersionMinor = matchProtocolVersion[2] ? parseInt(matchProtocolVersion[2], 10) : 0; } req.connection.encrypted = !!requestProperties.https; delete requestProperties.https; req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1'; delete requestProperties.ip; delete requestProperties.remoteAddress; req.headers = {}; httpRequest.headers.getNames().forEach(headerName => { const headerNameLowerCase = headerName.toLowerCase(); if (headerNameLowerCase === 'set-cookie') { req.headers[headerNameLowerCase] = [].concat( httpRequest.headers.getAll(headerName) ); } else { req.headers[headerNameLowerCase] = httpRequest.headers .getAll(headerName) .join(', '); } }); req.method = httpRequest.method; req.url = httpRequest.requestLine.url; _.extend(req, requestProperties); const res = new http.ServerResponse(req); _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals delete requestProperties.res; res.locals = res.locals || {}; const rawResponseChunks = []; res.assignSocket(new stream.Writable()); res.connection._write = (chunk, encoding, cb) => { rawResponseChunks.push(chunk); cb(); }; let isAsync = false; let isDestroyed = false; var requestDestroyed = false; let done = false; let errorPassedToNext; res.connection.destroy = () => { isDestroyed = true; }; setImmediate(() => { isAsync = true; }); return expect .promise((resolve, reject) => { ['write', 'end', 'destroy'].forEach(methodName => { const orig = res[methodName]; res[methodName] = function(chunk, encoding) { const returnValue = orig.apply(this, arguments); isDestroyed = isDestroyed || methodName === 'destroy'; if (methodName === 'end' || methodName === 'destroy') { resolve(); } // Don't attempt to implement backpressure, since we're buffering the entire response anyway. if (methodName !== 'write') { return returnValue; } }; }); subject(req, res, function(err, _req, _res, _next) { nextCalls.push(arguments); if (nextCalls.length > 1) { if (done) { if (err) { throw err; } else { throw new Error('next called more than once'); } } else { // Will be reported as a failure later return; } } errorPassedToNext = err; if (typeof err === 'number') { const statusCode = err; err = new Error('' + statusCode); err.statusCode = statusCode; } res.statusCode = (err && (err.statusCode || err.status)) || 404; resolve(); }); }) .then(() => { if (res.connection._writableState.corked > 0) { // Wait for the connection to become uncorked before proceeding const originalUncork = res.connection.uncork; return expect.promise((resolve, reject) => { res.connection.uncork = function() { const returnValue = originalUncork.apply(this, arguments); if (res.connection._writableState.corked === 0) { resolve(); } return returnValue; }; }); } }) .then(() => { _.extend(context, { req, res, httpRequest, metadata: { strictAsync: isAsync, errorPassedToNext: false, isDestroyed, requestDestroyed, nextCalled: nextCalls.length > 0, locals: res.locals, url: req.url } }); if ( errorPassedToNext && errorPassedToNext.statusCode && !res.headersSent ) { res.writeHead(errorPassedToNext.statusCode); } if (!res.headersSent) { // Make sure that the already set headers get flushed: res.writeHead(404); } const httpResponse = (context.httpResponse = new messy.HttpResponse( rawResponseChunks.length > 0 ? Buffer.concat(rawResponseChunks) : res._header )); if (typeof httpResponse.rawBody === 'undefined') { httpResponse.rawBody = Buffer.from([]); } httpResponse.statusCode = httpResponse.statusCode || res.statusCode; if (errorPassedToNext) { context.metadata.errorPassedToNext = errorPassedToNext; if (typeof expectedMetadata.errorPassedToNext !== 'undefined') { if (expectedMetadata.errorPassedToNext === true) { context.metadata.errorPassedToNext = true; } else if ( typeof expectedMetadata.errorPassedToNext === 'string' ) { context.metadata.errorPassedToNext = errorPassedToNext.message; } else { context.metadata.errorPassedToNext = errorPassedToNext; } } else if (typeof errorPassedToNext.statusCode === 'number') { // FIXME if (!httpResponse.headers.get('Content-Type')) { httpResponse.headers.set('Content-Type', 'text/plain'); httpResponse.body = errorPassedToNext.stack; } } else { throw errorPassedToNext; } } context.httpExchange = new messy.HttpExchange({ request: context.httpRequest, response: context.httpResponse }); const promiseByKey = { httpExchange: expect.promise(() => expect(context.httpExchange, 'to satisfy', { response: expectedResponseProperties }) ), metadata: {} }; Object.keys(expectedMetadata).forEach(key => { promiseByKey.metadata[key] = expect.promise(() => topLevelExpect( context.metadata[key], 'to satisfy', expectedMetadata[key] ) ); }); return expect.promise.settle(promiseByKey).then(promises => { if (promises.some(promise => promise.isRejected())) { expect.fail({ diff(output) { if (promiseByKey.httpExchange.isRejected()) { output.append( promiseByKey.httpExchange.reason().getDiff(output) ); } else { output.appendInspected(context.httpExchange); } Object.keys(promiseByKey.metadata).forEach(key => { if (promiseByKey.metadata[key].isRejected()) { output.nl().annotationBlock(function() { this.text(key) .text(':') .sp() .append( promiseByKey.metadata[key] .reason() .getErrorMessage(output) ); }); } }); return output; } }); } }); }) .then(() => { if (nextCalls.length > 1) { throw new Error('next called more than once'); } done = true; // Tell the next function that subsequent calls should cause an exception to be thrown return context; }); }
setImmediate(() => { // To work around nodejs v0.10.x issue with old-style streams, see also https://github.com/stream-utils/raw-body/pull/34 req.push(httpRequest.unchunkedBody); req.push(null); });
.on('error', err => { req.emit('error', err); });
function ProxiedIncomingMessage(socket) { http.IncomingMessage.call(this, socket); stream.PassThrough.call(this); }
.on('end', () => { updateHttpRequestBody(Buffer.concat(requestBodyChunks)); req.push(null); })