/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/fetch/23_request.js
896 строк
24 KB
Bartek Iwańczuk
feat(ext/fetch): support `priority` in `RequestInit` (#34716)
06 июн 2026, 16:47
Не верифицирован
06 июн 2026, 16:47
6405d83
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. (function () { const { core, internals, primordials } = __bootstrap; const { ArrayPrototypeMap, ArrayPrototypeSlice, ArrayPrototypeSplice, FunctionPrototypeCall, JSONParse, ObjectDefineProperty, ObjectKeys, ObjectPrototypeIsPrototypeOf, PromiseReject, PromiseResolve, RegExpPrototypeExec, StringPrototypeStartsWith, StringPrototypeToUpperCase, Symbol, SymbolFor, TypeError, } = primordials; const webidl = core.loadExtScript("ext:deno_webidl/00_webidl.js"); const { createFilteredInspectProxy } = core.loadExtScript( "ext:deno_web/01_console.js", ); const { HTTP_TOKEN_CODE_POINT_RE } = core.loadExtScript( "ext:deno_web/00_infra.js", ); const { URL } = core.loadExtScript("ext:deno_web/00_url.js"); const { extractBody, mixinBody } = core.loadExtScript( "ext:deno_fetch/22_body.js", ); const { getLocationHref } = core.loadExtScript("ext:deno_web/12_location.js"); const { extractMimeType } = core.loadExtScript("ext:deno_web/01_mimesniff.js"); const { blobFromObjectUrl } = core.loadExtScript("ext:deno_web/09_file.js"); const { fillHeaders, getDecodeSplitHeader, guardFromHeaders, headerListFromHeaders, headersFromHeaderList, headersFromHeaderListLazyTarget, } = core.loadExtScript("ext:deno_fetch/20_headers.js"); const { HttpClientPrototype } = core.loadExtScript( "ext:deno_fetch/22_http_client.js", ); const { createDependentAbortSignal, newSignal, signalAbort, } = core.loadExtScript("ext:deno_web/03_abort_signal.js"); const { DOMException } = core.loadExtScript("ext:deno_web/01_dom_exception.js"); const { markNotSerializable } = core.loadExtScript( "ext:deno_web/13_message_port.js", ); const { internalRidSymbol } = core; const _request = Symbol("request"); const _headers = Symbol("headers"); const _getHeaders = Symbol("get headers"); const _headersCache = Symbol("headers cache"); const _headersGuard = Symbol("headers guard"); const _signal = Symbol("signal"); const _signalCache = Symbol("signalCache"); const _mimeType = Symbol("mime type"); const _body = Symbol("body"); const _url = Symbol("url"); const _method = Symbol("method"); const _brand = webidl.brand; /** * @param {(() => string)[]} urlList * @param {string[]} urlListProcessed */ function processUrlList(urlList, urlListProcessed) { for (let i = 0; i < urlList.length; i++) { if (urlListProcessed[i] === undefined) { urlListProcessed[i] = urlList[i](); } } return urlListProcessed; } /** * Fields named `*Mode`, `mode`, `referrer*`, `integrity`, and `keepalive` are * only set on the inner object when the constructor's init bag customized * them away from the spec default. Leaving them off the object literal keeps * the V8 hidden class for `Deno.serve()`-created requests unchanged. The * getters substitute the spec defaults when a field is missing. * * @typedef InnerRequest * @property {() => string} method * @property {() => string} url * @property {() => string} currentUrl * @property {() => [string, string][]} headerList * @property {null | typeof __window.bootstrap.fetchBody.InnerBody} body * @property {undefined | "default" | "no-store" | "reload" | "no-cache" | "force-cache" | "only-if-cached"} [cacheMode] * @property {undefined | "omit" | "same-origin" | "include"} [credentialsMode] * @property {undefined | string} [integrity] * @property {undefined | boolean} [keepalive] * @property {undefined | "same-origin" | "no-cors" | "cors" | "navigate"} [mode] * @property {undefined | "auto" | "low" | "high"} [priority] * @property {"follow" | "error" | "manual"} redirectMode * @property {undefined | string} [referrer] "client", "no-referrer", or a serialized URL * @property {undefined | "" | "no-referrer" | "no-referrer-when-downgrade" | "same-origin" | "origin" | "strict-origin" | "origin-when-cross-origin" | "strict-origin-when-cross-origin" | "unsafe-url"} [referrerPolicy] * @property {number} redirectCount * @property {(() => string)[]} urlList * @property {string[]} urlListProcessed * @property {number | null} clientRid NOTE: non standard extension for `Deno.HttpClient`. * @property {Blob | null} blobUrlEntry */ /** * @param {string} method * @param {string | () => string} url * @param {() => [string, string][]} headerList * @param {typeof __window.bootstrap.fetchBody.InnerBody} body * @param {boolean} maybeBlob * @returns {InnerRequest} */ function newInnerRequest(method, url, headerList, body, maybeBlob) { let blobUrlEntry = null; if ( maybeBlob && typeof url === "string" && StringPrototypeStartsWith(url, "blob:") ) { blobUrlEntry = blobFromObjectUrl(url); } return { methodInner: method, get method() { return this.methodInner; }, set method(value) { this.methodInner = value; }, headerListInner: null, get headerList() { if (this.headerListInner === null) { try { this.headerListInner = headerList(); } catch { throw new TypeError("Cannot read headers: request closed"); } } return this.headerListInner; }, set headerList(value) { this.headerListInner = value; }, body, redirectMode: "follow", redirectCount: 0, urlList: [typeof url === "string" ? () => url : url], urlListProcessed: [], clientRid: null, blobUrlEntry, url() { if (this.urlListProcessed[0] === undefined) { try { this.urlListProcessed[0] = this.urlList[0](); } catch { throw new TypeError("cannot read url: request closed"); } } return this.urlListProcessed[0]; }, currentUrl() { const currentIndex = this.urlList.length - 1; if (this.urlListProcessed[currentIndex] === undefined) { try { this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); } catch { throw new TypeError("Cannot read url: request closed"); } } return this.urlListProcessed[currentIndex]; }, }; } /** * https://fetch.spec.whatwg.org/#concept-request-clone * @param {InnerRequest} request * @param {boolean} skipBody * @returns {InnerRequest} */ function cloneInnerRequest(request, skipBody = false) { const headerList = ArrayPrototypeMap( request.headerList, (x) => [x[0], x[1]], ); let body = null; if (request.body !== null && !skipBody) { body = request.body.clone(); } const cloned = { method: request.method, headerList, body, redirectMode: request.redirectMode, redirectCount: request.redirectCount, urlList: [() => request.url()], urlListProcessed: [request.url()], clientRid: request.clientRid, blobUrlEntry: request.blobUrlEntry, url() { if (this.urlListProcessed[0] === undefined) { try { this.urlListProcessed[0] = this.urlList[0](); } catch { throw new TypeError("Cannot read url: request closed"); } } return this.urlListProcessed[0]; }, currentUrl() { const currentIndex = this.urlList.length - 1; if (this.urlListProcessed[currentIndex] === undefined) { try { this.urlListProcessed[currentIndex] = this.urlList[currentIndex](); } catch { throw new TypeError("Cannot read url: request closed"); } } return this.urlListProcessed[currentIndex]; }, }; if (request.cacheMode !== undefined) cloned.cacheMode = request.cacheMode; if (request.credentialsMode !== undefined) { cloned.credentialsMode = request.credentialsMode; } if (request.integrity !== undefined) cloned.integrity = request.integrity; if (request.keepalive !== undefined) cloned.keepalive = request.keepalive; if (request.mode !== undefined) cloned.mode = request.mode; if (request.priority !== undefined) cloned.priority = request.priority; if (request.referrer !== undefined) cloned.referrer = request.referrer; if (request.referrerPolicy !== undefined) { cloned.referrerPolicy = request.referrerPolicy; } return cloned; } // method => normalized method const KNOWN_METHODS = { __proto__: null, "DELETE": "DELETE", "delete": "DELETE", "GET": "GET", "get": "GET", "HEAD": "HEAD", "head": "HEAD", "OPTIONS": "OPTIONS", "options": "OPTIONS", "PATCH": "PATCH", "POST": "POST", "post": "POST", "PUT": "PUT", "put": "PUT", }; /** * @param {string} m * @returns {string} */ function validateAndNormalizeMethod(m) { if (RegExpPrototypeExec(HTTP_TOKEN_CODE_POINT_RE, m) === null) { throw new TypeError("Method is not valid"); } const upperCase = StringPrototypeToUpperCase(m); switch (upperCase) { case "DELETE": case "GET": case "HEAD": case "OPTIONS": case "POST": case "PUT": return upperCase; case "CONNECT": case "TRACE": case "TRACK": throw new TypeError("Method is forbidden"); } return m; } class Request { /** @type {InnerRequest} */ [_request]; /** @type {Headers} */ [_headersCache]; [_getHeaders]; [_headersGuard]; [_signalCache]; [_url]; [_method]; /** @type {Headers} */ get [_headers]() { if (this[_headersCache] === undefined) { const getHeaders = this[_getHeaders]; if (getHeaders !== undefined && getHeaders !== null) { this[_headersCache] = getHeaders(); } else { const inner = this[_request]; const guard = this[_headersGuard]; if (typeof inner.header !== "function") { this[_headersCache] = headersFromHeaderList(inner.headerList, guard); } else { this[_headersCache] = headersFromHeaderListLazyTarget(inner, guard); } } } return this[_headersCache]; } set [_headers](value) { this[_headersCache] = value; } /** @type {AbortSignal} */ get [_signal]() { const signal = this[_signalCache]; // This signal has not been created yet, but the request has already completed if (signal === false) { const signal = newSignal(); this[_signalCache] = signal; signal[signalAbort]( new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), ); return signal; } // This signal not been created yet, and the request is still in progress if (signal === undefined) { const signal = newSignal(); this[_signalCache] = signal; this[_request].onCancel?.(() => { signal[signalAbort]( new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), ); }); return signal; } return signal; } get [_mimeType]() { const values = getDecodeSplitHeader( headerListFromHeaders(this[_headers]), "Content-Type", ); return extractMimeType(values); } get [_body]() { return this[_request].body; } /** * https://fetch.spec.whatwg.org/#dom-request * @param {RequestInfo} input * @param {RequestInit} init */ constructor(input, init = undefined) { if (input === _brand) { this[_brand] = _brand; return; } const prefix = "Failed to construct 'Request'"; webidl.requiredArguments(arguments.length, 1, prefix); input = webidl.converters["RequestInfo_DOMString"]( input, prefix, "Argument 1", ); init = webidl.converters["RequestInit"](init, prefix, "Argument 2"); this[_brand] = _brand; /** @type {InnerRequest} */ let request; const baseURL = getLocationHref(); // 4. let signal = null; // 5. if (typeof input === "string") { const parsedURL = new URL(input, baseURL); request = newInnerRequest( "GET", parsedURL.href, () => [], null, true, ); } else { // 6. if (!ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { throw new TypeError("Unreachable"); } const originalReq = input[_request]; // fold in of step 12 from below request = cloneInnerRequest(originalReq, true); request.redirectCount = 0; // reset to 0 - cloneInnerRequest copies the value signal = input[_signal]; } // 12. is folded into the else statement of step 6 above. // 17. referrer if (init.referrer !== undefined) { const referrer = init.referrer; if (referrer === "") { request.referrer = "no-referrer"; } else { let parsedReferrer; try { parsedReferrer = new URL(referrer, baseURL); } catch (err) { throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err, }); } if ( (parsedReferrer.protocol === "about:" && parsedReferrer.pathname === "client") ) { request.referrer = "client"; } else { request.referrer = parsedReferrer.href; } } } // 18. referrerPolicy if (init.referrerPolicy !== undefined) { request.referrerPolicy = init.referrerPolicy; } // 19. mode if (init.mode !== undefined) { if (init.mode === "navigate") { throw new TypeError("Request mode 'navigate' is not allowed"); } request.mode = init.mode; } // 20. credentials if (init.credentials !== undefined) { request.credentialsMode = init.credentials; } // 21. cache if (init.cache !== undefined) { request.cacheMode = init.cache; } // If request's cache mode is "only-if-cached" and request's mode is not // "same-origin", then throw a TypeError. if ( request.cacheMode === "only-if-cached" && request.mode !== "same-origin" ) { throw new TypeError( 'Request cache mode "only-if-cached" can only be used with same-origin mode', ); } // 22. if (init.redirect !== undefined) { request.redirectMode = init.redirect; } // 23. integrity if (init.integrity !== undefined) { request.integrity = init.integrity; } // 24. keepalive if (init.keepalive !== undefined) { request.keepalive = init.keepalive; } // priority if (init.priority !== undefined) { request.priority = init.priority; } // 25. if (init.method !== undefined) { const method = init.method; // fast path: check for known methods request.method = KNOWN_METHODS[method] ?? validateAndNormalizeMethod(method); } // 26. if (init.signal !== undefined) { signal = init.signal; } // NOTE: non standard extension. This handles Deno.HttpClient parameter if (init.client !== undefined) { if ( init.client !== null && !ObjectPrototypeIsPrototypeOf(HttpClientPrototype, init.client) ) { throw webidl.makeException( TypeError, "`client` must be a Deno.HttpClient", prefix, "Argument 2", ); } request.clientRid = init.client?.[internalRidSymbol] ?? null; } // 28. this[_request] = request; // 29 & 30. if (signal !== null) { this[_signalCache] = createDependentAbortSignal([signal], prefix); } // 31. this[_headers] = headersFromHeaderList(request.headerList, "request"); // 33. if (init.headers || ObjectKeys(init).length > 0) { const headerList = headerListFromHeaders(this[_headers]); const headers = init.headers ?? ArrayPrototypeSlice( headerList, 0, headerList.length, ); if (headerList.length !== 0) { ArrayPrototypeSplice(headerList, 0, headerList.length); } fillHeaders(this[_headers], headers); } // 34. let inputBody = null; if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) { inputBody = input[_body]; } // 35. if ( (request.method === "GET" || request.method === "HEAD") && ((init.body !== undefined && init.body !== null) || inputBody !== null) ) { throw new TypeError("Request with GET/HEAD method cannot have body"); } // 36. let initBody = null; // 37. if (init.body !== undefined && init.body !== null) { const res = extractBody(init.body); initBody = res.body; if (res.contentType !== null && !this[_headers].has("content-type")) { this[_headers].append("Content-Type", res.contentType); } } // 38. const inputOrInitBody = initBody ?? inputBody; // 40. let finalBody = inputOrInitBody; // 41. if (initBody === null && inputBody !== null) { if (input[_body] && input[_body].unusable()) { throw new TypeError("Input request's body is unusable"); } finalBody = inputBody.createProxy(); } // 42. request.body = finalBody; } get method() { webidl.assertBranded(this, RequestPrototype); if (this[_method]) { return this[_method]; } this[_method] = this[_request].method; return this[_method]; } get url() { webidl.assertBranded(this, RequestPrototype); if (this[_url]) { return this[_url]; } this[_url] = this[_request].url(); return this[_url]; } get headers() { webidl.assertBranded(this, RequestPrototype); return this[_headers]; } get redirect() { webidl.assertBranded(this, RequestPrototype); return this[_request].redirectMode; } get cache() { webidl.assertBranded(this, RequestPrototype); return this[_request].cacheMode ?? "default"; } get credentials() { webidl.assertBranded(this, RequestPrototype); return this[_request].credentialsMode ?? "same-origin"; } get integrity() { webidl.assertBranded(this, RequestPrototype); return this[_request].integrity ?? ""; } get keepalive() { webidl.assertBranded(this, RequestPrototype); return this[_request].keepalive ?? false; } get mode() { webidl.assertBranded(this, RequestPrototype); return this[_request].mode ?? "cors"; } get referrer() { webidl.assertBranded(this, RequestPrototype); const referrer = this[_request].referrer; if (referrer === undefined || referrer === "client") { return "about:client"; } if (referrer === "no-referrer") { return ""; } return referrer; } get referrerPolicy() { webidl.assertBranded(this, RequestPrototype); return this[_request].referrerPolicy ?? ""; } get signal() { webidl.assertBranded(this, RequestPrototype); return this[_signal]; } clone() { const prefix = "Failed to execute 'Request.clone'"; webidl.assertBranded(this, RequestPrototype); if (this[_body] && this[_body].unusable()) { throw new TypeError("Body is unusable"); } const clonedReq = cloneInnerRequest(this[_request]); const materializedSignal = this[_signal]; const clonedSignal = createDependentAbortSignal( [materializedSignal], prefix, ); const request = new Request(_brand); request[_request] = clonedReq; request[_signalCache] = clonedSignal; headerListFromHeaders(this[_headers]); request[_headersGuard] = guardFromHeaders(this[_headers]); return request; } [SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { return inspect( createFilteredInspectProxy({ object: this, evaluate: ObjectPrototypeIsPrototypeOf(RequestPrototype, this), keys: [ "bodyUsed", "headers", "method", "redirect", "url", ], }), inspectOptions, ); } } webidl.configureInterface(Request); const RequestPrototype = Request.prototype; markNotSerializable(RequestPrototype); mixinBody(RequestPrototype, _body, _mimeType); const requestJson = RequestPrototype.json; ObjectDefineProperty(RequestPrototype, "json", { __proto__: null, value: function json() { try { webidl.assertBranded(this, RequestPrototype); const text = this[_request].consumeTextBody?.(); if (text !== null && text !== undefined) { return PromiseResolve(JSONParse(text)); } } catch (error) { return PromiseReject(error); } return FunctionPrototypeCall(requestJson, this); }, writable: true, configurable: true, enumerable: true, }); webidl.converters["Request"] = webidl.createInterfaceConverter( "Request", RequestPrototype, ); webidl.converters["RequestInfo_DOMString"] = (V, prefix, context, opts) => { // Union for (Request or USVString) if (typeof V == "object") { if (ObjectPrototypeIsPrototypeOf(RequestPrototype, V)) { return webidl.converters["Request"](V, prefix, context, opts); } } // Passed to new URL(...) which implicitly converts DOMString -> USVString return webidl.converters["DOMString"](V, prefix, context, opts); }; webidl.converters["RequestRedirect"] = webidl.createEnumConverter( "RequestRedirect", [ "follow", "error", "manual", ], ); webidl.converters["RequestCache"] = webidl.createEnumConverter( "RequestCache", [ "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached", ], ); webidl.converters["RequestCredentials"] = webidl.createEnumConverter( "RequestCredentials", [ "omit", "same-origin", "include", ], ); webidl.converters["RequestMode"] = webidl.createEnumConverter( "RequestMode", [ "navigate", "same-origin", "no-cors", "cors", ], ); webidl.converters["RequestPriority"] = webidl.createEnumConverter( "RequestPriority", [ "auto", "low", "high", ], ); webidl.converters["ReferrerPolicy"] = webidl.createEnumConverter( "ReferrerPolicy", [ "", "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url", ], ); webidl.converters["RequestInit"] = webidl.createDictionaryConverter( "RequestInit", [ { key: "method", converter: webidl.converters["ByteString"] }, { key: "headers", converter: webidl.converters["HeadersInit"] }, { key: "body", converter: webidl.createNullableConverter( webidl.converters["BodyInit_DOMString"], ), }, { key: "referrer", converter: webidl.converters["USVString"] }, { key: "referrerPolicy", converter: webidl.converters["ReferrerPolicy"] }, { key: "mode", converter: webidl.converters["RequestMode"] }, { key: "credentials", converter: webidl.converters["RequestCredentials"] }, { key: "cache", converter: webidl.converters["RequestCache"] }, { key: "redirect", converter: webidl.converters["RequestRedirect"] }, { key: "integrity", converter: webidl.converters["DOMString"] }, { key: "keepalive", converter: webidl.converters["boolean"] }, { key: "priority", converter: webidl.converters["RequestPriority"] }, { key: "signal", converter: webidl.createNullableConverter( webidl.converters["AbortSignal"], ), }, { key: "client", converter: webidl.converters.any }, ], ); /** * @param {Request} request * @returns {InnerRequest} */ function toInnerRequest(request) { return request[_request]; } function requestHeadersExposed(request) { return request?.[_headersCache] !== undefined; } function cacheRequestHeaders(request) { if (request?.[_headersCache] !== undefined) { headerListFromHeaders(request[_headersCache]); } } /** * @param {InnerRequest} inner * @param {"request" | "immutable" | "request-no-cors" | "response" | "none"} guard * @returns {Request} */ function fromInnerRequest(inner, guard) { const request = new Request(_brand); request[_request] = inner; request[_headersGuard] = guard; return request; } const MESSAGE_REQUEST_CANCELLED = "The request has been cancelled."; function abortRequest(request) { if (request[_signalCache] !== undefined) { request[_signal][signalAbort]( new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), ); } else { request[_signalCache] = false; } } function getCachedAbortSignal(request) { return request[_signalCache]; } // For testing internals.getCachedAbortSignal = getCachedAbortSignal; return { abortRequest, cacheRequestHeaders, fromInnerRequest, newInnerRequest, processUrlList, Request, RequestPrototype, requestHeadersExposed, toInnerRequest, }; })();