mirror of
				https://github.com/therootcompany/request.js.git
				synced 2024-11-16 17:28:58 +00:00 
			
		
		
		
	Compare commits
	
		
			13 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bb30d5acf6 | |||
| 5b539deb7b | |||
| 0a2e7afa76 | |||
| ba60df7eab | |||
| bc4f6e59c0 | |||
| 3842ee1d61 | |||
| 9518cb970b | |||
| 2e9a643c0f | |||
| dcd41a33d0 | |||
| 5f5e0b6066 | |||
| ed2bab931f | |||
| a95d003ed5 | |||
| 5149bc9dcb | 
							
								
								
									
										22
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								.jshintrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| { | ||||
|   "browser": true, | ||||
|   "node": true, | ||||
|   "esversion": 11, | ||||
|   "curly": true, | ||||
|   "sub": true, | ||||
|   "bitwise": true, | ||||
|   "eqeqeq": true, | ||||
|   "forin": true, | ||||
|   "freeze": true, | ||||
|   "immed": true, | ||||
|   "latedef": "nofunc", | ||||
|   "nonbsp": true, | ||||
|   "nonew": true, | ||||
|   "plusplus": true, | ||||
|   "undef": true, | ||||
|   "unused": "vars", | ||||
|   "strict": true, | ||||
|   "maxdepth": 3, | ||||
|   "maxstatements": 100, | ||||
|   "maxcomplexity": 40 | ||||
| } | ||||
							
								
								
									
										6
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| # CHANGELOG | ||||
| 
 | ||||
| ## v1.8.0 | ||||
| 
 | ||||
| -   add `resp.ok` - same as WHATWG fetch `resp.ok = (resp.statusCode >= 200 && resp.statusCode < 300)` | ||||
| -   add `resp.stream.body()` to populate `resp.body` rather than (or perhaps in addition to) continuing to stream (useful for error handling) | ||||
							
								
								
									
										30
									
								
								EXTRA.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								EXTRA.md
									
									
									
									
									
								
							| @ -3,6 +3,36 @@ | ||||
| There are some niche features of @root/request which are beyond the request.js | ||||
| compatibility. | ||||
| 
 | ||||
| ## async/await & Promises | ||||
| 
 | ||||
| The differences in async support are explained in [README.md](/README.md), up near the top. | ||||
| 
 | ||||
| If you're familiar with Promises (and async/await), then it's pretty self-explanatory. | ||||
| 
 | ||||
| ## ok | ||||
| 
 | ||||
| Just like WHATWG `fetch`, we have `resp.ok`: | ||||
| 
 | ||||
| ```js | ||||
| let resp = await request({ | ||||
|     url: 'https://example.com' | ||||
| }).then(mustOk); | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| function mustOk(resp) { | ||||
|     if (!resp.ok) { | ||||
|         // handle error | ||||
|         throw new Error('BAD RESPONSE'); | ||||
|     } | ||||
|     return resp; | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## streams | ||||
| 
 | ||||
| The differences in stream support are explained in [README.md](/README.md), up near the top. | ||||
| 
 | ||||
| ## userAgent | ||||
| 
 | ||||
| There's a default User-Agent string describing the version of @root/request, node.js, and the OS. | ||||
|  | ||||
							
								
								
									
										60
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								README.md
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| # [µRequest](https://git.rootprojects.org/root/request.js) | a [Root](https://rootprojects.org) project | ||||
| # [@root/request](https://git.rootprojects.org/root/request.js) | a [Root](https://rootprojects.org) project | ||||
| 
 | ||||
| > Minimalist HTTP client | ||||
| 
 | ||||
| @ -10,7 +10,7 @@ Written from scratch, with zero-dependencies. | ||||
| 
 | ||||
| ## Super simple to use | ||||
| 
 | ||||
| µRequest is designed to be a drop-in replacement for request. It supports HTTPS and follows redirects by default. | ||||
| @root/request is designed to be a drop-in replacement for request. It also supports Promises and async/await by default, enhanced stream support, and a few other things as mentioned below. | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save @root/request | ||||
| @ -48,14 +48,29 @@ In order to keep this library lightweight, performant, and keep the code easy to | ||||
| read, the streaming behavior is **_slightly different_** from that of | ||||
| `request.js`. | ||||
| 
 | ||||
| ```diff | ||||
| -var request = require('request'); | ||||
| +var request = require('@root/request'); | ||||
| 
 | ||||
