request.mjs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. // src/request.ts
  2. import { Http2ServerRequest } from "http2";
  3. import { Readable } from "stream";
  4. var RequestError = class extends Error {
  5. constructor(message, options) {
  6. super(message, options);
  7. this.name = "RequestError";
  8. }
  9. };
  10. var toRequestError = (e) => {
  11. if (e instanceof RequestError) {
  12. return e;
  13. }
  14. return new RequestError(e.message, { cause: e });
  15. };
  16. var GlobalRequest = global.Request;
  17. var Request = class extends GlobalRequest {
  18. constructor(input, options) {
  19. if (typeof input === "object" && getRequestCache in input) {
  20. input = input[getRequestCache]();
  21. }
  22. if (typeof options?.body?.getReader !== "undefined") {
  23. ;
  24. options.duplex ??= "half";
  25. }
  26. super(input, options);
  27. }
  28. };
  29. var newHeadersFromIncoming = (incoming) => {
  30. const headerRecord = [];
  31. const rawHeaders = incoming.rawHeaders;
  32. for (let i = 0; i < rawHeaders.length; i += 2) {
  33. const { [i]: key, [i + 1]: value } = rawHeaders;
  34. if (key.charCodeAt(0) !== /*:*/
  35. 58) {
  36. headerRecord.push([key, value]);
  37. }
  38. }
  39. return new Headers(headerRecord);
  40. };
  41. var wrapBodyStream = Symbol("wrapBodyStream");
  42. var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
  43. const init = {
  44. method,
  45. headers,
  46. signal: abortController.signal
  47. };
  48. if (method === "TRACE") {
  49. init.method = "GET";
  50. const req = new Request(url, init);
  51. Object.defineProperty(req, "method", {
  52. get() {
  53. return "TRACE";
  54. }
  55. });
  56. return req;
  57. }
  58. if (!(method === "GET" || method === "HEAD")) {
  59. if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
  60. init.body = new ReadableStream({
  61. start(controller) {
  62. controller.enqueue(incoming.rawBody);
  63. controller.close();
  64. }
  65. });
  66. } else if (incoming[wrapBodyStream]) {
  67. let reader;
  68. init.body = new ReadableStream({
  69. async pull(controller) {
  70. try {
  71. reader ||= Readable.toWeb(incoming).getReader();
  72. const { done, value } = await reader.read();
  73. if (done) {
  74. controller.close();
  75. } else {
  76. controller.enqueue(value);
  77. }
  78. } catch (error) {
  79. controller.error(error);
  80. }
  81. }
  82. });
  83. } else {
  84. init.body = Readable.toWeb(incoming);
  85. }
  86. }
  87. return new Request(url, init);
  88. };
  89. var getRequestCache = Symbol("getRequestCache");
  90. var requestCache = Symbol("requestCache");
  91. var incomingKey = Symbol("incomingKey");
  92. var urlKey = Symbol("urlKey");
  93. var headersKey = Symbol("headersKey");
  94. var abortControllerKey = Symbol("abortControllerKey");
  95. var getAbortController = Symbol("getAbortController");
  96. var requestPrototype = {
  97. get method() {
  98. return this[incomingKey].method || "GET";
  99. },
  100. get url() {
  101. return this[urlKey];
  102. },
  103. get headers() {
  104. return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
  105. },
  106. [getAbortController]() {
  107. this[getRequestCache]();
  108. return this[abortControllerKey];
  109. },
  110. [getRequestCache]() {
  111. this[abortControllerKey] ||= new AbortController();
  112. return this[requestCache] ||= newRequestFromIncoming(
  113. this.method,
  114. this[urlKey],
  115. this.headers,
  116. this[incomingKey],
  117. this[abortControllerKey]
  118. );
  119. }
  120. };
  121. [
  122. "body",
  123. "bodyUsed",
  124. "cache",
  125. "credentials",
  126. "destination",
  127. "integrity",
  128. "mode",
  129. "redirect",
  130. "referrer",
  131. "referrerPolicy",
  132. "signal",
  133. "keepalive"
  134. ].forEach((k) => {
  135. Object.defineProperty(requestPrototype, k, {
  136. get() {
  137. return this[getRequestCache]()[k];
  138. }
  139. });
  140. });
  141. ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
  142. Object.defineProperty(requestPrototype, k, {
  143. value: function() {
  144. return this[getRequestCache]()[k]();
  145. }
  146. });
  147. });
  148. Object.setPrototypeOf(requestPrototype, Request.prototype);
  149. var newRequest = (incoming, defaultHostname) => {
  150. const req = Object.create(requestPrototype);
  151. req[incomingKey] = incoming;
  152. const incomingUrl = incoming.url || "";
  153. if (incomingUrl[0] !== "/" && // short-circuit for performance. most requests are relative URL.
  154. (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
  155. if (incoming instanceof Http2ServerRequest) {
  156. throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
  157. }
  158. try {
  159. const url2 = new URL(incomingUrl);
  160. req[urlKey] = url2.href;
  161. } catch (e) {
  162. throw new RequestError("Invalid absolute URL", { cause: e });
  163. }
  164. return req;
  165. }
  166. const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
  167. if (!host) {
  168. throw new RequestError("Missing host header");
  169. }
  170. let scheme;
  171. if (incoming instanceof Http2ServerRequest) {
  172. scheme = incoming.scheme;
  173. if (!(scheme === "http" || scheme === "https")) {
  174. throw new RequestError("Unsupported scheme");
  175. }
  176. } else {
  177. scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
  178. }
  179. const url = new URL(`${scheme}://${host}${incomingUrl}`);
  180. if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
  181. throw new RequestError("Invalid host header");
  182. }
  183. req[urlKey] = url.href;
  184. return req;
  185. };
  186. export {
  187. GlobalRequest,
  188. Request,
  189. RequestError,
  190. abortControllerKey,
  191. getAbortController,
  192. newRequest,
  193. toRequestError,
  194. wrapBodyStream
  195. };