관리-도구
편집 파일: request.js
'use strict' const { URL } = require('url') const { Minipass } = require('minipass') const Headers = require('./headers.js') const { exportNodeCompatibleHeaders } = Headers const Body = require('./body.js') const { clone, extractContentType, getTotalBytes } = Body const version = require('../package.json').version const defaultUserAgent = `minipass-fetch/${version} (+https://github.com/isaacs/minipass-fetch)` const INTERNALS = Symbol('Request internals') const isRequest = input => typeof input === 'object' && typeof input[INTERNALS] === 'object' const isAbortSignal = signal => { const proto = ( signal && typeof signal === 'object' && Object.getPrototypeOf(signal) ) return !!(proto && proto.constructor.name === 'AbortSignal') } class Request extends Body { constructor (input, init = {}) { const parsedURL = isRequest(input) ? new URL(input.url) : input && input.href ? new URL(input.href) : new URL(`${input}`) if (isRequest(input)) { init = { ...input[INTERNALS], ...init } } else if (!input || typeof input === 'string') { input = {} } const method = (init.method || input.method || 'GET').toUpperCase() const isGETHEAD = method === 'GET' || method === 'HEAD' if ((init.body !== null && init.body !== undefined || isRequest(input) && input.body !== null) && isGETHEAD) { throw new TypeError('Request with GET/HEAD method cannot have body') } const inputBody = init.body !== null && init.body !== undefined ? init.body : isRequest(input) && input.body !== null ? clone(input) : null super(inputBody, { timeout: init.timeout || input.timeout || 0, size: init.size || input.size || 0, }) const headers = new Headers(init.headers || input.headers || {}) if (inputBody !== null && inputBody !== undefined && !headers.has('Content-Type')) { const contentType = extractContentType(inputBody) if (contentType) { headers.append('Content-Type', contentType) } } const signal = 'signal' in init ? init.signal : null if (signal !== null && signal !== undefined && !isAbortSignal(signal)) { throw new TypeError('Expected signal must be an instanceof AbortSignal') } // TLS specific options that are handled by node const { ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, family, honorCipherOrder, key, passphrase, pfx, rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0', secureOptions, secureProtocol, servername, sessionIdContext, } = init this[INTERNALS] = { method, redirect: init.redirect || input.redirect || 'follow', headers, parsedURL, signal, ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, family, honorCipherOrder, key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext, } // node-fetch-only options this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20 this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true this.counter = init.counter || input.counter || 0 this.agent = init.agent || input.agent } 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 signal () { return this[INTERNALS].signal } clone () { return new Request(this) } get [Symbol.toStringTag] () { return 'Request' } static getNodeRequestOptions (request) { const parsedURL = request[INTERNALS].parsedURL const headers = new Headers(request[INTERNALS].headers) // fetch step 1.3 if (!headers.has('Accept')) { headers.set('Accept', '*/*') } // Basic fetch if (!/^https?:$/.test(parsedURL.protocol)) { throw new TypeError('Only HTTP(S) protocols are supported') } if (request.signal && Minipass.isStream(request.body) && typeof request.body.destroy !== 'function') { throw new Error( 'Cancellation of streamed requests with AbortSignal is not supported') } // HTTP-network-or-cache fetch steps 2.4-2.7 const contentLengthValue = (request.body === null || request.body === undefined) && /^(POST|PUT)$/i.test(request.method) ? '0' : request.body !== null && request.body !== undefined ? getTotalBytes(request) : null if (contentLengthValue) { headers.set('Content-Length', contentLengthValue + '') } // HTTP-network-or-cache fetch step 2.11 if (!headers.has('User-Agent')) { headers.set('User-Agent', defaultUserAgent) } // HTTP-network-or-cache fetch step 2.15 if (request.compress && !headers.has('Accept-Encoding')) { headers.set('Accept-Encoding', 'gzip,deflate') } const agent = typeof request.agent === 'function' ? request.agent(parsedURL) : request.agent if (!headers.has('Connection') && !agent) { headers.set('Connection', 'close') } // TLS specific options that are handled by node const { ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, family, honorCipherOrder, key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext, } = request[INTERNALS] // HTTP-network fetch step 4.2 // chunked encoding is handled by Node.js // we cannot spread parsedURL directly, so we have to read each property one-by-one // and map them to the equivalent https?.request() method options const urlProps = { auth: parsedURL.username || parsedURL.password ? `${parsedURL.username}:${parsedURL.password}` : '', host: parsedURL.host, hostname: parsedURL.hostname, path: `${parsedURL.pathname}${parsedURL.search}`, port: parsedURL.port, protocol: parsedURL.protocol, } return { ...urlProps, method: request.method, headers: exportNodeCompatibleHeaders(headers), agent, ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, family, honorCipherOrder, key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext, timeout: request.timeout, } } } module.exports = Request Object.defineProperties(Request.prototype, { method: { enumerable: true }, url: { enumerable: true }, headers: { enumerable: true }, redirect: { enumerable: true }, clone: { enumerable: true }, signal: { enumerable: true }, })