| -var stream = request({ url, headers }); | ||||
| +var stream = await request({ url, headers }); | ||||
| 
 | ||||
|  let attachment = await new MailgunAPI.Attachment({ | ||||
|    data: stream | ||||
|  }) | ||||
| ``` | ||||
| 
 | ||||
| Example: | ||||
| 
 | ||||
| ```js | ||||
| var request = require('@root/request'); | ||||
| 
 | ||||
| var resp = await request({ | ||||
|     url: 'http://www.google.com', | ||||
|     stream: true | ||||
|     stream: true // true | 'filename.ext' | stream.Writable | ||||
| }); | ||||
| 
 | ||||
| // 'resp' itself is a ReadableStream | ||||
| resp.on('data', function () { | ||||
|     // got some data | ||||
| }); | ||||
| @ -64,8 +79,8 @@ resp.on('end', function () { | ||||
|     // the data has ended | ||||
| }); | ||||
| 
 | ||||
| // resp.stream is a Promise that is resolved when the read stream is destroyed | ||||
| await resp.stream; | ||||
| // 'resp.stream' is a Promise that is resolved when the read stream is destroyed | ||||
| await resp.stream; // returns `undefined` | ||||
| console.log('Done'); | ||||
| ``` | ||||
| 
 | ||||
| @ -90,17 +105,42 @@ request({ | ||||
| }); | ||||
| ``` | ||||
| 
 | ||||
