
This patch makes sure that successful HTTP requests, which result in an error code, are passed to the catch() or reject handler, rather than the success handler. This results in two different cases in the claim() handler, that of an internally thrown exception (like a TypeError), and that of a Response() instance. In most cases, developers should not write code that throws exceptions - and even in those, the ES6 Promise API silently swallows most uncaught throws. Change-Id: I736bcaccf6324a3d66191692e5f2ce922a727dea
192 lines
6.3 KiB
JavaScript
192 lines
6.3 KiB
JavaScript
/*
|
|
* Copyright (c) 2016 Hewlett Packard Enterprise Development L.P.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
* use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
|
|
* the License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
import 'isomorphic-fetch';
|
|
|
|
/**
|
|
* This utility class provides an abstraction layer for HTTP calls via fetch(). Its purpose is
|
|
* to provide common, SDK-wide behavior for all HTTP requests. Included are:
|
|
*
|
|
* - Providing a common extension point for request and response manipulation.
|
|
* - Access to default headers.
|
|
*
|
|
* In the future, this class chould also be extended to provide:
|
|
*
|
|
* - Some form of progress() support for large uploads and downloads (perhaps via introduction of Q)
|
|
* - Convenience decoding of the response body, depending on Content-Type.
|
|
* - Internal error handling (At this time, HTTP errors are passed to then() rather than catch()).
|
|
* - Other features.
|
|
*/
|
|
export default class Http {
|
|
|
|
/**
|
|
* The list of active request interceptors for this instance. You may modify this list to
|
|
* adjust how your responses are processed. Each interceptor will be passed the Request
|
|
* instance, which must be returned from the interceptor either directly, or via a promise.
|
|
*
|
|
* @returns {Array} An array of all request interceptors.
|
|
*/
|
|
get requestInterceptors () {
|
|
return this._requestInterceptors;
|
|
}
|
|
|
|
/**
|
|
* The list of active response interceptors for this instance. Each interceptor will be passed
|
|
* the raw (read-only) Response instance, which should be returned from the interceptor either
|
|
* directly, or via a promise.
|
|
*
|
|
* @returns {Array} An array of all response interceptors.
|
|
*/
|
|
get responseInterceptors () {
|
|
return this._responseInterceptors;
|
|
}
|
|
|
|
/**
|
|
* The default headers which will be sent with every request. A copy of these headers will be
|
|
* added to the Request instance passed through the interceptor chain, and may be
|
|
* modified there.
|
|
*
|
|
* @returns {{string: string}} A mapping of 'headerName': 'headerValue'
|
|
*/
|
|
get defaultHeaders () {
|
|
return this._defaultHeaders;
|
|
}
|
|
|
|
/**
|
|
* Create a new HTTP handler.
|
|
*/
|
|
constructor () {
|
|
this._requestInterceptors = [];
|
|
this._responseInterceptors = [];
|
|
|
|
// Add default response interceptors.
|
|
this._defaultHeaders = {
|
|
'Content-Type': 'application/json'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Make a decorated HTTP request.
|
|
*
|
|
* @param {String} method The HTTP method.
|
|
* @param {String} url The request URL.
|
|
* @param {{}} headers A map of HTTP headers.
|
|
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
|
* @returns {Promise} A promise which will resolve with the processed request response.
|
|
*/
|
|
httpRequest (method, url, headers = {}, body) {
|
|
|
|
// Sanitize the headers...
|
|
headers = Object.assign({}, headers, this.defaultHeaders);
|
|
|
|
// Build the request
|
|
const init = {method, headers};
|
|
|
|
// The Request() constructor will throw an error if the method is GET/HEAD, and there's a body.
|
|
if (['GET', 'HEAD'].indexOf(method) === -1 && body) {
|
|
init.body = JSON.stringify(body);
|
|
}
|
|
const request = new Request(url, init);
|
|
|
|
// Build the wrapper promise.
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let promise = Promise.resolve(request);
|
|
|
|
// Loop through the request interceptors, constructing a promise chain.
|
|
for (let interceptor of this.requestInterceptors) {
|
|
promise = promise.then(interceptor);
|
|
}
|
|
|
|
// Make the actual request...
|
|
promise = promise
|
|
.then((request) => {
|
|
// Deconstruct the request, since fetch-mock doesn't actually support fetch(Request);
|
|
const init = {
|
|
method: request.method,
|
|
headers: request.headers
|
|
};
|
|
if (['GET', 'HEAD'].indexOf(request.method) === -1 && request.body) {
|
|
init.body = request.body;
|
|
}
|
|
|
|
return fetch(request.url, init);
|
|
});
|
|
|
|
// Fetch will treat all http responses (2xx, 3xx, 4xx, 5xx, etc) as successful responses.
|
|
// This will catch all 4xx and 5xx responses and return them to the catch() handler. Note
|
|
// that it's up to the downstream developer to determine whether what they received is an
|
|
// error or a failed response.
|
|
promise.then((response) => {
|
|
if (response.status >= 400) {
|
|
return reject(response);
|
|
} else {
|
|
return response;
|
|
}
|
|
});
|
|
|
|
// Pass the response content through the response interceptors...
|
|
for (let interceptor of this.responseInterceptors) {
|
|
promise = promise.then(interceptor);
|
|
}
|
|
|
|
promise.then((response) => resolve(response), (error) => reject(error));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Make a raw GET request against a particular URL.
|
|
*
|
|
* @param {String} url The request URL.
|
|
* @returns {Promise} A promise which will resolve with the processed request response.
|
|
*/
|
|
httpGet (url) {
|
|
return this.httpRequest('GET', url, {}, null);
|
|
}
|
|
|
|
/**
|
|
* Make a raw PUT request against a particular URL.
|
|
*
|
|
* @param {String} url The request URL.
|
|
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
|
* @returns {Promise} A promise which will resolve with the processed request response.
|
|
*/
|
|
httpPut (url, body) {
|
|
return this.httpRequest('PUT', url, {}, body);
|
|
}
|
|
|
|
/**
|
|
* Make a raw POST request against a particular URL.
|
|
*
|
|
* @param {String} url The request URL.
|
|
* @param {{}} body The body. It will be JSON-Encoded by the handler.
|
|
* @returns {Promise} A promise which will resolve with the processed request response.
|
|
*/
|
|
httpPost (url, body) {
|
|
return this.httpRequest('POST', url, {}, body);
|
|
}
|
|
|
|
/**
|
|
* Make a raw DELETE request against a particular URL.
|
|
*
|
|
* @param {String} url The request URL.
|
|
* @returns {Promise} A promise which will resolve with the processed request response.
|
|
*/
|
|
httpDelete (url) {
|
|
return this.httpRequest('DELETE', url, {}, null);
|
|
}
|
|
}
|