diff --git a/src/hono-base.ts b/src/hono-base.ts index f3e9ad030..9d7fb0540 100644 --- a/src/hono-base.ts +++ b/src/hono-base.ts @@ -27,7 +27,7 @@ import type { RouterRoute, Schema, } from './types' -import { getPath, getPathNoStrict, getQueryStrings, mergePath } from './utils/url' +import { getPath, getPathNoStrict, mergePath } from './utils/url' /** * Symbol used to mark a composed handler. @@ -91,6 +91,15 @@ export type HonoOptions = { getPath?: GetPath } +type MountOptionHandler = (c: Context) => unknown +type MountReplaceRequest = (originalRequest: Request) => Request +type MountOptions = + | MountOptionHandler + | { + optionHandler?: MountOptionHandler + replaceRequest?: MountReplaceRequest + } + class Hono { get!: HandlerInterface post!: HandlerInterface @@ -296,7 +305,7 @@ class Hono req, + * }) + * ``` */ mount( path: string, applicationHandler: (request: Request, ...args: any) => Response | Promise, - optionHandler?: (c: Context) => unknown + options?: MountOptions ): Hono { - const mergedPath = mergePath(this._basePath, path) - const pathPrefixLength = mergedPath === '/' ? 0 : mergedPath.length + // handle options + let replaceRequest: MountReplaceRequest | undefined + let optionHandler: MountOptionHandler | undefined + if (options) { + if (typeof options === 'function') { + optionHandler = options + } else { + optionHandler = options.optionHandler + replaceRequest = options.replaceRequest + } + } + + // prepare handlers for request + const getOptions: (c: Context) => unknown[] = optionHandler + ? (c) => { + const options = optionHandler!(c) + return Array.isArray(options) ? options : [options] + } + : (c) => { + let executionContext: ExecutionContext | undefined = undefined + try { + executionContext = c.executionCtx + } catch {} // Do nothing + return [c.env, executionContext] + } + replaceRequest ||= (() => { + const mergedPath = mergePath(this._basePath, path) + const pathPrefixLength = mergedPath === '/' ? 0 : mergedPath.length + return (request) => { + const url = new URL(request.url) + url.pathname = url.pathname.slice(pathPrefixLength) || '/' + return new Request(url, request) + } + })() const handler: MiddlewareHandler = async (c, next) => { - let executionContext: ExecutionContext | undefined = undefined - try { - executionContext = c.executionCtx - } catch {} // Do nothing - const options = optionHandler ? optionHandler(c) : [c.env, executionContext] - const optionsArray = Array.isArray(options) ? options : [options] - - const queryStrings = getQueryStrings(c.req.url) - const res = await applicationHandler( - new Request( - new URL((c.req.path.slice(pathPrefixLength) || '/') + queryStrings, c.req.url), - c.req.raw - ), - ...optionsArray - ) + const res = await applicationHandler(replaceRequest!(c.req.raw), ...getOptions(c)) if (res) { return res diff --git a/src/hono.test.ts b/src/hono.test.ts index 7dc81526d..ed81683f5 100644 --- a/src/hono.test.ts +++ b/src/hono.test.ts @@ -2661,6 +2661,27 @@ describe('app.mount()', () => { expect(await res.text()).toBe('Not Found from AnotherApp') }) }) + + describe('With replaceRequest option', () => { + const anotherApp = (req: Request) => { + const path = getPath(req) + if (path === '/app') { + return new Response(getPath(req)) + } + return new Response(null, { status: 404 }) + } + + const app = new Hono() + app.mount('/app', anotherApp, { + replaceRequest: (req) => req, + }) + + it('Should return 200 response with the correct path', async () => { + const res = await app.request('/app') + expect(res.status).toBe(200) + expect(await res.text()).toBe('/app') + }) + }) }) describe('HEAD method', () => {