UNPKG

@adobe/fetch

Version:

Light-weight Fetch implementation transparently supporting both HTTP/1(.1) and HTTP/2

200 lines (172 loc) 5.4 kB
/* * Copyright 2020 Adobe. All rights reserved. * This file is licensed to you 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 REPRESENTATIONS * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ import { Body, cloneStream, guessContentType } from './body.js'; import Headers from './headers.js'; import { isPlainObject } from '../common/utils.js'; import { isFormData, FormDataSerializer } from '../common/formData.js'; const DEFAULT_FOLLOW = 20; const INTERNALS = Symbol('Request internals'); /** * Request class * * @see https://fetch.spec.whatwg.org/#request-class */ class Request extends Body { /** * Constructs a new Request instance * * @constructor * @param {Request|String} input * @param {Object} [init={}] */ constructor(input, init = {}) { // normalize input const req = input instanceof Request ? input : null; const parsedURL = req ? new URL(req.url) : new URL(input); let method = init.method || (req && req.method) || 'GET'; method = method.toUpperCase(); // eslint-disable-next-line no-eq-null, eqeqeq if ((init.body != null // neither null nor undefined || (req && req.body !== null)) && ['GET', 'HEAD'].includes(method)) { throw new TypeError('Request with GET/HEAD method cannot have body'); } let body = init.body || (req && req.body ? cloneStream(req) : null); const headers = new Headers(init.headers || (req && req.headers) || {}); if (isFormData(body)) { // spec-compliant FormData if (!headers.has('content-type')) { const fd = new FormDataSerializer(body); body = fd.stream(); headers.set('content-type', fd.contentType()); if (!headers.has('transfer-encoding') && !headers.has('content-length')) { headers.set('content-length', fd.length()); } } } if (!headers.has('content-type')) { if (isPlainObject(body)) { // non-spec extension: support plain js object body (JSON serialization) body = JSON.stringify(body); headers.set('content-type', 'application/json'); } else { const contentType = guessContentType(body); if (contentType) { headers.set('content-type', contentType); } } } // call Body constructor super(body); let signal = req ? req.signal : null; if ('signal' in init) { signal = init.signal; } if (signal && (typeof signal !== 'object' || typeof signal.aborted !== 'boolean' || typeof signal.addEventListener !== 'function')) { throw new TypeError('signal must be an AbortSignal'); } const redirect = init.redirect || (req && req.redirect) || 'follow'; if (!['follow', 'error', 'manual'].includes(redirect)) { throw new TypeError(`'${redirect}' is not a valid redirect option`); } const cache = init.cache || (req && req.cache) || 'default'; if (!['default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached'].includes(cache)) { throw new TypeError(`'${cache}' is not a valid cache option`); } this[INTERNALS] = { init: { ...init }, method, redirect, cache, headers, parsedURL, signal, }; // non-spec extension options if (init.follow === undefined) { if (!req || req.follow === undefined) { this.follow = DEFAULT_FOLLOW; } else { this.follow = req.follow; } } else { this.follow = init.follow; } this.counter = init.counter || (req && req.counter) || 0; if (init.compress === undefined) { if (!req || req.compress === undefined) { // default this.compress = true; } else { this.compress = req.compress; } } else { this.compress = init.compress; } if (init.decode === undefined) { if (!req || req.decode === undefined) { // default this.decode = true; } else { this.decode = req.decode; } } else { this.decode = init.decode; } } get method() { return this[INTERNALS].method; } get url() { return this[INTERNALS].parsedURL.toString(); } get headers() { return this[INTERNALS].headers; } get redirect() { return this[INTERNALS].redirect; } get cache() { return this[INTERNALS].cache; } get signal() { return this[INTERNALS].signal; } /** * Clone this request * * @return {Request} */ clone() { return new Request(this); } get init() { return this[INTERNALS].init; } get [Symbol.toStringTag]() { return this.constructor.name; } } Object.defineProperties(Request.prototype, { method: { enumerable: true }, url: { enumerable: true }, headers: { enumerable: true }, redirect: { enumerable: true }, cache: { enumerable: true }, clone: { enumerable: true }, signal: { enumerable: true }, }); export default Request;