request.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. // src/request.ts
  2. import { HTTPException } from "./http-exception.js";
  3. import { GET_MATCH_RESULT } from "./request/constants.js";
  4. import { parseBody } from "./utils/body.js";
  5. import { decodeURIComponent_, getQueryParam, getQueryParams, tryDecode } from "./utils/url.js";
  6. var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
  7. var HonoRequest = class {
  8. /**
  9. * `.raw` can get the raw Request object.
  10. *
  11. * @see {@link https://hono.dev/docs/api/request#raw}
  12. *
  13. * @example
  14. * ```ts
  15. * // For Cloudflare Workers
  16. * app.post('/', async (c) => {
  17. * const metadata = c.req.raw.cf?.hostMetadata?
  18. * ...
  19. * })
  20. * ```
  21. */
  22. raw;
  23. #validatedData;
  24. // Short name of validatedData
  25. #matchResult;
  26. routeIndex = 0;
  27. /**
  28. * `.path` can get the pathname of the request.
  29. *
  30. * @see {@link https://hono.dev/docs/api/request#path}
  31. *
  32. * @example
  33. * ```ts
  34. * app.get('/about/me', (c) => {
  35. * const pathname = c.req.path // `/about/me`
  36. * })
  37. * ```
  38. */
  39. path;
  40. bodyCache = {};
  41. constructor(request, path = "/", matchResult = [[]]) {
  42. this.raw = request;
  43. this.path = path;
  44. this.#matchResult = matchResult;
  45. this.#validatedData = {};
  46. }
  47. param(key) {
  48. return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
  49. }
  50. #getDecodedParam(key) {
  51. const paramKey = this.#matchResult[0][this.routeIndex][1][key];
  52. const param = this.#getParamValue(paramKey);
  53. return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
  54. }
  55. #getAllDecodedParams() {
  56. const decoded = {};
  57. const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
  58. for (const key of keys) {
  59. const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
  60. if (value !== void 0) {
  61. decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
  62. }
  63. }
  64. return decoded;
  65. }
  66. #getParamValue(paramKey) {
  67. return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
  68. }
  69. query(key) {
  70. return getQueryParam(this.url, key);
  71. }
  72. queries(key) {
  73. return getQueryParams(this.url, key);
  74. }
  75. header(name) {
  76. if (name) {
  77. return this.raw.headers.get(name) ?? void 0;
  78. }
  79. const headerData = {};
  80. this.raw.headers.forEach((value, key) => {
  81. headerData[key] = value;
  82. });
  83. return headerData;
  84. }
  85. async parseBody(options) {
  86. return parseBody(this, options);
  87. }
  88. #cachedBody = (key) => {
  89. const { bodyCache, raw } = this;
  90. const cachedBody = bodyCache[key];
  91. if (cachedBody) {
  92. return cachedBody;
  93. }
  94. const anyCachedKey = Object.keys(bodyCache)[0];
  95. if (anyCachedKey) {
  96. return bodyCache[anyCachedKey].then((body) => {
  97. if (anyCachedKey === "json") {
  98. body = JSON.stringify(body);
  99. }
  100. return new Response(body)[key]();
  101. });
  102. }
  103. return bodyCache[key] = raw[key]();
  104. };
  105. /**
  106. * `.json()` can parse Request body of type `application/json`
  107. *
  108. * @see {@link https://hono.dev/docs/api/request#json}
  109. *
  110. * @example
  111. * ```ts
  112. * app.post('/entry', async (c) => {
  113. * const body = await c.req.json()
  114. * })
  115. * ```
  116. */
  117. json() {
  118. return this.#cachedBody("text").then((text) => JSON.parse(text));
  119. }
  120. /**
  121. * `.text()` can parse Request body of type `text/plain`
  122. *
  123. * @see {@link https://hono.dev/docs/api/request#text}
  124. *
  125. * @example
  126. * ```ts
  127. * app.post('/entry', async (c) => {
  128. * const body = await c.req.text()
  129. * })
  130. * ```
  131. */
  132. text() {
  133. return this.#cachedBody("text");
  134. }
  135. /**
  136. * `.arrayBuffer()` parse Request body as an `ArrayBuffer`
  137. *
  138. * @see {@link https://hono.dev/docs/api/request#arraybuffer}
  139. *
  140. * @example
  141. * ```ts
  142. * app.post('/entry', async (c) => {
  143. * const body = await c.req.arrayBuffer()
  144. * })
  145. * ```
  146. */
  147. arrayBuffer() {
  148. return this.#cachedBody("arrayBuffer");
  149. }
  150. /**
  151. * Parses the request body as a `Blob`.
  152. * @example
  153. * ```ts
  154. * app.post('/entry', async (c) => {
  155. * const body = await c.req.blob();
  156. * });
  157. * ```
  158. * @see https://hono.dev/docs/api/request#blob
  159. */
  160. blob() {
  161. return this.#cachedBody("blob");
  162. }
  163. /**
  164. * Parses the request body as `FormData`.
  165. * @example
  166. * ```ts
  167. * app.post('/entry', async (c) => {
  168. * const body = await c.req.formData();
  169. * });
  170. * ```
  171. * @see https://hono.dev/docs/api/request#formdata
  172. */
  173. formData() {
  174. return this.#cachedBody("formData");
  175. }
  176. /**
  177. * Adds validated data to the request.
  178. *
  179. * @param target - The target of the validation.
  180. * @param data - The validated data to add.
  181. */
  182. addValidatedData(target, data) {
  183. this.#validatedData[target] = data;
  184. }
  185. valid(target) {
  186. return this.#validatedData[target];
  187. }
  188. /**
  189. * `.url()` can get the request url strings.
  190. *
  191. * @see {@link https://hono.dev/docs/api/request#url}
  192. *
  193. * @example
  194. * ```ts
  195. * app.get('/about/me', (c) => {
  196. * const url = c.req.url // `http://localhost:8787/about/me`
  197. * ...
  198. * })
  199. * ```
  200. */
  201. get url() {
  202. return this.raw.url;
  203. }
  204. /**
  205. * `.method()` can get the method name of the request.
  206. *
  207. * @see {@link https://hono.dev/docs/api/request#method}
  208. *
  209. * @example
  210. * ```ts
  211. * app.get('/about/me', (c) => {
  212. * const method = c.req.method // `GET`
  213. * })
  214. * ```
  215. */
  216. get method() {
  217. return this.raw.method;
  218. }
  219. get [GET_MATCH_RESULT]() {
  220. return this.#matchResult;
  221. }
  222. /**
  223. * `.matchedRoutes()` can return a matched route in the handler
  224. *
  225. * @deprecated
  226. *
  227. * Use matchedRoutes helper defined in "hono/route" instead.
  228. *
  229. * @see {@link https://hono.dev/docs/api/request#matchedroutes}
  230. *
  231. * @example
  232. * ```ts
  233. * app.use('*', async function logger(c, next) {
  234. * await next()
  235. * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
  236. * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
  237. * console.log(
  238. * method,
  239. * ' ',
  240. * path,
  241. * ' '.repeat(Math.max(10 - path.length, 0)),
  242. * name,
  243. * i === c.req.routeIndex ? '<- respond from here' : ''
  244. * )
  245. * })
  246. * })
  247. * ```
  248. */
  249. get matchedRoutes() {
  250. return this.#matchResult[0].map(([[, route]]) => route);
  251. }
  252. /**
  253. * `routePath()` can retrieve the path registered within the handler
  254. *
  255. * @deprecated
  256. *
  257. * Use routePath helper defined in "hono/route" instead.
  258. *
  259. * @see {@link https://hono.dev/docs/api/request#routepath}
  260. *
  261. * @example
  262. * ```ts
  263. * app.get('/posts/:id', (c) => {
  264. * return c.json({ path: c.req.routePath })
  265. * })
  266. * ```
  267. */
  268. get routePath() {
  269. return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
  270. }
  271. };
  272. var cloneRawRequest = async (req) => {
  273. if (!req.raw.bodyUsed) {
  274. return req.raw.clone();
  275. }
  276. const cacheKey = Object.keys(req.bodyCache)[0];
  277. if (!cacheKey) {
  278. throw new HTTPException(500, {
  279. message: "Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly."
  280. });
  281. }
  282. const requestInit = {
  283. body: await req[cacheKey](),
  284. cache: req.raw.cache,
  285. credentials: req.raw.credentials,
  286. headers: req.header(),
  287. integrity: req.raw.integrity,
  288. keepalive: req.raw.keepalive,
  289. method: req.method,
  290. mode: req.raw.mode,
  291. redirect: req.raw.redirect,
  292. referrer: req.raw.referrer,
  293. referrerPolicy: req.raw.referrerPolicy,
  294. signal: req.raw.signal
  295. };
  296. return new Request(req.url, requestInit);
  297. };
  298. export {
  299. HonoRequest,
  300. cloneRawRequest
  301. };