gaxios.js 7.74 KB
"use strict";
// Copyright 2018, Google, LLC.
// 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.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const extend = require("extend");
const node_fetch_1 = require("node-fetch");
const qs = require("querystring");
const stream = require("stream");
const url = require("url");
const common_1 = require("./common");
const isbrowser_1 = require("./isbrowser");
const retry_1 = require("./retry");
const URL = isbrowser_1.isBrowser() ? window.URL : url.URL;
const fetch = isbrowser_1.isBrowser() ? window.fetch : node_fetch_1.default;
// tslint:disable-next-line variable-name no-any
let HttpsProxyAgent;
// Figure out if we should be using a proxy. Only if it's required, load
// the https-proxy-agent module as it adds startup cost.
function loadProxy() {
    const proxy = process.env.HTTPS_PROXY || process.env.https_proxy ||
        process.env.HTTP_PROXY || process.env.http_proxy;
    if (proxy) {
        HttpsProxyAgent = require('https-proxy-agent');
    }
    return proxy;
}
loadProxy();
class Gaxios {
    /**
     * The Gaxios class is responsible for making HTTP requests.
     * @param defaults The default set of options to be used for this instance.
     */
    constructor(defaults) {
        this.agentCache = new Map();
        this.defaults = defaults || {};
    }
    /**
     * Perform an HTTP request with the given options.
     * @param opts Set of HTTP options that will be used for this HTTP request.
     */
    request(opts = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            opts = this.validateOpts(opts);
            try {
                let translatedResponse;
                if (opts.adapter) {
                    translatedResponse = yield opts.adapter(opts);
                }
                else {
                    const res = yield fetch(opts.url, opts);
                    const data = yield this.getResponseData(opts, res);
                    translatedResponse = this.translateResponse(opts, res, data);
                }
                if (!opts.validateStatus(translatedResponse.status)) {
                    throw new common_1.GaxiosError(`Request failed with status code ${translatedResponse.status}`, opts, translatedResponse);
                }
                return translatedResponse;
            }
            catch (e) {
                const err = e;
                err.config = opts;
                const { shouldRetry, config } = yield retry_1.getRetryConfig(e);
                if (shouldRetry && config) {
                    err.config.retryConfig.currentRetryAttempt =
                        config.retryConfig.currentRetryAttempt;
                    return this.request(err.config);
                }
                throw err;
            }
        });
    }
    getResponseData(opts, res) {
        return __awaiter(this, void 0, void 0, function* () {
            switch (opts.responseType) {
                case 'stream':
                    return res.body;
                case 'json':
                    let data = yield res.text();
                    try {
                        data = JSON.parse(data);
                    }
                    catch (e) {
                    }
                    return data;
                case 'arraybuffer':
                    return res.arrayBuffer();
                case 'blob':
                    return res.blob();
                default:
                    return res.text();
            }
        });
    }
    /**
     * Validates the options, and merges them with defaults.
     * @param opts The original options passed from the client.
     */
    validateOpts(options) {
        const opts = extend(true, {}, this.defaults, options);
        if (!opts.url) {
            throw new Error('URL is required.');
        }
        // baseUrl has been deprecated, remove in 2.0
        const baseUrl = opts.baseUrl || opts.baseURL;
        if (baseUrl) {
            opts.url = baseUrl + opts.url;
        }
        const parsedUrl = new URL(opts.url);
        opts.url = `${parsedUrl.origin}${parsedUrl.pathname}`;
        opts.params = extend(qs.parse(parsedUrl.search.substr(1)), // removes leading ?
        opts.params);
        opts.paramsSerializer = opts.paramsSerializer || this.paramsSerializer;
        if (opts.params) {
            parsedUrl.search = opts.paramsSerializer(opts.params);
        }
        opts.url = parsedUrl.href;
        if (typeof options.maxContentLength === 'number') {
            opts.size = options.maxContentLength;
        }
        if (typeof options.maxRedirects === 'number') {
            opts.follow = options.maxRedirects;
        }
        opts.headers = opts.headers || {};
        if (opts.data) {
            if (this.isReadableStream(opts.data)) {
                opts.body = opts.data;
            }
            else if (typeof opts.data === 'object') {
                opts.body = JSON.stringify(opts.data);
                opts.headers['Content-Type'] = 'application/json';
            }
            else {
                opts.body = opts.data;
            }
        }
        opts.validateStatus = opts.validateStatus || this.validateStatus;
        opts.responseType = opts.responseType || 'json';
        if (!opts.headers['Accept'] && opts.responseType === 'json') {
            opts.headers['Accept'] = 'application/json';
        }
        opts.method = opts.method || 'GET';
        const proxy = loadProxy();
        if (proxy) {
            if (this.agentCache.has(proxy)) {
                opts.agent = this.agentCache.get(proxy);
            }
            else {
                opts.agent = new HttpsProxyAgent(proxy);
                this.agentCache.set(proxy, opts.agent);
            }
        }
        return opts;
    }
    /**
     * By default, throw for any non-2xx status code
     * @param status status code from the HTTP response
     */
    validateStatus(status) {
        return status >= 200 && status < 300;
    }
    /**
     * Encode a set of key/value pars into a querystring format (?foo=bar&baz=boo)
     * @param params key value pars to encode
     */
    paramsSerializer(params) {
        return qs.stringify(params);
    }
    isReadableStream(obj) {
        return obj instanceof stream.Readable && typeof obj._read === 'function';
    }
    translateResponse(opts, res, data) {
        // headers need to be converted from a map to an obj
        const headers = {};
        res.headers.forEach((value, key) => {
            headers[key] = value;
        });
        return {
            config: opts,
            data: data,
            headers,
            status: res.status,
            statusText: res.statusText
        };
    }
}
exports.Gaxios = Gaxios;
//# sourceMappingURL=gaxios.js.map