├── .gitignore ├── .travis.yml ├── collaborators.md ├── index.js ├── package.json ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "5.0.0" 5 | 6 | # Using faster container based build environment. 7 | 8 | sudo: false 9 | 10 | before_script: 11 | - npm install -g npm 12 | 13 | script: 14 | - npm test 15 | -------------------------------------------------------------------------------- /collaborators.md: -------------------------------------------------------------------------------- 1 | ## Collaborators 2 | 3 | request-stream is only possible due to the excellent work of the following collaborators: 4 | 5 | 6 | 7 | 8 |
maxogdenGitHub/maxogden
mafintoshGitHub/mafintosh
karissaGitHub/karissa
9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var https = require('https') 3 | var parseUrl = require('url').parse 4 | var xtend = require('xtend') 5 | var debug = require('debug')('request-stream') 6 | 7 | module.exports = requester('GET') 8 | module.exports.get = module.exports 9 | module.exports.post = requester('POST') 10 | module.exports.put = requester('PUT') 11 | module.exports.del = requester('DELETE') 12 | module.exports.head = requester('HEAD') 13 | 14 | function requester (method) { 15 | return function httpRequest (url, opts, cb) { 16 | if (typeof opts === 'function') return httpRequest(url, {}, opts) 17 | if (typeof url === 'undefined') throw new Error('Must supply url') 18 | if (typeof cb === 'undefined') throw new Error('Must supply callback') 19 | if (opts.method) method = opts.method 20 | if (!/:\/\//.test(url)) url = 'http://' + url 21 | 22 | var parsed = parseUrl(url) 23 | var host = parsed.hostname 24 | var port = parsed.port 25 | var path = parsed.path 26 | var mod = parsed.protocol === 'https:' ? https : http 27 | var called = false 28 | 29 | var defaults = { 30 | method: method, 31 | host: host, 32 | path: path, 33 | port: port, 34 | maxRedirects: 10 35 | } 36 | 37 | var reqOpts = xtend(defaults, opts) 38 | var req = mod.request(reqOpts) 39 | debug('request %j', reqOpts) 40 | req.on('error', done) 41 | req.on('response', function (res) { 42 | var redir = shouldRedirect(req, res) 43 | debug('response', res.statusCode) 44 | if (redir) { 45 | if (opts.followRedirects === false) return done(null, res) 46 | if (opts.maxRedirects === 0) { 47 | return done(new Error('Max redirects exceeded'), res) 48 | } 49 | debug('redirect', redir) 50 | reqOpts.path = redir 51 | reqOpts.maxRedirects-- 52 | httpRequest(redir, reqOpts, done) 53 | } 54 | else done(null, res) 55 | }) 56 | 57 | if (method === 'GET' || method === 'HEAD' || method === 'DELETE') req.end() 58 | return req 59 | 60 | function done (err, res) { 61 | if (called) return 62 | called = true 63 | if (err) return cb(err, res) 64 | cb(null, res) 65 | } 66 | } 67 | } 68 | 69 | function shouldRedirect (req, res) { 70 | var redirectTo = false 71 | var loc = res.headers['location'] 72 | var code = res.statusCode 73 | if (code >= 300 && code < 400 && typeof loc !== 'undefined') { 74 | var mtd = req.method 75 | if (mtd === 'PATCH' || mtd === 'PUT' || mtd === 'POST' || mtd === 'DELETE') return false 76 | redirectTo = loc 77 | } 78 | return redirectTo 79 | } 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "request-stream", 3 | "version": "1.2.2", 4 | "description": "Extremely minimal wrapper around node core http/https to conveniently get request and response streams", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard && node test.js" 8 | }, 9 | "author": "max ogden", 10 | "license": "ISC", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/maxogden/request-stream.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/maxogden/request-stream/issues" 17 | }, 18 | "homepage": "https://github.com/maxogden/request-stream#readme", 19 | "dependencies": { 20 | "debug": "^2.2.0", 21 | "xtend": "^4.0.1" 22 | }, 23 | "devDependencies": { 24 | "standard": "^5.4.1", 25 | "tape": "^4.2.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # request-stream 2 | 3 | Extremely minimal wrapper around node core `http`/`https` to get request and response streams. 4 | 5 | [![travis][travis-image]][travis-url] 6 | 7 | [travis-image]: https://img.shields.io/travis/maxogden/request-stream.svg?style=flat 8 | [travis-url]: https://travis-ci.org/maxogden/request-stream 9 | 10 | ### why 11 | 12 | This module is under 100 lines of code, total file + folder size including `node_modules` is 150kb, compared to 4.7mb for the popular `request` module. The benefit and drawback of this module is that it doesn't have that many features. Use it if you want something low level and lightweight. 13 | 14 | ## usage 15 | 16 | ### `var request = require('request-stream')` 17 | 18 | You can now use `request` to make new requests 19 | 20 | ### `var req = request(url, [opts], callback)` 21 | 22 | `req` is a writable stream. Data written to it will be written to the request upload body. For requests wtih an upload body you **must call** `req.end()` for the request to finish, even if you write no data to it. If you don't call `req.end()` you will never receive a response. 23 | 24 | `url` is the HTTP url for this request 25 | 26 | `opts` are request options: 27 | 28 | - **method** - default `GET` - sets HTTP method 29 | - **host** - defaults to the hostname from `url` 30 | - **path** - defaults to the path from `url` 31 | - **port** - defaults to the port from `url` 32 | - **maxRedirects** - defaults to `10` 33 | - **followRedirects** - defaults to `true` 34 | 35 | `callback` is called with `(err, res)`. If there was no `err`, `res` will be a readable stream of the response data. 36 | 37 | In the event that `maxRedirects` was exceeded you will receive both an `err` and the `res` of the last redirect. 38 | 39 | Both `req` and `res` are the unmodified [`http.ClientRequest`](https://nodejs.org/api/http.html#http_class_http_clientrequest) and [`http.IncomingMessage`](https://nodejs.org/api/http.html#http_http_incomingmessage) 40 | 41 | ### convenience methods 42 | 43 | These set the `method` option for you 44 | 45 | #### request.get(url, opts, cb) 46 | 47 | #### request.post(url, opts, cb) 48 | 49 | #### request.put(url, opts, cb) 50 | 51 | #### request.delete(url, opts, cb) 52 | 53 | #### request.head(url, opts, cb) 54 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | var test = require('tape') 3 | var concat = require('concat-stream') 4 | var r = require('./') 5 | var url = 'http://localhost:8080' 6 | 7 | serverTest('get', function (t, done) { 8 | r(url, function (err, res) { 9 | t.notOk(err, 'no err') 10 | res.pipe(concat(function (bod) { 11 | t.equal(bod.toString(), 'root', 'root') 12 | done() 13 | })) 14 | }) 15 | }) 16 | 17 | serverTest('redirect', function (t, done) { 18 | r(url + '/redirect', function (err, res) { 19 | t.notOk(err, 'no err') 20 | res.pipe(concat(function (bod) { 21 | t.equal(bod.toString(), 'root', 'redirected to root') 22 | done() 23 | })) 24 | }) 25 | }) 26 | 27 | serverTest('infinite redirect', function (t, done) { 28 | r(url + '/infiniteredirect', function (err, res) { 29 | t.ok(err.message.indexOf('redirects exceeded') > -1, 'got err') 30 | res.pipe(concat(function (bod) { 31 | t.equal(bod.toString(), 'infiniteredirect', 'infiniteredirect') 32 | done() 33 | })) 34 | }) 35 | }) 36 | 37 | serverTest('maxRedirects', function (t, done) { 38 | r(url + '/infiniteredirect', {maxRedirects: 3}, function (err, res) { 39 | t.ok(err.message.indexOf('redirects exceeded') > -1, 'got err') 40 | res.pipe(concat(function (bod) { 41 | t.equal(bod.toString(), 'infiniteredirect', 'infiniteredirect') 42 | done() 43 | })) 44 | }) 45 | }) 46 | 47 | serverTest('followRedirects false', function (t, done) { 48 | r(url + '/infiniteredirect', {followRedirects: false}, function (err, res) { 49 | t.notOk(err, 'no err') 50 | res.pipe(concat(function (bod) { 51 | t.equal(bod.toString(), 'infiniteredirect', 'infiniteredirect') 52 | done() 53 | })) 54 | }) 55 | }) 56 | 57 | function serverTest (msg, cb) { 58 | test(msg, function (t) { 59 | startServer(function (server) { 60 | cb(t, function () { 61 | server.close(function () { 62 | t.end() 63 | }) 64 | }) 65 | }) 66 | }) 67 | } 68 | 69 | function startServer (cb) { 70 | var server = http.createServer(function (req, res) { 71 | if (req.url === '/') return res.end('root') 72 | if (req.url === '/redirect') { 73 | res.statusCode = 302 74 | res.setHeader('location', '/') 75 | return res.end('redirect') 76 | } 77 | if (req.url === '/infiniteredirect') { 78 | res.statusCode = 302 79 | res.setHeader('location', '/infiniteredirect') 80 | return res.end('infiniteredirect') 81 | } 82 | res.end() 83 | }) 84 | server.listen(8080, function (err) { 85 | if (err) throw err 86 | cb(server) 87 | }) 88 | } 89 | --------------------------------------------------------------------------------