mirror of
https://github.com/honojs/hono.git
synced 2024-11-21 18:18:57 +01:00
feat(cloudflare-pages): Add Cloudflare Pages middleware handler (#3028)
* Add Cloudflare Pages middleware handler * fix: handle HTTPException * fix: handle context.error * fix: remove HonoRequest from conninfo test
This commit is contained in:
parent
a8a84f3055
commit
204e10b7ce
@ -1,6 +1,8 @@
|
||||
import { getCookie } from '../../helper/cookie'
|
||||
import { Hono } from '../../hono'
|
||||
import { HTTPException } from '../../http-exception'
|
||||
import type { EventContext } from './handler'
|
||||
import { handle } from './handler'
|
||||
import { handle, handleMiddleware } from './handler'
|
||||
|
||||
type Env = {
|
||||
Bindings: {
|
||||
@ -49,3 +51,193 @@ describe('Adapter for Cloudflare Pages', () => {
|
||||
expect(() => handler({ request })).toThrowError('Custom Error')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Middleware adapter for Cloudflare Pages', () => {
|
||||
it('Should return the middleware response', async () => {
|
||||
const request = new Request('http://localhost/api/foo', {
|
||||
headers: {
|
||||
Cookie: 'my_cookie=1234',
|
||||
},
|
||||
})
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(async (c, next) => {
|
||||
const cookie = getCookie(c, 'my_cookie')
|
||||
|
||||
await next()
|
||||
|
||||
return c.json({ cookie, response: await c.res.json() })
|
||||
})
|
||||
|
||||
const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
expect(await res.json()).toEqual({
|
||||
cookie: '1234',
|
||||
response: 'From Cloudflare Pages',
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return the middleware response when exceptions are handled', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(async (c, next) => {
|
||||
await next()
|
||||
|
||||
return c.json({ error: c.error?.message })
|
||||
})
|
||||
|
||||
const next = vi.fn().mockRejectedValue(new Error('Error from next()'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
expect(await res.json()).toEqual({
|
||||
error: 'Error from next()',
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return the middleware response if next() is not called', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(async (c) => {
|
||||
return c.json({ response: 'Skip Cloudflare Pages' })
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
|
||||
expect(await res.json()).toEqual({
|
||||
response: 'Skip Cloudflare Pages',
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return the Pages response if the middleware does not return a response', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
expect(await res.json()).toEqual('From Cloudflare Pages')
|
||||
})
|
||||
|
||||
it('Should handle a HTTPException by returning error.getResponse()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => {
|
||||
const res = new Response('Unauthorized', { status: 401 })
|
||||
throw new HTTPException(401, { res })
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
|
||||
expect(res.status).toBe(401)
|
||||
expect(await res.text()).toBe('Unauthorized')
|
||||
})
|
||||
|
||||
it('Should handle an HTTPException thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi
|
||||
.fn()
|
||||
.mockRejectedValue(new HTTPException(401, { res: Response.json('Unauthorized') }))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const res = await handler({ request, env, next })
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
|
||||
expect(await res.json()).toEqual('Unauthorized')
|
||||
})
|
||||
|
||||
it('Should handle an Error thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockRejectedValue(new Error('Error from next()'))
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Error from next()')
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should handle a non-Error thrown by next()', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware((c, next) => next())
|
||||
|
||||
const next = vi.fn().mockRejectedValue('Error from next()')
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Error from next()')
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should rethrow an Error', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => {
|
||||
throw new Error('Something went wrong')
|
||||
})
|
||||
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Something went wrong')
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('Should rethrow non-Error exceptions', async () => {
|
||||
const request = new Request('http://localhost/api/foo')
|
||||
const env = {
|
||||
TOKEN: 'HONOISCOOL',
|
||||
}
|
||||
const handler = handleMiddleware(() => Promise.reject('Something went wrong'))
|
||||
const next = vi.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await expect(handler({ request, env, next })).rejects.toThrowError('Something went wrong')
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Context } from '../../context'
|
||||
import type { Hono } from '../../hono'
|
||||
import type { MiddlewareHandler } from '../../types'
|
||||
import { HTTPException } from '../../http-exception'
|
||||
import type { Env, Input, MiddlewareHandler } from '../../types'
|
||||
|
||||
// Ref: https://github.com/cloudflare/workerd/blob/main/types/defines/pages.d.ts
|
||||
|
||||
@ -18,6 +20,13 @@ export type EventContext<Env = {}, P extends string = any, Data = {}> = {
|
||||
data: Data
|
||||
}
|
||||
|
||||
declare type PagesFunction<
|
||||
Env = unknown,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Params extends string = any,
|
||||
Data extends Record<string, unknown> = Record<string, unknown>
|
||||
> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>
|
||||
|
||||
interface HandleInterface {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(app: Hono<any, any, any>): (eventContext: EventContext) => Response | Promise<Response>
|
||||
@ -34,6 +43,54 @@ export const handle: HandleInterface = (app: Hono) => (eventContext: EventContex
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function handleMiddleware<E extends Env = any, P extends string = any, I extends Input = {}>(
|
||||
middleware: MiddlewareHandler<E, P, I>
|
||||
): PagesFunction {
|
||||
return async (executionCtx) => {
|
||||
const context = new Context(executionCtx.request, {
|
||||
env: executionCtx.env,
|
||||
executionCtx,
|
||||
})
|
||||
|
||||
let response: Response | void = undefined
|
||||
|
||||
try {
|
||||
response = await middleware(context, async () => {
|
||||
try {
|
||||
context.res = await executionCtx.next()
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
context.error = error
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
context.error = error
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
if (response) {
|
||||
return response
|
||||
}
|
||||
|
||||
if (context.error instanceof HTTPException) {
|
||||
return context.error.getResponse()
|
||||
}
|
||||
|
||||
if (context.error) {
|
||||
throw context.error
|
||||
}
|
||||
|
||||
return context.res
|
||||
}
|
||||
}
|
||||
|
||||
declare abstract class FetcherLike {
|
||||
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>
|
||||
}
|
||||
|
@ -3,5 +3,5 @@
|
||||
* Cloudflare Pages Adapter for Hono.
|
||||
*/
|
||||
|
||||
export { handle, serveStatic } from './handler'
|
||||
export { handle, handleMiddleware, serveStatic } from './handler'
|
||||
export type { EventContext } from './handler'
|
||||
|
Loading…
Reference in New Issue
Block a user