Comparing version 0.0.2 to 0.0.3
{ | ||
"name": "get-it", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "Generic HTTP request library for node and browsers", | ||
@@ -16,4 +16,13 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"bundle": "npm run bundle:build && npm run bundle:build:all && npm run bundle:minify && npm run bundle:minify:all && npm run bundle:size", | ||
"bundle:analyze": "npm run compile && NODE_ENV=production DEBUG='' browserify --full-paths -t envify -g uglifyify lib/index.js | discify --open", | ||
"bundle:analyze:all": "npm run compile && NODE_ENV=production DEBUG='' browserify --full-paths -t envify -g uglifyify lib/bundle-all.js | discify --open", | ||
"bundle:build": "npm run compile && NODE_ENV=production DEBUG='' browserify -t envify -g uglifyify lib/index.js -o umd/get-it.js --standalone=getIt", | ||
"bundle:build:all": "npm run compile && NODE_ENV=production DEBUG='' browserify -t envify -g uglifyify lib/bundle-all.js -o umd/get-it-all.js --standalone=getIt", | ||
"bundle:minify": "uglifyjs -c -m -- umd/get-it.js > umd/get-it.min.js", | ||
"bundle:minify:all": "uglifyjs -c -m -- umd/get-it-all.js > umd/get-it-all.min.js", | ||
"bundle:size": "node -r babel-register src/scripts/print-bundle-size", | ||
"ci": "npm run coverage && npm run karma && npm run lint", | ||
"clean": "rimraf lib .nyc_output coverage npm-debug.log yarn-debug.log", | ||
"clean": "rimraf lib .nyc_output coverage npm-debug.log yarn-debug.log umd/*.js", | ||
"compile": "babel --source-maps --copy-files -d lib/ src/", | ||
"coverage": "nyc --reporter=html --reporter=lcov --reporter=text _mocha", | ||
@@ -34,4 +43,2 @@ "karma": "karma start", | ||
"@rexxars/timed-out": "^3.0.1", | ||
"any-observable": "^0.2.0", | ||
"any-promise": "^1.3.0", | ||
"create-error-class": "^3.0.2", | ||
@@ -54,13 +61,18 @@ "debug": "^2.3.3", | ||
"devDependencies": { | ||
"@sanity/observable": "^1.1.0", | ||
"babel-cli": "^6.18.0", | ||
"babel-loader": "^6.2.8", | ||
"babel-preset-es2015": "^6.18.0", | ||
"babel-preset-es2015-node4": "^2.1.0", | ||
"babel-register": "^6.18.0", | ||
"boxen": "^0.8.1", | ||
"browserify": "^13.1.1", | ||
"chai": "^3.5.0", | ||
"chai-as-promised": "^6.0.0", | ||
"chai-subset": "^1.3.0", | ||
"chalk": "^1.1.3", | ||
"disc": "^1.3.2", | ||
"envify": "^4.0.0", | ||
"es6-promise": "^4.0.5", | ||
"eslint": "^3.11.1", | ||
"eslint-config-sanity": "^2.0.1", | ||
"gzip-size": "^3.0.0", | ||
"karma": "^1.3.0", | ||
@@ -77,6 +89,9 @@ "karma-chrome-launcher": "^2.0.0", | ||
"nyc": "^10.0.0", | ||
"pinkie-promise": "^2.0.1", | ||
"pretty-bytes": "^4.0.2", | ||
"rimraf": "^2.5.4", | ||
"webpack": "^1.13.3" | ||
"uglify-js": "^2.7.5", | ||
"uglifyify": "^3.0.4", | ||
"webpack": "^1.14.0", | ||
"zen-observable": "^0.3.0" | ||
} | ||
} |
189
README.md
# get-it | ||
## Wanted features | ||
Generic HTTP request library for node and browsers (IE9 and newer) | ||
* [ ] Developer-friendly assertions that are stripped in production to reduce bundle size and performance | ||
* [ ] Authentication (basic) | ||
* [ ] Stream response middleware? | ||
* [x] Progress upload/download events as middleware (available on observable/eventemitter) | ||
* [x] All HTTP methods supported (obviously) | ||
* [x] Configurable number of retries + "should retry" handler | ||
* [x] Follow redirects (optional) up to limit | ||
* [x] Gzip unwrapping support in node (browser automatically handles this) | ||
* [x] Middleware-ish (like MJ's request lib) | ||
* [x] Node and browser support (XHR), with small browser bundle footprint | ||
* [x] Observable/promise/callback/eventemitter support as middleware (low-level by default) | ||
* [x] Parsing of JSON request/response payloads as middleware | ||
* [x] Send file/buffer/stuff as body, should just work | ||
* [x] Timeouts with errors that are catchable (connect/response as separate in node) | ||
## Motivation | ||
We wanted an HTTP request library that worked transparently in Node.js and browsers with a small browser bundle footprint. | ||
To be able to use the same library in a range of different applications with varying requirements, but still keep the bundle size down, we took inspiration from [http-client](https://github.com/mjackson/http-client) which cleverly composes functionality into the client. | ||
## Features | ||
Using a middleware approach, `get-it` has the following feature set: | ||
* Promise, observable and low-level event-emitter patterns | ||
* Automatic retrying with customizable number of attempts and filtering functionality | ||
* Cancellation of requests | ||
* Configurable connect/socket timeouts | ||
* Automatic parsing of JSON responses | ||
* Automatic stringifying of JSON request bodies | ||
* Automatic gzip unwrapping in Node | ||
* Automatically prepend base URL | ||
* Automatically follow redirects (configurable number of retries) | ||
* Upload/download progress events | ||
* Treat HTTP status codes >=400 as errors | ||
* Debug requests with environment variables/localStorage setting | ||
## Usage | ||
How `get-it` behaves depends on which middleware you've loaded, but common to all approaches is the setup process. | ||
```js | ||
// Require the core get-it package, which is used to generate a requester | ||
const getIt = require('get-it') | ||
// And require whatever middleware you want to use | ||
const base = require('get-it/lib/middleware/base') | ||
const jsonResponse = require('get-it/lib/middleware/jsonResponse') | ||
const promise = require('get-it/lib/middleware/promise') | ||
// Now compose the middleware you want to use | ||
const request = getIt([ | ||
base('https://api.your.service/v1'), | ||
jsonResponse() | ||
]) | ||
// You can also register middleware using `.use(middleware)` | ||
request.use(promise()) | ||
// Now you're ready to use the requester: | ||
request({url: '/projects'}) | ||
.then(projects => console.log(projects)) | ||
.catch(err => console.error(err)) | ||
``` | ||
In most larger projects, you'd probably make a `httpClient.js` or similar, where you would instantiate the requester and export it for other modules to reuse. | ||
## Options | ||
* `rawBody` - Set to `true` to return the raw value of the response body, instead of a string. *Important note*: The returned body will be different in Node and browser environments. In Node, it will return a `Buffer`, while in browsers it will return an `ArrayBuffer`. | ||
* `url` - URL to the resource you want to reach. | ||
* `method` - HTTP method to use for request. Default: `GET`, unless a body is provided, in which case the default is `POST`. | ||
* `headers` - Object of HTTP headers to send. Note that cross-origin requests in IE9 will not be able to set these headers. | ||
* `body` - The request body. If the `jsonRequest` middleware is used, it will serialize to a JSON string before sending. Otherwise, it tries to send whatever is passed to it using the underlying adapter. Supported types: | ||
* *Browser*: `string`, `ArrayBufferView`, `Blob`, `Document`, `FormData` (deprecated: `ArrayBuffer`) | ||
* *Node*: `string`, `buffer`, `ReadStream` | ||
* `bodySize` - Size of body, in bytes. Only used in Node when passing a `ReadStream` as body, in order for progress events to emit status on upload progress. | ||
* `timeout` - Timeout in millisecond for the request. Takes an object with `connect` and `socket` properties. | ||
* `maxRedirects` - Maximum number of redirects to follow before giving up. Note that this is only used in Node, as browsers have built-in redirect handling which cannot be adjusted. Default: `5` | ||
* `rawBody` - Set to `true` to return the raw value of the response body, instead of a string. The type returned differs based on the underlying adapter: | ||
* *Browser*: `ArrayBuffer` | ||
* *Node*: `Buffer` | ||
## Middleware | ||
## Return values | ||
Each middleware is an object of hook => action bindings. They are called in the order they are added to the request instance using `request.use()`. For instance, if you want to always set a certain header on outgoing responses, you could do: | ||
By default, `get-it` will return an object of single-channel event emitters. This is done in order to provide a low-level API surface that others can build upon, which is what the `promise` and `observable` middlewares do. Unless you really know what you're doing, you'll probably want to use those middlewares. | ||
## Response objects | ||
`get-it` does not expose the low-level primitives such as the `XMLHttpRequest` or `http.IncomingMessage` instances. Instead, it provides a response object with the following properties: | ||
```js | ||
{ | ||
// string (ArrayBuffer or Buffer if `rawBody` is set to `true`) | ||
body: 'Response body' | ||
url: 'http://foo.bar/baz', | ||
method: 'GET', | ||
statusCode: 200, | ||
statusMessage: 'OK', | ||
headers: { | ||
'Date': 'Fri, 09 Dec 2016 14:55:32 GMT', | ||
'Cache-Control': 'public, max-age=120' | ||
} | ||
} | ||
``` | ||
const isAwesome = { | ||
processOptions: options => Object.assign({ | ||
headers: Object.assign({}, options.headers, { | ||
'X-Is-Awesome': 'Absolutely' | ||
}) | ||
}) | ||
} | ||
request.use(isAwesome) | ||
## Promise API | ||
```js | ||
const getIt = require('get-it') | ||
const request = getIt([require('get-it/lib/middleware/promise')]) | ||
request({url: 'http://foo.bar'}) | ||
.then(res => console.log(res.body)) | ||
.catch(err => console.error(err)) | ||
``` | ||
The available hooks, their arguments and expected return values are as following: | ||
If you are targetting older browsers that do not have a global Promise implementation, you must register one globally using a polyfill such as [es6-promise](https://github.com/stefanpenner/es6-promise): | ||
### processOptions | ||
```js | ||
require('es6-promise/auto') | ||
Called once a new request is instantiated. Can be used to alter options before they are validated and turned into an actual HTTP request. This hook is used by the `base` middleware, which prefixes the passed URL if it is not an absolute URI already. | ||
const promise = require('get-it/lib/middleware/promise') | ||
const request = getIt([promise]) | ||
``` | ||
Arguments: | ||
### Cancelling promise-based requests | ||
1. `options` - Object of request options passed to the request. Should be cloned if modifications are to be performed, to prevent unexpected side-effects. | ||
With the Promise API, you can cancel requests using a _cancel token_. This API is based on the [Cancelable Promises proposal](https://github.com/tc39/proposal-cancelable-promises), which is currently at Stage 1. | ||
Should return: Plain object of options to pass on to the rest of the middlewares. | ||
You can create a cancel token using the `CancelToken.source` factory as shown below: | ||
### parseResponse | ||
```js | ||
const promise = require('get-it/lib/middleware/promise') | ||
const request = getIt([promise]) | ||
Called once a response has been received and the response body is ready. | ||
const source = promise.CancelToken.source() | ||
@todo document | ||
request.get({ | ||
url: 'http://foo.bar/baz', | ||
cancelToken: source.token | ||
}).catch(err => { | ||
if (promise.isCancel(err)) { | ||
console.log('Request canceled', err.message) | ||
} else { | ||
// handle error | ||
} | ||
}) | ||
## Prior work | ||
// Cancel the request (the message parameter is optional) | ||
source.cancel('Operation canceled by the user') | ||
``` | ||
## Observable API | ||
The observable API requires you to pass an Observable-implementation that you want to use. Optionally, you can register it under the global `Observable`, but this is not recommended. | ||
```js | ||
const getIt = require('get-it') | ||
const observable = require('get-it/lib/middleware/observable') | ||
const request = getIt() | ||
request.use(observable({ | ||
implementation: require('zen-observable')} | ||
)) | ||
const observer = request({url: 'http://foo.bar/baz'}) | ||
.filter(ev => ev.type === 'response') | ||
.subscribe({ | ||
next: res => console.log(res.body), | ||
error: err => console.error(err) | ||
}) | ||
// If you want to cancel the request, simply unsubscribe: | ||
observer.unsubscribe() | ||
``` | ||
It's important to note that the observable middleware does not only emit `response` objects, but also `progress` events. You should always filter to specify what you're interested in receiving. Every emitted value has a `type` property. | ||
## Upcoming features | ||
* Developer-friendly assertions that are stripped in production to reduce bundle size and performance | ||
* Authentication (basic) | ||
* Stream response middleware? | ||
## Prior art | ||
This module was inspired by the great work of others: | ||
@@ -61,0 +178,0 @@ |
@@ -1,5 +0,5 @@ | ||
import pubsub from 'nano-pubsub' | ||
import middlewareReducer from './util/middlewareReducer' | ||
import processOptions from './middleware/defaultOptionsProcessor' | ||
import httpRequest from './request' // node-request in node, browser-request in browsers | ||
const pubsub = require('nano-pubsub') | ||
const middlewareReducer = require('./util/middlewareReducer') | ||
const processOptions = require('./middleware/defaultOptionsProcessor') | ||
const httpRequest = require('./request') // node-request in node, browser-request in browsers | ||
@@ -6,0 +6,0 @@ const channelNames = ['request', 'response', 'progress', 'error', 'abort'] |
const Cancel = require('./Cancel') | ||
const Promise = require('any-promise') | ||
@@ -4,0 +3,0 @@ function CancelToken(executor) { |
@@ -1,2 +0,2 @@ | ||
import createErrorClass from 'create-error-class' | ||
const createErrorClass = require('create-error-class') | ||
@@ -8,3 +8,3 @@ const HttpError = createErrorClass('HttpError', function (res) { | ||
module.exports = { | ||
module.exports = () => ({ | ||
onResponse: res => { | ||
@@ -18,2 +18,2 @@ const isHttpError = res.statusCode >= 400 | ||
} | ||
} | ||
}) |
const objectAssign = require('object-assign') | ||
module.exports = { | ||
module.exports = () => ({ | ||
processOptions: options => { | ||
@@ -16,2 +16,2 @@ if (typeof options.body === 'undefined') { | ||
} | ||
} | ||
}) |
const objectAssign = require('object-assign') | ||
module.exports = { | ||
module.exports = () => ({ | ||
onResponse: response => { | ||
@@ -14,5 +14,5 @@ const contentType = response.headers['content-type'] | ||
processOptions: options => objectAssign({}, options, { | ||
headers: objectAssign({}, options.headers, {Accept: 'application/json'}) | ||
headers: objectAssign({Accept: 'application/json'}, options.headers) | ||
}) | ||
} | ||
}) | ||
@@ -19,0 +19,0 @@ function tryParse(body) { |
@@ -1,16 +0,23 @@ | ||
import Observable from 'any-observable' | ||
import objectAssign from 'object-assign' | ||
const global = require('global') | ||
const objectAssign = require('object-assign') | ||
module.exports = { | ||
onReturn: (channels, context) => new Observable(observer => { | ||
channels.error.subscribe(err => observer.error(err)) | ||
channels.progress.subscribe(event => observer.next(objectAssign({type: 'progress'}, event))) | ||
channels.response.subscribe(response => { | ||
observer.next({type: 'response', response}) | ||
observer.complete() | ||
module.exports = (opts = {}) => { | ||
const Observable = opts.implementation || global.Observable | ||
if (!Observable) { | ||
throw new Error('`Observable` is not available in global scope, and no implementation was passed') | ||
} | ||
return { | ||
onReturn: (channels, context) => new Observable(observer => { | ||
channels.error.subscribe(err => observer.error(err)) | ||
channels.progress.subscribe(event => observer.next(objectAssign({type: 'progress'}, event))) | ||
channels.response.subscribe(response => { | ||
observer.next(objectAssign({type: 'response'}, response)) | ||
observer.complete() | ||
}) | ||
channels.request.publish(context) | ||
return () => channels.abort.publish() | ||
}) | ||
channels.request.publish(context) | ||
return () => channels.abort.publish() | ||
}) | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
module.exports = { | ||
module.exports = () => ({ | ||
onRequest: evt => { | ||
@@ -31,2 +31,2 @@ if (evt.adapter !== 'xhr') { | ||
} | ||
} | ||
}) |
@@ -13,3 +13,3 @@ const progressStream = require('progress-stream') | ||
module.exports = { | ||
module.exports = () => ({ | ||
onHeaders: (response, evt) => { | ||
@@ -41,2 +41,2 @@ const progress = progressStream({time: 16}) | ||
} | ||
} | ||
}) |
@@ -1,2 +0,2 @@ | ||
const Promise = require('any-promise') | ||
const global = require('global') | ||
const Cancel = require('./cancel/Cancel') | ||
@@ -6,5 +6,6 @@ const CancelToken = require('./cancel/CancelToken') | ||
const promise = (opts = {}) => { | ||
const promise = () => { | ||
const Promise = global.Promise | ||
if (!Promise) { | ||
throw new Error('`Promise` is not available in global scope, and no implementation was given') | ||
throw new Error('`Promise` is not available in global scope, and no implementation was passed') | ||
} | ||
@@ -11,0 +12,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
378747
16
88
2040
189
33
6
13
5
- Removedany-observable@^0.2.0
- Removedany-promise@^1.3.0
- Removedany-observable@0.2.0(transitive)
- Removedany-promise@1.3.0(transitive)