├── .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 |
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 |
--------------------------------------------------------------------------------