| Also, `await resp.stream.body()` can be used to get back the full body (the same as if you didn't use the `stream` option: | ||||
| 
 | ||||
| ```js | ||||
| let resp = await request({ | ||||
|     url: 'http://www.google.com', | ||||
|     stream: true | ||||
| }); | ||||
| if (!resp.ok) { | ||||
|     await resp.stream.body(); | ||||
|     console.error(resp.body); | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ## Table of contents | ||||
| 
 | ||||
| -   [Extra Features](/EXTRA.md) | ||||
| -   [Forms](#forms) | ||||
| -   [HTTP Authentication](#http-authentication) | ||||
| -   [Custom HTTP Headers](#custom-http-headers) | ||||
| -   [Unix Domain Sockets](#unix-domain-sockets) | ||||
| -   [**All Available Options**](#requestoptions-callback) | ||||
| 
 | ||||
| ## Extra Features | ||||
| 
 | ||||
| The following are features that the original `request` did not have, but have been added for convenience in `@root/request`. | ||||
| 
 | ||||
| -   Support for `async`/`await` & `Promise`s (as explained above) | ||||
| -   `request({ userAgent: 'my-api/1.1' })` (for building API clients) | ||||
| -   `resp.ok` (just like `fetch`) | ||||
| -   `resp.stream` (see above) | ||||
| 
 | ||||
| See [EXTRA.md](/EXTRA.md) | ||||
| 
 | ||||
| ## Forms | ||||
| 
 | ||||
| `urequest` supports `application/x-www-form-urlencoded` and `multipart/form-data` form uploads. | ||||
| `@root/request` supports `application/x-www-form-urlencoded` and `multipart/form-data` form uploads. | ||||
| 
 | ||||
| <!-- For `multipart/related` refer to the `multipart` API. --> | ||||
| 
 | ||||
| @ -126,12 +166,12 @@ request.post('http://service.com/upload').form({key:'value'}) | ||||
| 
 | ||||
| #### multipart/form-data (Multipart Form Uploads) | ||||
| 
 | ||||
| For `multipart/form-data` we use the [form-data](https://github.com/form-data/form-data) library by [@felixge](https://github.com/felixge). For the most cases, you can pass your upload form data via the `formData` option. | ||||
| For `multipart/form-data` we use the [form-data](https://github.com/form-data/form-data/tree/v2.5.1) library by [@felixge](https://github.com/felixge). For the most cases, you can pass your upload form data via the `formData` option. | ||||
| 
 | ||||
| To use `form-data`, you must install it separately: | ||||
| 
 | ||||
| ```bash | ||||
| npm install --save form-data@2 | ||||
| npm install --save form-data@2.x | ||||
| ``` | ||||
| 
 | ||||
| ```js | ||||
| @ -295,7 +335,7 @@ request(options, callback); | ||||
| 
 | ||||
| ## UNIX Domain Sockets | ||||
| 
 | ||||
| `urequest` supports making requests to [UNIX Domain Sockets](https://en.wikipedia.org/wiki/Unix_domain_socket). To make one, use the following URL scheme: | ||||
| `@root/request` supports making requests to [UNIX Domain Sockets](https://en.wikipedia.org/wiki/Unix_domain_socket). To make one, use the following URL scheme: | ||||
| 
 | ||||
| ```js | ||||
| /* Pattern */ 'http://unix:SOCKET:PATH'; | ||||
| @ -411,7 +451,7 @@ These HTTP method convenience functions act just like `request()` but with a def | ||||
| 
 | ||||
| There are at least <!--three--> two ways to debug the operation of `request`: | ||||
| 
 | ||||
| 1. Launch the node process like `NODE_DEBUG=urequest node script.js` | ||||
| 1. Launch the node process like `NODE_DEBUG=@root/request node script.js` | ||||
|    (`lib,request,otherlib` works too). | ||||
| 
 | ||||
| 2. Set `require('@root/request').debug = true` at any time (this does the same thing | ||||
|  | ||||
							
								
								
									
										233
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								index.js
									
									
									
									
									
								
							| @ -66,6 +66,102 @@ function toJSONifier(keys) { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| function setupPipe(resp, opts) { | ||||
|     // make the response await-able
 | ||||
|     var resolve; | ||||
|     var reject; | ||||
|     var p = new Promise(function (_resolve, _reject) { | ||||
|         resolve = _resolve; | ||||
|         reject = _reject; | ||||
|     }); | ||||
| 
 | ||||
|     // or an existing write stream
 | ||||
|     if ('function' === typeof opts.stream.pipe) { | ||||
|         if (opts.debug) { | ||||
|             console.debug('[@root/request] stream piped'); | ||||
|         } | ||||
|         resp.pipe(opts.stream); | ||||
|     } | ||||
|     resp.once('error', function (e) { | ||||
|         if (opts.debug) { | ||||
|             console.debug("[@root/request] stream 'error'"); | ||||
|             console.error(e.stack); | ||||
|         } | ||||
|         resp.destroy(); | ||||
|         if ('function' === opts.stream.destroy) { | ||||
|             opts.stream.destroy(e); | ||||
|         } | ||||
|         reject(e); | ||||
|     }); | ||||
|     resp.once('end', function () { | ||||
|         if (opts.debug) { | ||||
|             console.debug("[@root/request] stream 'end'"); | ||||
|         } | ||||
|         if ('function' === opts.stream.destroy) { | ||||
|             opts.stream.end(); | ||||
|             // this will close the stream (i.e. sync to disk)
 | ||||
|             opts.stream.destroy(); | ||||
|         } | ||||
|     }); | ||||
|     resp.once('close', function () { | ||||
|         if (opts.debug) { | ||||
|             console.debug("[@root/request] stream 'close'"); | ||||
|         } | ||||
|         resolve(); | ||||
|     }); | ||||
|     return p; | ||||
| } | ||||
| 
 | ||||
| function handleResponse(resp, opts, cb) { | ||||
|     // body can be buffer, string, or json
 | ||||
|     if (null === opts.encoding) { | ||||
|         resp._body = []; | ||||
|     } else { | ||||
|         resp.body = ''; | ||||
|     } | ||||
|     resp._bodyLength = 0; | ||||
|     resp.on('readable', function () { | ||||
|         var chunk; | ||||
|         while ((chunk = resp.read())) { | ||||
|             if ('string' === typeof resp.body) { | ||||
|                 resp.body += chunk.toString(opts.encoding); | ||||
|             } else { | ||||
|                 resp._body.push(chunk); | ||||
|                 resp._bodyLength += chunk.length; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     resp.once('end', function () { | ||||
|         if ('string' !== typeof resp.body) { | ||||
|             if (1 === resp._body.length) { | ||||
|                 resp.body = resp._body[0]; | ||||
|             } else { | ||||
|                 resp.body = Buffer.concat(resp._body, resp._bodyLength); | ||||
|             } | ||||
|             resp._body = null; | ||||
|         } | ||||
|         if (opts.json && 'string' === typeof resp.body) { | ||||
|             // TODO I would parse based on Content-Type
 | ||||
|             // but request.js doesn't do that.
 | ||||
|             try { | ||||
|                 resp.body = JSON.parse(resp.body); | ||||
|             } catch (e) { | ||||
|                 // ignore
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         debug('\n[urequest] resp.toJSON():'); | ||||
|         if (module.exports.debug) { | ||||
|             debug(resp.toJSON()); | ||||
|         } | ||||
|         if (opts.debug) { | ||||
|             console.debug('[@root/request] Response Body:'); | ||||
|             console.debug(resp.body); | ||||
|         } | ||||
|         cb(null, resp, resp.body); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function setDefaults(defs) { | ||||
|     defs = defs || {}; | ||||
| 
 | ||||
| @ -76,6 +172,19 @@ function setDefaults(defs) { | ||||
|         var req; | ||||
|         var finalOpts = {}; | ||||
| 
 | ||||
|         // allow specifying a file
 | ||||
|         if ('string' === typeof opts.stream) { | ||||
|             if (opts.debug) { | ||||
|                 console.debug('[@root/request] creating file write stream'); | ||||
|             } | ||||
|             try { | ||||
|                 opts.stream = fs.createWriteStream(opts.stream); | ||||
|             } catch (e) { | ||||
|                 cb(e); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         function onResponse(resp) { | ||||
|             var followRedirect; | ||||
| 
 | ||||
| @ -87,9 +196,16 @@ function setDefaults(defs) { | ||||
|             }); | ||||
|             followRedirect = opts.followRedirect; | ||||
| 
 | ||||
|             // copied from WHATWG fetch
 | ||||
|             resp.ok = false; | ||||
|             if (resp.statusCode >= 200 && resp.statusCode < 300) { | ||||
|                 resp.ok = true; | ||||
|             } | ||||
| 
 | ||||
|             resp.toJSON = toJSONifier([ | ||||
|                 'statusCode', | ||||
|                 'body', | ||||
|                 'ok', | ||||
|                 'headers', | ||||
|                 'request' | ||||
|             ]); | ||||
| @ -143,109 +259,18 @@ function setDefaults(defs) { | ||||
|             } | ||||
| 
 | ||||
|             if (opts.stream) { | ||||
|                 // make the response await-able
 | ||||
|                 var resolve; | ||||
|                 var reject; | ||||
|                 resp.stream = new Promise(function (_resolve, _reject) { | ||||
|                     resolve = _resolve; | ||||
|                     reject = _reject; | ||||
|                 }); | ||||
| 
 | ||||
|                 // allow specifying a file
 | ||||
|                 if ('string' === typeof opts.stream) { | ||||
|                     try { | ||||
|                         if (opts.debug) { | ||||
|                             console.debug( | ||||
|                                 '[@root/request] file write stream created' | ||||
|                             ); | ||||
|                         } | ||||
|                         opts.stream = fs.createWriteStream(opts.stream); | ||||
|                     } catch (e) { | ||||
|                         cb(e); | ||||
|                     } | ||||
|                 } | ||||
|                 // or an existing write stream
 | ||||
|                 if ('function' === typeof opts.stream.pipe) { | ||||
|                     if (opts.debug) { | ||||
|                         console.debug('[@root/request] stream piped'); | ||||
|                     } | ||||
|                     resp.pipe(opts.stream); | ||||
|                 } | ||||
|                 resp.on('error', function (e) { | ||||
|                     if (opts.debug) { | ||||
|                         console.debug("[@root/request] stream 'error'"); | ||||
|                         console.error(e.stack); | ||||
|                     } | ||||
|                     resp.destroy(); | ||||
|                     if ('function' === opts.stream.destroy) { | ||||
|                         opts.stream.destroy(e); | ||||
|                     } | ||||
|                     reject(e); | ||||
|                 }); | ||||
|                 resp.on('end', function () { | ||||
|                     if (opts.debug) { | ||||
|                         console.debug("[@root/request] stream 'end'"); | ||||
|                     } | ||||
|                     if ('function' === opts.stream.destroy) { | ||||
|                         opts.stream.end(); | ||||
|                         // this will close the stream (i.e. sync to disk)
 | ||||
|                         opts.stream.destroy(); | ||||
|                     } | ||||
|                 }); | ||||
|                 resp.on('close', function () { | ||||
|                     if (opts.debug) { | ||||
|                         console.debug("[@root/request] stream 'close'"); | ||||
|                     } | ||||
|                     resolve(); | ||||
|                 }); | ||||
|                 // and in all cases, return the stream
 | ||||
|                 resp.stream = setupPipe(resp, opts); | ||||
|                 // can be string, buffer, or json... why not an async function too?
 | ||||
|                 resp.stream.body = async function () { | ||||
|                     handleResponse(resp, opts, cb); | ||||
|                     await resp.stream; | ||||
|                     return resp.body; | ||||
|                 }; | ||||
|                 cb(null, resp); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (null === opts.encoding) { | ||||
|                 resp._body = []; | ||||
|             } else { | ||||
|                 resp.body = ''; | ||||
|             } | ||||
|             resp._bodyLength = 0; | ||||
|             resp.on('data', function (chunk) { | ||||
|                 if ('string' === typeof resp.body) { | ||||
|                     resp.body += chunk.toString(opts.encoding); | ||||
|                 } else { | ||||
|                     resp._body.push(chunk); | ||||
|                     resp._bodyLength += chunk.length; | ||||
|                 } | ||||
|             }); | ||||
|             resp.on('end', function () { | ||||
|                 if ('string' !== typeof resp.body) { | ||||
|                     if (1 === resp._body.length) { | ||||
|                         resp.body = resp._body[0]; | ||||
|                     } else { | ||||
|                         resp.body = Buffer.concat(resp._body, resp._bodyLength); | ||||
|                     } | ||||
|                     resp._body = null; | ||||
|                 } | ||||
|                 if (opts.json && 'string' === typeof resp.body) { | ||||
|                     // TODO I would parse based on Content-Type
 | ||||
|                     // but request.js doesn't do that.
 | ||||
|                     try { | ||||
|                         resp.body = JSON.parse(resp.body); | ||||
|                     } catch (e) { | ||||
|                         // ignore
 | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 debug('\n[urequest] resp.toJSON():'); | ||||
|                 if (module.exports.debug) { | ||||
|                     debug(resp.toJSON()); | ||||
|                 } | ||||
|                 if (opts.debug) { | ||||
|                     console.debug('[@root/request] Response Body:'); | ||||
|                     console.debug(resp.body); | ||||
|                 } | ||||
|                 cb(null, resp, resp.body); | ||||
|             }); | ||||
|             handleResponse(resp, opts, cb); | ||||
|         } | ||||
| 
 | ||||
|         var _body; | ||||
| @ -255,7 +280,7 @@ function setDefaults(defs) { | ||||
|         var requester; | ||||
| 
 | ||||
|         if (opts.body) { | ||||
|             if (true === opts.json) { | ||||
|             if (true === opts.json && 'string' !== typeof opts.body) { | ||||
|                 _body = JSON.stringify(opts.body); | ||||
|             } else { | ||||
|                 _body = opts.body; | ||||
| @ -299,7 +324,9 @@ function setDefaults(defs) { | ||||
|             'timeout', | ||||
|             'setHost' | ||||
|         ].forEach(function (key) { | ||||
|             finalOpts[key] = opts.uri[key]; | ||||
|             if (key in opts) { | ||||
|                 finalOpts[key] = opts[key]; | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         finalOpts.method = opts.method; | ||||
| @ -437,7 +464,7 @@ function setDefaults(defs) { | ||||
|             } | ||||
|         } | ||||
|         req = requester.request(finalOpts, onResponse); | ||||
|         req.on('error', cb); | ||||
|         req.once('error', cb); | ||||
| 
 | ||||
|         if (_body) { | ||||
|             debug("\n[urequest] '" + finalOpts.method + "' (request) body"); | ||||
| @ -445,7 +472,7 @@ function setDefaults(defs) { | ||||
|             if ('function' === typeof _body.pipe) { | ||||
|                 // used for chunked encoding
 | ||||
|                 _body.pipe(req); | ||||
|                 _body.on('error', function (err) { | ||||
|                 _body.once('error', function (err) { | ||||
|                     // https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
 | ||||
|                     // if the Readable stream emits an error during processing,
 | ||||
|                     // the Writable destination is not closed automatically
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| { | ||||
|     "name": "@root/request", | ||||
|     "version": "1.7.0", | ||||
|     "version": "1.8.2", | ||||
|     "lockfileVersion": 1 | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@root/request", | ||||
|     "version": "1.7.0", | ||||
|     "version": "1.8.2", | ||||
|     "description": "A lightweight, zero-dependency drop-in replacement for request", | ||||
|     "main": "index.js", | ||||
|     "files": [ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user