| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 |
- // src/listener.ts
- import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
- // src/request.ts
- import { Http2ServerRequest } from "http2";
- import { Readable } from "stream";
- var RequestError = class extends Error {
- constructor(message, options) {
- super(message, options);
- this.name = "RequestError";
- }
- };
- var toRequestError = (e) => {
- if (e instanceof RequestError) {
- return e;
- }
- return new RequestError(e.message, { cause: e });
- };
- var GlobalRequest = global.Request;
- var Request = class extends GlobalRequest {
- constructor(input, options) {
- if (typeof input === "object" && getRequestCache in input) {
- input = input[getRequestCache]();
- }
- if (typeof options?.body?.getReader !== "undefined") {
- ;
- options.duplex ??= "half";
- }
- super(input, options);
- }
- };
- var newHeadersFromIncoming = (incoming) => {
- const headerRecord = [];
- const rawHeaders = incoming.rawHeaders;
- for (let i = 0; i < rawHeaders.length; i += 2) {
- const { [i]: key, [i + 1]: value } = rawHeaders;
- if (key.charCodeAt(0) !== /*:*/
- 58) {
- headerRecord.push([key, value]);
- }
- }
- return new Headers(headerRecord);
- };
- var wrapBodyStream = Symbol("wrapBodyStream");
- var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
- const init = {
- method,
- headers,
- signal: abortController.signal
- };
- if (method === "TRACE") {
- init.method = "GET";
- const req = new Request(url, init);
- Object.defineProperty(req, "method", {
- get() {
- return "TRACE";
- }
- });
- return req;
- }
- if (!(method === "GET" || method === "HEAD")) {
- if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
- init.body = new ReadableStream({
- start(controller) {
- controller.enqueue(incoming.rawBody);
- controller.close();
- }
- });
- } else if (incoming[wrapBodyStream]) {
- let reader;
- init.body = new ReadableStream({
- async pull(controller) {
- try {
- reader ||= Readable.toWeb(incoming).getReader();
- const { done, value } = await reader.read();
- if (done) {
- controller.close();
- } else {
- controller.enqueue(value);
- }
- } catch (error) {
- controller.error(error);
- }
- }
- });
- } else {
- init.body = Readable.toWeb(incoming);
- }
- }
- return new Request(url, init);
- };
- var getRequestCache = Symbol("getRequestCache");
- var requestCache = Symbol("requestCache");
- var incomingKey = Symbol("incomingKey");
- var urlKey = Symbol("urlKey");
- var headersKey = Symbol("headersKey");
- var abortControllerKey = Symbol("abortControllerKey");
- var getAbortController = Symbol("getAbortController");
- var requestPrototype = {
- get method() {
- return this[incomingKey].method || "GET";
- },
- get url() {
- return this[urlKey];
- },
- get headers() {
- return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
- },
- [getAbortController]() {
- this[getRequestCache]();
- return this[abortControllerKey];
- },
- [getRequestCache]() {
- this[abortControllerKey] ||= new AbortController();
- return this[requestCache] ||= newRequestFromIncoming(
- this.method,
- this[urlKey],
- this.headers,
- this[incomingKey],
- this[abortControllerKey]
- );
- }
- };
- [
- "body",
- "bodyUsed",
- "cache",
- "credentials",
- "destination",
- "integrity",
- "mode",
- "redirect",
- "referrer",
- "referrerPolicy",
- "signal",
- "keepalive"
- ].forEach((k) => {
- Object.defineProperty(requestPrototype, k, {
- get() {
- return this[getRequestCache]()[k];
- }
- });
- });
- ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
- Object.defineProperty(requestPrototype, k, {
- value: function() {
- return this[getRequestCache]()[k]();
- }
- });
- });
- Object.setPrototypeOf(requestPrototype, Request.prototype);
- var newRequest = (incoming, defaultHostname) => {
- const req = Object.create(requestPrototype);
- req[incomingKey] = incoming;
- const incomingUrl = incoming.url || "";
- if (incomingUrl[0] !== "/" && // short-circuit for performance. most requests are relative URL.
- (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
- if (incoming instanceof Http2ServerRequest) {
- throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
- }
- try {
- const url2 = new URL(incomingUrl);
- req[urlKey] = url2.href;
- } catch (e) {
- throw new RequestError("Invalid absolute URL", { cause: e });
- }
- return req;
- }
- const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
- if (!host) {
- throw new RequestError("Missing host header");
- }
- let scheme;
- if (incoming instanceof Http2ServerRequest) {
- scheme = incoming.scheme;
- if (!(scheme === "http" || scheme === "https")) {
- throw new RequestError("Unsupported scheme");
- }
- } else {
- scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
- }
- const url = new URL(`${scheme}://${host}${incomingUrl}`);
- if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
- throw new RequestError("Invalid host header");
- }
- req[urlKey] = url.href;
- return req;
- };
- // src/response.ts
- var responseCache = Symbol("responseCache");
- var getResponseCache = Symbol("getResponseCache");
- var cacheKey = Symbol("cache");
- var GlobalResponse = global.Response;
- var Response2 = class _Response {
- #body;
- #init;
- [getResponseCache]() {
- delete this[cacheKey];
- return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
- }
- constructor(body, init) {
- let headers;
- this.#body = body;
- if (init instanceof _Response) {
- const cachedGlobalResponse = init[responseCache];
- if (cachedGlobalResponse) {
- this.#init = cachedGlobalResponse;
- this[getResponseCache]();
- return;
- } else {
- this.#init = init.#init;
- headers = new Headers(init.#init.headers);
- }
- } else {
- this.#init = init;
- }
- if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
- ;
- this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
- }
- }
- get headers() {
- const cache = this[cacheKey];
- if (cache) {
- if (!(cache[2] instanceof Headers)) {
- cache[2] = new Headers(
- cache[2] || { "content-type": "text/plain; charset=UTF-8" }
- );
- }
- return cache[2];
- }
- return this[getResponseCache]().headers;
- }
- get status() {
- return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
- }
- get ok() {
- const status = this.status;
- return status >= 200 && status < 300;
- }
- };
- ["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
- Object.defineProperty(Response2.prototype, k, {
- get() {
- return this[getResponseCache]()[k];
- }
- });
- });
- ["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
- Object.defineProperty(Response2.prototype, k, {
- value: function() {
- return this[getResponseCache]()[k]();
- }
- });
- });
- Object.setPrototypeOf(Response2, GlobalResponse);
- Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
- // src/utils.ts
- async function readWithoutBlocking(readPromise) {
- return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(void 0))]);
- }
- function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
- const cancel = (error) => {
- reader.cancel(error).catch(() => {
- });
- };
- writable.on("close", cancel);
- writable.on("error", cancel);
- (currentReadPromise ?? reader.read()).then(flow, handleStreamError);
- return reader.closed.finally(() => {
- writable.off("close", cancel);
- writable.off("error", cancel);
- });
- function handleStreamError(error) {
- if (error) {
- writable.destroy(error);
- }
- }
- function onDrain() {
- reader.read().then(flow, handleStreamError);
- }
- function flow({ done, value }) {
- try {
- if (done) {
- writable.end();
- } else if (!writable.write(value)) {
- writable.once("drain", onDrain);
- } else {
- return reader.read().then(flow, handleStreamError);
- }
- } catch (e) {
- handleStreamError(e);
- }
- }
- }
- function writeFromReadableStream(stream, writable) {
- if (stream.locked) {
- throw new TypeError("ReadableStream is locked.");
- } else if (writable.destroyed) {
- return;
- }
- return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
- }
- var buildOutgoingHttpHeaders = (headers) => {
- const res = {};
- if (!(headers instanceof Headers)) {
- headers = new Headers(headers ?? void 0);
- }
- const cookies = [];
- for (const [k, v] of headers) {
- if (k === "set-cookie") {
- cookies.push(v);
- } else {
- res[k] = v;
- }
- }
- if (cookies.length > 0) {
- res["set-cookie"] = cookies;
- }
- res["content-type"] ??= "text/plain; charset=UTF-8";
- return res;
- };
- // src/utils/response/constants.ts
- var X_ALREADY_SENT = "x-hono-already-sent";
- // src/globals.ts
- import crypto from "crypto";
- if (typeof global.crypto === "undefined") {
- global.crypto = crypto;
- }
- // src/listener.ts
- var outgoingEnded = Symbol("outgoingEnded");
- var incomingDraining = Symbol("incomingDraining");
- var DRAIN_TIMEOUT_MS = 500;
- var MAX_DRAIN_BYTES = 64 * 1024 * 1024;
- var drainIncoming = (incoming) => {
- const incomingWithDrainState = incoming;
- if (incoming.destroyed || incomingWithDrainState[incomingDraining]) {
- return;
- }
- incomingWithDrainState[incomingDraining] = true;
- if (incoming instanceof Http2ServerRequest2) {
- try {
- ;
- incoming.stream?.close?.(h2constants.NGHTTP2_NO_ERROR);
- } catch {
- }
- return;
- }
- let bytesRead = 0;
- const cleanup = () => {
- clearTimeout(timer);
- incoming.off("data", onData);
- incoming.off("end", cleanup);
- incoming.off("error", cleanup);
- };
- const forceClose = () => {
- cleanup();
- const socket = incoming.socket;
- if (socket && !socket.destroyed) {
- socket.destroySoon();
- }
- };
- const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
- timer.unref?.();
- const onData = (chunk) => {
- bytesRead += chunk.length;
- if (bytesRead > MAX_DRAIN_BYTES) {
- forceClose();
- }
- };
- incoming.on("data", onData);
- incoming.on("end", cleanup);
- incoming.on("error", cleanup);
- incoming.resume();
- };
- var handleRequestError = () => new Response(null, {
- status: 400
- });
- var handleFetchError = (e) => new Response(null, {
- status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
- });
- var handleResponseError = (e, outgoing) => {
- const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
- if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
- console.info("The user aborted a request.");
- } else {
- console.error(e);
- if (!outgoing.headersSent) {
- outgoing.writeHead(500, { "Content-Type": "text/plain" });
- }
- outgoing.end(`Error: ${err.message}`);
- outgoing.destroy(err);
- }
- };
- var flushHeaders = (outgoing) => {
- if ("flushHeaders" in outgoing && outgoing.writable) {
- outgoing.flushHeaders();
- }
- };
- var responseViaCache = async (res, outgoing) => {
- let [status, body, header] = res[cacheKey];
- let hasContentLength = false;
- if (!header) {
- header = { "content-type": "text/plain; charset=UTF-8" };
- } else if (header instanceof Headers) {
- hasContentLength = header.has("content-length");
- header = buildOutgoingHttpHeaders(header);
- } else if (Array.isArray(header)) {
- const headerObj = new Headers(header);
- hasContentLength = headerObj.has("content-length");
- header = buildOutgoingHttpHeaders(headerObj);
- } else {
- for (const key in header) {
- if (key.length === 14 && key.toLowerCase() === "content-length") {
- hasContentLength = true;
- break;
- }
- }
- }
- if (!hasContentLength) {
- if (typeof body === "string") {
- header["Content-Length"] = Buffer.byteLength(body);
- } else if (body instanceof Uint8Array) {
- header["Content-Length"] = body.byteLength;
- } else if (body instanceof Blob) {
- header["Content-Length"] = body.size;
- }
- }
- outgoing.writeHead(status, header);
- if (typeof body === "string" || body instanceof Uint8Array) {
- outgoing.end(body);
- } else if (body instanceof Blob) {
- outgoing.end(new Uint8Array(await body.arrayBuffer()));
- } else {
- flushHeaders(outgoing);
- await writeFromReadableStream(body, outgoing)?.catch(
- (e) => handleResponseError(e, outgoing)
- );
- }
- ;
- outgoing[outgoingEnded]?.();
- };
- var isPromise = (res) => typeof res.then === "function";
- var responseViaResponseObject = async (res, outgoing, options = {}) => {
- if (isPromise(res)) {
- if (options.errorHandler) {
- try {
- res = await res;
- } catch (err) {
- const errRes = await options.errorHandler(err);
- if (!errRes) {
- return;
- }
- res = errRes;
- }
- } else {
- res = await res.catch(handleFetchError);
- }
- }
- if (cacheKey in res) {
- return responseViaCache(res, outgoing);
- }
- const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
- if (res.body) {
- const reader = res.body.getReader();
- const values = [];
- let done = false;
- let currentReadPromise = void 0;
- if (resHeaderRecord["transfer-encoding"] !== "chunked") {
- let maxReadCount = 2;
- for (let i = 0; i < maxReadCount; i++) {
- currentReadPromise ||= reader.read();
- const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
- console.error(e);
- done = true;
- });
- if (!chunk) {
- if (i === 1) {
- await new Promise((resolve) => setTimeout(resolve));
- maxReadCount = 3;
- continue;
- }
- break;
- }
- currentReadPromise = void 0;
- if (chunk.value) {
- values.push(chunk.value);
- }
- if (chunk.done) {
- done = true;
- break;
- }
- }
- if (done && !("content-length" in resHeaderRecord)) {
- resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
- }
- }
- outgoing.writeHead(res.status, resHeaderRecord);
- values.forEach((value) => {
- ;
- outgoing.write(value);
- });
- if (done) {
- outgoing.end();
- } else {
- if (values.length === 0) {
- flushHeaders(outgoing);
- }
- await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
- }
- } else if (resHeaderRecord[X_ALREADY_SENT]) {
- } else {
- outgoing.writeHead(res.status, resHeaderRecord);
- outgoing.end();
- }
- ;
- outgoing[outgoingEnded]?.();
- };
- var getRequestListener = (fetchCallback, options = {}) => {
- const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
- if (options.overrideGlobalObjects !== false && global.Request !== Request) {
- Object.defineProperty(global, "Request", {
- value: Request
- });
- Object.defineProperty(global, "Response", {
- value: Response2
- });
- }
- return async (incoming, outgoing) => {
- let res, req;
- try {
- req = newRequest(incoming, options.hostname);
- let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
- if (!incomingEnded) {
- ;
- incoming[wrapBodyStream] = true;
- incoming.on("end", () => {
- incomingEnded = true;
- });
- if (incoming instanceof Http2ServerRequest2) {
- ;
- outgoing[outgoingEnded] = () => {
- if (!incomingEnded) {
- setTimeout(() => {
- if (!incomingEnded) {
- setTimeout(() => {
- drainIncoming(incoming);
- });
- }
- });
- }
- };
- }
- outgoing.on("finish", () => {
- if (!incomingEnded) {
- drainIncoming(incoming);
- }
- });
- }
- outgoing.on("close", () => {
- const abortController = req[abortControllerKey];
- if (abortController) {
- if (incoming.errored) {
- req[abortControllerKey].abort(incoming.errored.toString());
- } else if (!outgoing.writableFinished) {
- req[abortControllerKey].abort("Client connection prematurely closed.");
- }
- }
- if (!incomingEnded) {
- setTimeout(() => {
- if (!incomingEnded) {
- setTimeout(() => {
- drainIncoming(incoming);
- });
- }
- });
- }
- });
- res = fetchCallback(req, { incoming, outgoing });
- if (cacheKey in res) {
- return responseViaCache(res, outgoing);
- }
- } catch (e) {
- if (!res) {
- if (options.errorHandler) {
- res = await options.errorHandler(req ? e : toRequestError(e));
- if (!res) {
- return;
- }
- } else if (!req) {
- res = handleRequestError();
- } else {
- res = handleFetchError(e);
- }
- } else {
- return handleResponseError(e, outgoing);
- }
- }
- try {
- return await responseViaResponseObject(res, outgoing, options);
- } catch (e) {
- return handleResponseError(e, outgoing);
- }
- };
- };
- // src/vercel.ts
- var handle = (app) => {
- return getRequestListener(app.fetch);
- };
- export {
- handle
- };
|