├── .gitignore ├── test ├── lib │ ├── gen-cert.sh │ ├── cert.pem │ ├── key.pem │ └── index.js └── basic.js ├── .travis.yml ├── package.json ├── index.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/lib/gen-cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # instructions from http://www.selfsignedcertificate.com/ 4 | openssl genrsa -out key.pem 2048 5 | openssl req -new -x509 -key key.pem -out cert.pem -days 3560 -subj /CN=localhost 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "iojs" 5 | - "0.12" 6 | - "0.10" 7 | 8 | env: 9 | - REQUEST_VERSION=2.30.0 10 | - REQUEST_VERSION=2.40.0 11 | - REQUEST_VERSION=2.50.0 12 | - REQUEST_VERSION=2.52.0 13 | - REQUEST_VERSION=2.53.0 14 | - REQUEST_VERSION=2.54.0 15 | - REQUEST_VERSION=2.55.0 16 | 17 | before_script: 18 | - rm -r node_modules/request/ 19 | - npm install request@$REQUEST_VERSION 20 | 21 | sudo: false 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "request-debug", 3 | "author" : "James Nylen ", 4 | "description" : "Library to assist with debugging HTTP(s) requests made by the request module.", 5 | "version" : "0.2.0", 6 | "repository" : { 7 | "type" : "git", 8 | "url" : "https://github.com/request/request-debug" 9 | }, 10 | "bugs" : { 11 | "url" : "https://github.com/request/request-debug/issues" 12 | }, 13 | "license" : "MIT", 14 | "keywords" : [ 15 | "request", 16 | "debug", 17 | "http", 18 | "https", 19 | "headers" 20 | ], 21 | "scripts" : { 22 | "test" : "node node_modules/.bin/mocha" 23 | }, 24 | "main" : "index.js", 25 | "devDependencies" : { 26 | "detect-engine" : "~1.0.2", 27 | "express" : "~4.8.6", 28 | "mocha" : "~1.21.4", 29 | "passport" : "~0.2.0", 30 | "passport-http" : "~0.2.2", 31 | "request" : "~2.50.0", 32 | "should" : "~4.0.4" 33 | }, 34 | "dependencies" : { 35 | "stringify-clone": "^1.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/lib/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAeOgAwIBAgIJALhUr+7OkYw8MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCWxvY2FsaG9zdDAeFw0xNDA4MjgyMTE2NDBaFw0yNDA1MjcyMTE2NDBaMBQx 4 | EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBAM59YnTCpDUjppxvxCOyD3BbfxOWjnLxzQSyojFCejujCkI6oKp5WSQwD4go 6 | bRx1NJS6Of8b2QFJhoDWSfiiaMqsDzkd5ogC9gA1APW5U9f8reZvTelkQ9iQTMGH 7 | MG4xAImZxYnwUmUikpJCFhNUHfhK1xd5lVWkEVBzCoUbdz/zur0QGujc/txRtuRH 8 | EABO/xXskw6q8nsV9mo8H4717EWKzhune+G9T+rwvhoSclhZB3SE6I9btAKcVd1G 9 | gqYs4sNOgRJNrnYxnYOBnllHQb+Hk26lNo/34H57ZmP0T7KsTXeXvF7x/0E/5cmo 10 | RPQErnltNWiUt3Ur+6s545j/eWsCAwEAAaNQME4wHQYDVR0OBBYEFO2ZA8yuO38m 11 | PG5tWjuqGK+oyhp5MB8GA1UdIwQYMBaAFO2ZA8yuO38mPG5tWjuqGK+oyhp5MAwG 12 | A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALZ9vHmbXbRIqK5DaNw68ffM 13 | Oav0Kc8BSjkeQBwBzKUt3XtJCNeFvlse37SJX96AoEDgqrudKbntLkz8ljuxOd9J 14 | qeaXD29fRtBZpdTpscYrhztyhUPvZq7rtUjTeDnIfMExqON1FU+JRDpEYhRyLSm7 15 | MCHaAiKn9GYjMO5BiuG1OkW4aVYPeMmlxJmk8hbXbw04LKL9B7Fe9uiTgVcLvq/X 16 | k7T58FS7EV6khJ4JhGMKiHphGJuyWA3Pr/FFLhNQoD7xuN8oIgqY9OGFuPHe8LIZ 17 | H03Fk39YPCabyvdoHbXj1LgatbOOsUAPWNzzg3Qsq+H82CKbqbeJWR+WGiUXGU0= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /test/lib/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAzn1idMKkNSOmnG/EI7IPcFt/E5aOcvHNBLKiMUJ6O6MKQjqg 3 | qnlZJDAPiChtHHU0lLo5/xvZAUmGgNZJ+KJoyqwPOR3miAL2ADUA9blT1/yt5m9N 4 | 6WRD2JBMwYcwbjEAiZnFifBSZSKSkkIWE1Qd+ErXF3mVVaQRUHMKhRt3P/O6vRAa 5 | 6Nz+3FG25EcQAE7/FeyTDqryexX2ajwfjvXsRYrOG6d74b1P6vC+GhJyWFkHdITo 6 | j1u0ApxV3UaCpiziw06BEk2udjGdg4GeWUdBv4eTbqU2j/fgfntmY/RPsqxNd5e8 7 | XvH/QT/lyahE9ASueW01aJS3dSv7qznjmP95awIDAQABAoIBAQDJdHTRG3DfU0fi 8 | yfwfI0KCyRGCx9l0dUojcjBhRM1CT7/be51ylG+OqcENgvtoz0Sccc/Rf5bI9bXf 9 | DrIefD0mtpUnkW9vdQwSiaPFGKmfSKtU7XSi5qp2p9nq2f07FF3E4ZxjBm8ssn0z 10 | anxlISUYL9QH2BwE8MvfpE04LeK7JUtBc8SVbmPVyMNSw2jr1PEOx5jP5XJm3wxF 11 | +ZEAnH/GVt8bpdlFIkJuqN1Wfx9DWlgUWv4jrAXHCCbC+sTQr+XUp+7xnD0VMN9c 12 | vU7ag7BBxfmq5QFddlXk2mKiX9XJo1lIZWJvw0mnR+LO+jJbXlqA/J5u9VD5Z815 13 | oZsWn/sJAoGBAO3XFpxWGP494RtOhPh6e9l/uhMBviCkr7jmFQ3N5rqw+DqSzSUP 14 | pR61NsFSmUhJbrhHtbgw4slRi2QedVcU2DMWHH43BcMPCIoAWieQrae7ZTz40yE4 15 | dkEYHPpmMwE7dIBp5JjRoR3yuffyO/qh3p4LqpPnHaKsFuGHj5vrIT1VAoGBAN5B 16 | gn4sEFibJ4N+9R8tVT9M4oBnpHtGEm+TanIW2Nw/Z6Esar25C2TNqYH6CSBgCL6b 17 | QBQch2F5AEPnNZ5mOhQskt571U66WRFYA6zuke1ep82dRH3KoBD1mKKoAvHFffXO 18 | LTIlNLAEeXPr/ZdqhmungsW3yjmGjlBpzsSUz9u/AoGBAJj3av1linGKDstnNrIw 19 | 5JLASUaMKmIISVQb9pKISMrtotjZLcOWSlZzqiGwJBGP2PwAculh15ovLf1YcZNu 20 | Ppass+qhLL2FX7KSA2nK7UUhiz0ps3B8ReIbxhdtv4QdXxJwCKwYtwzx3ZWQX+8U 21 | SH9RdYXmcPL8x0KJ010uXpR5AoGASYl7V6vUrLyJUmTLTcl+DzetKbchIPiKWQh5 22 | i84rapRAAX/kwWlcb7nsf+ju5BtiSU6s4PqfgIy8nbr+bq58QPiOj7aBWU8m8Soh 23 | 7THybouHEe0bIFPOqk3YdtkIQF6L0qypH5JX4HVaihRnD5Zba3s9NZYd/vKaW9jm 24 | ZPyn2ZkCgYAaldWVE4rwXcIi26P28sQzbeHhnAlvwbLjtufOpFHZvC3NldtBCXeb 25 | 2zbumyiWPTfGzYqJj9j5GZKpDVu5PYfLLONZbp5wtWxvVWdQb/6+69rMBrKEytyu 26 | WMAlb5+ldXzzgOj+4J0rMevJLUllOquaX+j4gFGZrUI3eZ+LoDW53w== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var clone = require('stringify-clone'); 2 | 3 | var debugId = 0 4 | 5 | module.exports = exports = function(request, log) { 6 | log = log || exports.log 7 | 8 | var proto 9 | if (request.Request) { 10 | proto = request.Request.prototype 11 | } else if (request.get && request.post) { 12 | // The object returned by request.defaults() doesn't include the 13 | // Request property, so do this horrible thing to get at it. Per 14 | // Wikipedia, port 4 is unassigned. 15 | var req = request('http://localhost:4').on('error', function() { }) 16 | proto = req.constructor.prototype 17 | } else { 18 | throw new Error( 19 | "Pass the object returned by require('request') to this function.") 20 | } 21 | 22 | if (!proto._initBeforeDebug) { 23 | proto._initBeforeDebug = proto.init 24 | 25 | proto.init = function() { 26 | if (!this._debugId) { 27 | 28 | this.on('request', function(req) { 29 | var data = { 30 | debugId : this._debugId, 31 | uri : this.uri.href, 32 | method : this.method, 33 | headers : clone(this.headers) 34 | } 35 | if (this.body) { 36 | data.body = this.body.toString('utf8') 37 | } 38 | log('request', data, this) 39 | 40 | }).on('response', function(res) { 41 | if (this.callback) { 42 | // callback specified, request will buffer the body for 43 | // us, so wait until the complete event to do anything 44 | } else { 45 | // cannot get body since no callback specified 46 | log('response', { 47 | debugId : this._debugId, 48 | headers : clone(res.headers), 49 | statusCode : res.statusCode 50 | }, this) 51 | } 52 | 53 | }).on('complete', function(res, body) { 54 | if (this.callback) { 55 | log('response', { 56 | debugId : this._debugId, 57 | headers : clone(res.headers), 58 | statusCode : res.statusCode, 59 | body : res.body 60 | }, this) 61 | } 62 | 63 | }).on('redirect', function() { 64 | var type = (this.response.statusCode == 401 ? 'auth' : 'redirect') 65 | log(type, { 66 | debugId : this._debugId, 67 | statusCode : this.response.statusCode, 68 | headers : clone(this.response.headers), 69 | uri : this.uri.href 70 | }, this) 71 | }) 72 | 73 | this._debugId = ++debugId 74 | } 75 | 76 | return proto._initBeforeDebug.apply(this, arguments) 77 | } 78 | } 79 | 80 | if (!request.stopDebugging) { 81 | request.stopDebugging = function() { 82 | proto.init = proto._initBeforeDebug 83 | delete proto._initBeforeDebug 84 | } 85 | } 86 | } 87 | 88 | exports.log = function(type, data, r) { 89 | var toLog = {} 90 | toLog[type] = data 91 | console.error(toLog) 92 | } 93 | -------------------------------------------------------------------------------- /test/lib/index.js: -------------------------------------------------------------------------------- 1 | var engine = require('detect-engine'), 2 | express = require('express'), 3 | fs = require('fs'), 4 | http = require('http'), 5 | https = require('https'), 6 | mocha = require('mocha'), 7 | passport = require('passport'), 8 | DigestStrategy = require('passport-http').DigestStrategy, 9 | path = require('path'), 10 | should = require('should'), 11 | util = require('util') 12 | 13 | var app, 14 | ports = { 15 | http : 8480, 16 | https : 8443 17 | } 18 | 19 | exports.ports = ports 20 | exports.requests = [] 21 | exports.urls = {} 22 | exports.debugId = 0 23 | 24 | for (var proto in ports) { 25 | exports.urls[proto] = util.format( 26 | '%s://localhost:%d', 27 | proto, 28 | ports[proto]) 29 | } 30 | 31 | exports.enableDebugging = function(request) { 32 | // enable debugging 33 | require('../..')(request, function(type, data, r) { 34 | var obj = {} 35 | obj[type] = data 36 | exports.requests.push(obj) 37 | if (typeof r._initBeforeDebug != 'function') { 38 | throw new Error('Expected a Request instance here.') 39 | } 40 | }) 41 | } 42 | 43 | exports.clearRequests = function() { 44 | exports.requests = [] 45 | exports.debugId++ 46 | } 47 | 48 | var fixHeader = { 49 | date : function(val) { 50 | return '' 51 | }, 52 | etag : function(val) { 53 | return val.split('"')[0] + '""' 54 | }, 55 | connection : function(val) { 56 | return val.replace(/^(close|keep-alive)$/, '') 57 | }, 58 | authorization : function(val) { 59 | var arr = val.split(', ') 60 | if (arr.length > 1) { 61 | val = util.format( 62 | '%s <+%s>', 63 | arr[0], 64 | arr.slice(1).map(function(v) { 65 | return v.split('=')[0] 66 | }).join(',')) 67 | } 68 | return val 69 | }, 70 | referer : function(val) { 71 | return null 72 | }, 73 | 'content-type' : function(val) { 74 | return val.replace(/^application\/x-www-form-urlencoded(; charset=utf-8)?$/, '') 75 | }, 76 | 'content-length' : function(val, obj) { 77 | if (engine == 'iojs' && obj.statusCode == 401) { 78 | // io.js sends content-length here, Node does not 79 | return null 80 | } else { 81 | return val 82 | } 83 | } 84 | } 85 | fixHeader['www-authenticate'] = fixHeader.authorization 86 | 87 | exports.fixVariableHeaders = function() { 88 | exports.requests.forEach(function(req) { 89 | for (var type in req) { 90 | for (var header in req[type].headers) { 91 | if (fixHeader[header]) { 92 | var fixed = fixHeader[header](req[type].headers[header], req[type]) 93 | if (fixed === null) { 94 | delete req[type].headers[header] 95 | } else { 96 | req[type].headers[header] = fixed 97 | } 98 | } 99 | } 100 | } 101 | }) 102 | } 103 | 104 | exports.startServers = function() { 105 | passport.use(new DigestStrategy( 106 | { qop : 'auth' }, 107 | function(user, done) { 108 | return done(null, 'admin', 'mypass') 109 | } 110 | )) 111 | 112 | app = express() 113 | 114 | app.use(passport.initialize()) 115 | 116 | function handleRequest(req, res) { 117 | if (req.params.level == 'bottom') { 118 | if (req.header('accept') == 'application/json') { 119 | res.json({ key : 'value' }) 120 | } else { 121 | res.send('Request OK') 122 | } 123 | return 124 | } 125 | var level = (req.params.level == 'top' ? 'middle' : 'bottom') 126 | if (req.params.proto && req.params.proto != req.protocol) { 127 | res.redirect(exports.urls[req.params.proto] + '/' + level) 128 | } else { 129 | res.redirect('/' + level) 130 | } 131 | } 132 | 133 | var auth = passport.authenticate('digest', { session : false }) 134 | app.get('/auth/:level/:proto?', auth, handleRequest) 135 | 136 | app.get('/:level/:proto?', handleRequest) 137 | 138 | http.createServer(app).listen(ports.http) 139 | 140 | https.createServer({ 141 | key : fs.readFileSync(path.join(__dirname, 'key.pem')), 142 | cert : fs.readFileSync(path.join(__dirname, 'cert.pem')) 143 | }, app).listen(ports.https) 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # request-debug [![Build status](https://img.shields.io/travis/request/request-debug.svg?style=flat)](https://travis-ci.org/request/request-debug) [![npm package](http://img.shields.io/npm/v/request-debug.svg?style=flat)](https://www.npmjs.org/package/request-debug) 2 | 3 | This Node.js module provides an easy way to monitor HTTP(S) requests performed 4 | by the [`request` module](https://github.com/request/request), and their 5 | responses from external servers. 6 | 7 | ## Usage 8 | 9 | Basic usage is to require the module and call it, passing in the object 10 | returned by `require('request')`: 11 | 12 | ```js 13 | var request = require('request'); 14 | 15 | require('request-debug')(request); 16 | ``` 17 | 18 | This will set up event handlers on every request performed with the `request` 19 | variable from this point. 20 | 21 | You can also specify a function to handle request or response data: 22 | 23 | ```js 24 | require('request-debug')(request, function(type, data, r) { 25 | // put your request or response handling logic here 26 | }); 27 | ``` 28 | 29 | If you specify your own handling function, `r` will be the `Request` instance 30 | that generated the event, and `type` will be one of the following values: 31 | 32 | - **request** - Headers were sent to the server and will be included as 33 | `data.headers`. `data.body` may also be present for POST requests. 34 | 35 | - **response** - Headers were received from the server and will be included as 36 | `data.headers`. Note that `request` only buffers the response body if a 37 | callback was given, so it will only be available as `data.body` if the 38 | initial call to `request` included a callback. 39 | 40 | - **redirect** - A redirect status code (*HTTP 3xx*) was received. The `data` 41 | object will have properties `statusCode`, `headers`, and `uri` (the address 42 | of the next request). 43 | 44 | - **auth** - A *HTTP 401 Unathorized* response was received. Internally, 45 | `request` handles this like a redirect, so the same properties will be 46 | available on the `data` object. 47 | 48 | You can use the `data.debugId` parameter to match up requests with their 49 | responses and other events. 50 | 51 | The default handling function writes the data to *stderr* in Node's JSON-like 52 | object display format. See the example below for more details. 53 | 54 | To disable debugging, call `request.stopDebugging()` (this function only exists 55 | if debugging has already been enabled). Any requests that are in progress when 56 | `stopDebugging()` is called will still generate debug events. 57 | 58 | ## Example 59 | 60 | ```js 61 | var request = require('request'); 62 | 63 | require('request-debug')(request); 64 | 65 | // digest.php is example 2 from: 66 | // http://php.net/manual/en/features.http-auth.php 67 | 68 | request({ 69 | uri : 'http://nylen.tv/digest.php', 70 | auth : { 71 | user : 'admin', 72 | pass : 'mypass', 73 | sendImmediately : false 74 | }, 75 | rejectUnauthorized : false, 76 | }, function(err, res, body) { 77 | console.log('REQUEST RESULTS:', err, res.statusCode, body); 78 | }); 79 | ``` 80 | 81 | Unless you provide your own function as the second parameter to the 82 | `request-debug` call, this will produce console output similar to the 83 | following: 84 | 85 | ```js 86 | { request: 87 | { debugId: 1, 88 | uri: 'http://nylen.tv/digest.php', 89 | method: 'GET', 90 | headers: { host: 'nylen.tv' } } } 91 | { auth: 92 | { debugId: 1, 93 | statusCode: 401, 94 | headers: 95 | { date: 'Mon, 20 Oct 2014 03:34:58 GMT', 96 | server: 'Apache/2.4.6 (Debian)', 97 | 'x-powered-by': 'PHP/5.5.6-1', 98 | 'www-authenticate': 'Digest realm="Restricted area",qop="auth",nonce="544482e2556d9",opaque="cdce8a5c95a1427d74df7acbf41c9ce0"', 99 | 'content-length': '39', 100 | 'keep-alive': 'timeout=5, max=100', 101 | connection: 'Keep-Alive', 102 | 'content-type': 'text/html' }, 103 | uri: 'http://nylen.tv/digest.php' } } 104 | { request: 105 | { debugId: 1, 106 | uri: 'http://nylen.tv/digest.php', 107 | method: 'GET', 108 | headers: 109 | { authorization: 'Digest username="admin", realm="Restricted area", nonce="544482e2556d9", uri="/digest.php", qop=auth, response="e833c7fa52e8d42fae3ca784b96dfd38", nc=00000001, cnonce="ab6ff3dd95a0449e990a6c8465a6bb26", opaque="cdce8a5c95a1427d74df7acbf41c9ce0"', 110 | host: 'nylen.tv' } } } 111 | { response: 112 | { debugId: 1, 113 | headers: 114 | { date: 'Mon, 20 Oct 2014 03:34:58 GMT', 115 | server: 'Apache/2.4.6 (Debian)', 116 | 'x-powered-by': 'PHP/5.5.6-1', 117 | 'content-length': '27', 118 | 'keep-alive': 'timeout=5, max=100', 119 | connection: 'Keep-Alive', 120 | 'content-type': 'text/html' }, 121 | statusCode: 200, 122 | body: 'You are logged in as: admin' } } 123 | REQUEST RESULTS: null 200 You are logged in as: admin 124 | ``` 125 | 126 | ## Compatibility 127 | 128 | Tested with Node.js versions 0.8.x, 0.10.x, and 0.11.x on Travis, and a bunch 129 | of different `request` versions. 130 | 131 | Does not work with `request` versions older than 2.22.0 (July 2013). Tests 132 | don't start passing until version 2.28.0 (December 2013). 133 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | var engine = require('detect-engine'), 2 | lib = require('./lib'), 3 | mocha = require('mocha'), 4 | request = require('request'), 5 | should = require('should') 6 | 7 | describe('request-debug', function() { 8 | var proto = request.Request.prototype 9 | 10 | before(function() { 11 | lib.enableDebugging(request) 12 | lib.startServers() 13 | 14 | request = request.defaults({ 15 | headers : { 16 | host : 'localhost' 17 | }, 18 | rejectUnauthorized : false 19 | }) 20 | }) 21 | 22 | beforeEach(function() { 23 | lib.clearRequests() 24 | }) 25 | 26 | function maybeTransferEncodingChunked(obj) { 27 | if (engine == 'node') { 28 | // Node sends 'Transfer-Encoding: chunked' here, io.js does not 29 | obj['transfer-encoding'] = 'chunked' 30 | } 31 | return obj 32 | } 33 | 34 | it('should capture a normal request', function(done) { 35 | request(lib.urls.http + '/bottom', function(err, res, body) { 36 | should.not.exist(err) 37 | lib.fixVariableHeaders() 38 | lib.requests.should.eql([ 39 | { 40 | request : { 41 | debugId : lib.debugId, 42 | uri : lib.urls.http + '/bottom', 43 | method : 'GET', 44 | headers : { 45 | host : 'localhost' 46 | } 47 | } 48 | }, { 49 | response : { 50 | debugId : lib.debugId, 51 | headers : { 52 | connection : '', 53 | 'content-length' : '10', 54 | 'content-type' : 'text/html; charset=utf-8', 55 | date : '', 56 | etag : 'W/""', 57 | 'x-powered-by' : 'Express' 58 | }, 59 | statusCode : 200, 60 | body : 'Request OK' 61 | } 62 | } 63 | ]) 64 | done() 65 | }) 66 | }) 67 | 68 | it('should capture a request with no callback', function(done) { 69 | var r = request(lib.urls.http + '/bottom') 70 | r.on('complete', function(res) { 71 | lib.fixVariableHeaders() 72 | lib.requests.should.eql([ 73 | { 74 | request : { 75 | debugId : lib.debugId, 76 | uri : lib.urls.http + '/bottom', 77 | method : 'GET', 78 | headers : { 79 | host : 'localhost' 80 | } 81 | } 82 | }, { 83 | response : { 84 | debugId : lib.debugId, 85 | headers : { 86 | connection : '', 87 | 'content-length' : '10', 88 | 'content-type' : 'text/html; charset=utf-8', 89 | date : '', 90 | etag : 'W/""', 91 | 'x-powered-by' : 'Express' 92 | }, 93 | statusCode : 200 94 | } 95 | } 96 | ]) 97 | done() 98 | }) 99 | }) 100 | 101 | it('should capture a redirect', function(done) { 102 | request(lib.urls.http + '/middle', function(err, res, body) { 103 | should.not.exist(err) 104 | lib.fixVariableHeaders() 105 | lib.requests.should.eql([ 106 | { 107 | request : { 108 | debugId : lib.debugId, 109 | uri : lib.urls.http + '/middle', 110 | method : 'GET', 111 | headers : { 112 | host : 'localhost' 113 | } 114 | } 115 | }, { 116 | redirect : { 117 | debugId : lib.debugId, 118 | headers : { 119 | connection : '', 120 | 'content-length' : '41', 121 | 'content-type' : 'text/plain; charset=utf-8', 122 | date : '', 123 | location : '/bottom', 124 | vary : 'Accept', 125 | 'x-powered-by' : 'Express', 126 | }, 127 | statusCode : 302, 128 | uri : lib.urls.http + '/bottom' 129 | } 130 | }, { 131 | request : { 132 | debugId : lib.debugId, 133 | uri : lib.urls.http + '/bottom', 134 | method : 'GET', 135 | headers : { 136 | host : 'localhost:' + lib.ports.http 137 | } 138 | } 139 | }, { 140 | response : { 141 | debugId : lib.debugId, 142 | headers : { 143 | connection : '', 144 | 'content-length' : '10', 145 | 'content-type' : 'text/html; charset=utf-8', 146 | date : '', 147 | etag : 'W/""', 148 | 'x-powered-by' : 'Express' 149 | }, 150 | statusCode : 200, 151 | body : 'Request OK' 152 | } 153 | } 154 | ]) 155 | done() 156 | }) 157 | }) 158 | 159 | it('should capture a cross-protocol redirect', function(done) { 160 | request(lib.urls.https + '/middle/http', function(err, res, body) { 161 | should.not.exist(err) 162 | lib.fixVariableHeaders() 163 | lib.requests.should.eql([ 164 | { 165 | request : { 166 | debugId : lib.debugId, 167 | uri : lib.urls.https + '/middle/http', 168 | method : 'GET', 169 | headers : { 170 | host : 'localhost' 171 | } 172 | } 173 | }, { 174 | redirect : { 175 | debugId : lib.debugId, 176 | headers : { 177 | connection : '', 178 | 'content-length' : '62', 179 | 'content-type' : 'text/plain; charset=utf-8', 180 | date : '', 181 | location : lib.urls.http + '/bottom', 182 | vary : 'Accept', 183 | 'x-powered-by' : 'Express', 184 | }, 185 | statusCode : 302, 186 | uri : lib.urls.http + '/bottom' 187 | } 188 | }, { 189 | request : { 190 | debugId : lib.debugId, 191 | uri : lib.urls.http + '/bottom', 192 | method : 'GET', 193 | headers : { 194 | host : 'localhost:' + lib.ports.http 195 | } 196 | } 197 | }, { 198 | response : { 199 | debugId : lib.debugId, 200 | headers : { 201 | connection : '', 202 | 'content-length' : '10', 203 | 'content-type' : 'text/html; charset=utf-8', 204 | date : '', 205 | etag : 'W/""', 206 | 'x-powered-by' : 'Express' 207 | }, 208 | statusCode : 200, 209 | body : 'Request OK' 210 | } 211 | } 212 | ]) 213 | done() 214 | }) 215 | }) 216 | 217 | it('should capture an auth challenge', function(done) { 218 | request(lib.urls.http + '/auth/bottom', { 219 | auth : { 220 | user : 'admin', 221 | pass : 'mypass', 222 | sendImmediately : false 223 | } 224 | }, function(err, res, body) { 225 | should.not.exist(err) 226 | lib.fixVariableHeaders() 227 | lib.requests.should.eql([ 228 | { 229 | request : { 230 | debugId : lib.debugId, 231 | uri : lib.urls.http + '/auth/bottom', 232 | method : 'GET', 233 | headers : { 234 | host : 'localhost' 235 | } 236 | } 237 | }, { 238 | auth : { 239 | debugId : lib.debugId, 240 | headers : maybeTransferEncodingChunked({ 241 | connection : '', 242 | date : '', 243 | 'www-authenticate' : 'Digest realm="Users" <+nonce,qop>', 244 | 'x-powered-by' : 'Express', 245 | }), 246 | statusCode : 401, 247 | uri : lib.urls.http + '/auth/bottom' 248 | } 249 | }, { 250 | request : { 251 | debugId : lib.debugId, 252 | uri : lib.urls.http + '/auth/bottom', 253 | method : 'GET', 254 | headers : { 255 | authorization : 'Digest username="admin" <+realm,nonce,uri,qop,response,nc,cnonce>', 256 | host : 'localhost' 257 | } 258 | } 259 | }, { 260 | response : { 261 | debugId : lib.debugId, 262 | headers : { 263 | connection : '', 264 | 'content-length' : '10', 265 | 'content-type' : 'text/html; charset=utf-8', 266 | date : '', 267 | etag : 'W/""', 268 | 'x-powered-by' : 'Express' 269 | }, 270 | statusCode : 200, 271 | body : 'Request OK' 272 | } 273 | } 274 | ]) 275 | done() 276 | }) 277 | }) 278 | 279 | it('should capture a complicated redirect', function(done) { 280 | request(lib.urls.https + '/auth/top/http', { 281 | auth : { 282 | user : 'admin', 283 | pass : 'mypass', 284 | sendImmediately : false 285 | } 286 | }, function(err, res, body) { 287 | should.not.exist(err) 288 | lib.fixVariableHeaders() 289 | lib.requests.should.eql([ 290 | { 291 | request : { 292 | debugId : lib.debugId, 293 | uri : lib.urls.https + '/auth/top/http', 294 | method : 'GET', 295 | headers : { 296 | host : 'localhost' 297 | } 298 | } 299 | }, { 300 | auth : { 301 | debugId : lib.debugId, 302 | headers : maybeTransferEncodingChunked({ 303 | connection : '', 304 | date : '', 305 | 'www-authenticate' : 'Digest realm="Users" <+nonce,qop>', 306 | 'x-powered-by' : 'Express', 307 | }), 308 | statusCode : 401, 309 | uri : lib.urls.https + '/auth/top/http' 310 | } 311 | }, { 312 | request : { 313 | debugId : lib.debugId, 314 | uri : lib.urls.https + '/auth/top/http', 315 | method : 'GET', 316 | headers : { 317 | authorization : 'Digest username="admin" <+realm,nonce,uri,qop,response,nc,cnonce>', 318 | host : 'localhost' 319 | } 320 | } 321 | }, { 322 | redirect : { 323 | debugId : lib.debugId, 324 | headers : { 325 | connection : '', 326 | 'content-length' : '62', 327 | 'content-type' : 'text/plain; charset=utf-8', 328 | date : '', 329 | location : lib.urls.http + '/middle', 330 | vary : 'Accept', 331 | 'x-powered-by' : 'Express', 332 | }, 333 | statusCode : 302, 334 | uri : lib.urls.http + '/middle' 335 | } 336 | }, { 337 | request : { 338 | debugId : lib.debugId, 339 | uri : lib.urls.http + '/middle', 340 | method : 'GET', 341 | headers : { 342 | authorization : 'Digest username="admin" <+realm,nonce,uri,qop,response,nc,cnonce>', 343 | host : 'localhost:' + lib.ports.http 344 | } 345 | } 346 | }, { 347 | redirect : { 348 | debugId : lib.debugId, 349 | headers : { 350 | connection : '', 351 | 'content-length' : '41', 352 | 'content-type' : 'text/plain; charset=utf-8', 353 | date : '', 354 | location : '/bottom', 355 | vary : 'Accept', 356 | 'x-powered-by' : 'Express', 357 | }, 358 | statusCode : 302, 359 | uri : lib.urls.http + '/bottom' 360 | } 361 | }, { 362 | request : { 363 | debugId : lib.debugId, 364 | uri : lib.urls.http + '/bottom', 365 | method : 'GET', 366 | headers : { 367 | authorization : 'Digest username="admin" <+realm,nonce,uri,qop,response,nc,cnonce>', 368 | host : 'localhost:' + lib.ports.http 369 | } 370 | } 371 | }, { 372 | response : { 373 | debugId : lib.debugId, 374 | headers : { 375 | connection : '', 376 | 'content-length' : '10', 377 | 'content-type' : 'text/html; charset=utf-8', 378 | date : '', 379 | etag : 'W/""', 380 | 'x-powered-by' : 'Express' 381 | }, 382 | statusCode : 200, 383 | body : 'Request OK' 384 | } 385 | } 386 | ]) 387 | done() 388 | }) 389 | }) 390 | 391 | it('should capture POST data and 404 responses', function(done) { 392 | request({ 393 | uri : lib.urls.http + '/bottom', 394 | method : 'POST', 395 | form : { 396 | formKey : 'formData' 397 | } 398 | }, function(err, res, body) { 399 | should.not.exist(err) 400 | lib.fixVariableHeaders() 401 | lib.requests.should.eql([ 402 | { 403 | request : { 404 | debugId : lib.debugId, 405 | uri : lib.urls.http + '/bottom', 406 | method : 'POST', 407 | headers : { 408 | host : 'localhost', 409 | 'content-length' : 16, 410 | 'content-type' : '' 411 | }, 412 | body : 'formKey=formData' 413 | } 414 | }, { 415 | response : { 416 | debugId : lib.debugId, 417 | headers : { 418 | connection : '', 419 | 'content-length' : '20', 420 | 'content-type' : 'text/html; charset=utf-8', 421 | date : '', 422 | 'x-powered-by' : 'Express' 423 | }, 424 | statusCode : 404, 425 | body : 'Cannot POST /bottom\n' 426 | } 427 | } 428 | ]) 429 | done() 430 | }) 431 | }) 432 | 433 | it('should capture JSON responses', function(done) { 434 | request({ 435 | uri : lib.urls.http + '/bottom', 436 | json : true 437 | }, function(err, res, body) { 438 | should.not.exist(err) 439 | lib.fixVariableHeaders() 440 | lib.requests.should.eql([ 441 | { 442 | request : { 443 | debugId : lib.debugId, 444 | uri : lib.urls.http + '/bottom', 445 | method : 'GET', 446 | headers : { 447 | accept : 'application/json', 448 | host : 'localhost' 449 | } 450 | } 451 | }, { 452 | response : { 453 | debugId : lib.debugId, 454 | headers : { 455 | connection : '', 456 | 'content-length' : '15', 457 | 'content-type' : 'application/json; charset=utf-8', 458 | date : '', 459 | etag : 'W/""', 460 | 'x-powered-by' : 'Express' 461 | }, 462 | statusCode : 200, 463 | body : { 464 | key : 'value' 465 | } 466 | } 467 | } 468 | ]) 469 | done() 470 | }) 471 | }) 472 | 473 | it('should work with the result of request.defaults()', function(done) { 474 | proto.should.have.property('_initBeforeDebug') 475 | proto.init = proto._initBeforeDebug 476 | delete proto._initBeforeDebug 477 | 478 | request = require('request').defaults({ 479 | headers : { 480 | host : 'localhost' 481 | }, 482 | }) 483 | 484 | lib.enableDebugging(request) 485 | 486 | request(lib.urls.http + '/bottom', function(err, res, body) { 487 | should.not.exist(err) 488 | lib.fixVariableHeaders() 489 | lib.requests.should.eql([ 490 | { 491 | request : { 492 | debugId : lib.debugId, 493 | uri : lib.urls.http + '/bottom', 494 | method : 'GET', 495 | headers : { 496 | host : 'localhost' 497 | } 498 | } 499 | }, { 500 | response : { 501 | debugId : lib.debugId, 502 | headers : { 503 | connection : '', 504 | 'content-length' : '10', 505 | 'content-type' : 'text/html; charset=utf-8', 506 | date : '', 507 | etag : 'W/""', 508 | 'x-powered-by' : 'Express' 509 | }, 510 | statusCode : 200, 511 | body : 'Request OK' 512 | } 513 | } 514 | ]) 515 | done() 516 | }) 517 | }) 518 | 519 | it('should not capture anything after stopDebugging()', function(done) { 520 | request.stopDebugging() 521 | request(lib.urls.http + '/bottom', function(err, res, body) { 522 | should.not.exist(err) 523 | lib.requests.should.eql([]) 524 | done() 525 | }) 526 | }) 527 | }) 528 | --------------------------------------------------------------------------------