Skip to content

Commit

Permalink
feat: http handler helper (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
gkatsev authored Jul 26, 2021
1 parent 3b7632c commit 5765328
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 4 deletions.
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# xhr

[![Join the chat at https://gitter.im/naugtur-xhr/Lobby](https://badges.gitter.im/naugtur-xhr/Lobby.svg)](https://gitter.im/naugtur-xhr/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)


> Originally forked from [naugtur/xhr](https://github.com/naugtur/xhr).
A small XMLHttpRequest wrapper. Designed for use with [browserify](http://browserify.org/), [webpack](https://webpack.github.io/) etc.

Expand Down Expand Up @@ -76,6 +74,8 @@ the returned object is either an [`XMLHttpRequest`][3] instance
or an [`XDomainRequest`][4] instance (if on IE8/IE9 &&
`options.useXDR` is set to `true`)
### XhrCallback
Your callback will be called once with the arguments
( [`Error`][5], `response` , `body` ) where the response is an object:
```js
Expand Down Expand Up @@ -202,6 +202,52 @@ A function being called right before the `send` method of the `XMLHttpRequest` o
Pass an `XMLHttpRequest` object (or something that acts like one) to use instead of constructing a new one using the `XMLHttpRequest` or `XDomainRequest` constructors. Useful for testing.
## Helpers
This module exposes the following helpers on the `xhr` object.
### `xhr.httpHandler(callback, decodeResponseBody) => XhrCallback`
`httpHandler` is a wrapper for the [XhrCallback][] which returns an error for HTTP Status Codes 4xx and 5xx. Given a callback, it'll either return an error with the response body as the error's cause, or return the response body.
Usage like so:
```js
xhr({
uri: "https://example.com/foo",
responseType: 'arraybuffer'
}, xhr.httpHandler(function(err, responseBody) {

// we got an error if the XHR errored out or if the status code was 4xx/5xx
if (err) {
// error cause is coming soon to JavaScript https://github.com/tc39/proposal-error-cause
throw new Error(err, {cause: err.cause});
}

// this will log an ArrayBuffer
console.log(responseBody);
});
```
```js
xhr({
uri: "https://example.com/foo",
responseType: 'arraybuffer'
}, xhr.httpHandler(function(err, responseBody) {

if (err) {
throw new Error(err, {cause: err.cause});
}

// in this case, responseBody will be a String
console.log(responseBody);
},

// passing true as the second argument will cause httpHandler try and decode the response body into a string
true)
```
## FAQ
- Why is my server's JSON response not parsed? I returned the right content-type.
Expand Down
50 changes: 50 additions & 0 deletions http-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var window = require('global/window');

const httpResponseHandler = (callback, decodeResponseBody = false) => (err, response, responseBody) => {
// if the XHR failed, return that error
if (err) {
callback(err);
return;
}

// if the HTTP status code is 4xx or 5xx, the request also failed
if (response.statusCode >= 400 && response.statusCode <= 599) {
let cause = responseBody;

if (decodeResponseBody) {
if (window.TextDecoder) {
const charset = getCharset(response.headers && response.headers['content-type']);

try {
cause = new TextDecoder(charset).decode(responseBody);
} catch (e) {
}
} else {
cause = String.fromCharCode.apply(null, new Uint8Array(responseBody));
}
}

callback({cause});
return;
}

// otherwise, request succeeded
callback(null, responseBody);
};

function getCharset(contentTypeHeader = '') {
return contentTypeHeader
.toLowerCase()
.split(';')
.reduce((charset, contentType) => {
const [type, value] = contentType.split('=');

if (type.trim() === 'charset') {
return value.trim();
}

return charset;
}, 'utf-8');
}

module.exports = httpResponseHandler;
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export type BodyCallback = (
error: Error,
body: any
) => void;

export type HttpResponseHandler = (
callback: BodyCallback,
decodeResponseBody: boolean
) => XhrCallback;

export type XhrCallback = (
error: Error,
response: XhrResponse,
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ var window = require("global/window")
var _extends = require("@babel/runtime/helpers/extends");
var isFunction = require('is-function');

createXHR.httpHandler = require('./http-handler.js');

/**
* @license
* slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
},
"license": "MIT",
"scripts": {
"test": "run-browser test/index.js -b -m test/mock-server.js | tap-spec",
"test:index": "run-browser test/index.js -b -m test/mock-server.js | tap-spec",
"test:http-handler": "run-browser test/http-handler.js -b -m test/mock-server.js | tap-spec",
"test": "npm run test:index && npm run test:http-handler",
"browser": "run-browser -m test/mock-server.js test/index.js"
}
}
104 changes: 104 additions & 0 deletions test/http-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
var window = require("global/window")
var test = require("tape")
var forEach = require("for-each")

var httpHandler = require("../http-handler.js")

function toArrayBuffer(item) {
const buffer = new ArrayBuffer(item.length);
const bufferView = new Uint8Array(buffer);

for (let i = 0; i < item.length; i++) {
bufferView[i] = item.charCodeAt(i);
}
return buffer;
}

test('httpHandler takes a callback and returns a method of arity 3', function(assert) {
const xhrHandler = httpHandler(() => {});

assert.equal(xhrHandler.length, 3);
assert.end();
});


test('httpHandler returns responseBody to callback if no error and success http status code', function(assert) {
const xhrHandler = httpHandler((err, body) => {
assert.equal(body, 'hello');
});

xhrHandler(null, { statusCode: 200 }, 'hello');
assert.end();
});

test('httpHandler passes error to callback', function(assert) {
const error = new Error('the error');

const xhrHandler = httpHandler((err, body) => {
assert.equal(err, error);
});

xhrHandler(error, null, 'hello');
assert.end();
});

test('httpHandler passes error to callback', function(assert) {
const error = new Error('the error');

const xhrHandler = httpHandler((err, body) => {
assert.equal(err, error);
});

xhrHandler(error, null, 'hello');
assert.end();
});

test('httpHandler returns responseBody as cause for 4xx/5xx responses', function(assert) {
const xhrHandler = httpHandler((err, body) => {
assert.equal(err.cause, "can't touch this");
});

xhrHandler(null, { statusCode: 403 }, "can't touch this");
xhrHandler(null, { statusCode: 504 }, "can't touch this");
assert.end();
});

test('httpHandler decodes responseBody using TextDecoder for 4xx/5xx responses', function(assert) {
const xhrHandler = httpHandler((err, body) => {
assert.equal(err.cause, "can't touch this");
}, true);

xhrHandler(null, { statusCode: 403 }, toArrayBuffer("can't touch this"));
xhrHandler(null, { statusCode: 504 }, toArrayBuffer("can't touch this"));
assert.end();
});

test('httpHandler decodes responseBody using TextDecoder for 4xx/5xx responses', function(assert) {
let xhrHandler = httpHandler((err, body) => {
assert.equal(err.cause, "");
}, true);

xhrHandler(null, { statusCode: 403 }, toArrayBuffer(""));

xhrHandler = httpHandler((err, body) => {
assert.equal(err.cause, null);
}, true);
xhrHandler(null, { statusCode: 504 }, null);
assert.end();
});

test('httpHandler decodes responseBody using fromCharCode if TextDecoder is unavailable for 4xx/5xx responses', function(assert) {
const TextDecoder = window.TextDecoder;

window.TextDecoder = null;

const xhrHandler = httpHandler((err, body) => {
assert.equal(err.cause, "can't touch this");
}, true);

xhrHandler(null, { statusCode: 403 }, toArrayBuffer("can't touch this"));
xhrHandler(null, { statusCode: 504 }, toArrayBuffer("can't touch this"));

window.TextDecoder = TextDecoder;
assert.end();
});
5 changes: 5 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,8 @@ test("XHR can be overridden", { timeout: 500 }, function(assert) {
assert.equal(xdrs, 1, "created the custom XDR")
assert.end()
})

test('httpHandler is available on XHR', function(assert) {
assert.ok(xhr.httpHandler)
assert.end();
});

0 comments on commit 5765328

Please sign in to comment.