Skip to content

Commit

Permalink
feat: Add onNotFound handler in adapters' serveStatic (#1825)
Browse files Browse the repository at this point in the history
* feat: add onNotFound handler in adapters' serveStatic

* test: add serveStatic onNotFound tests for bun and deno

* test: add serveStatic onNotFound tests for cloudflare workers
  • Loading branch information
Th1nkK1D authored Dec 26, 2023
1 parent 5b45921 commit b128d02
Show file tree
Hide file tree
Showing 8 changed files with 39 additions and 15 deletions.
7 changes: 4 additions & 3 deletions deno_dist/adapter/deno/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ServeStaticOptions = {
root?: string
path?: string
rewriteRequestPath?: (path: string) => string
onNotFound?: (path: string) => void | Promise<void>
}

const DEFAULT_DOCUMENT = 'index.html'
Expand Down Expand Up @@ -50,10 +51,10 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }) => {
}
// Return Response object with stream
return c.body(file.readable)
} else {
console.warn(`Static file: ${path} is not found`)
await next()
}

await options.onNotFound?.(path)
await next()
return
}
}
10 changes: 8 additions & 2 deletions runtime_tests/bun/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'bun:test'
import { describe, expect, it, mock, beforeEach } from 'bun:test'
import { serveStatic } from '../../src/adapter/bun'
import { Context } from '../../src/context'
import { env, getRuntimeKey } from '../../src/helper/adapter'
Expand Down Expand Up @@ -76,10 +76,11 @@ describe('Basic Auth Middleware', () => {

describe('Serve Static Middleware', () => {
const app = new Hono()
const onNotFound = mock(() => {})
app.all('/favicon.ico', serveStatic({ path: './runtime_tests/bun/favicon.ico' }))
app.all(
'/favicon-notfound.ico',
serveStatic({ path: './runtime_tests/bun/favicon-notfound.ico' })
serveStatic({ path: './runtime_tests/bun/favicon-notfound.ico', onNotFound })
)
app.use('/favicon-notfound.ico', async (c, next) => {
await next()
Expand All @@ -89,6 +90,7 @@ describe('Serve Static Middleware', () => {
'/static/*',
serveStatic({
root: './runtime_tests/bun/',
onNotFound,
})
)
app.get(
Expand All @@ -99,6 +101,8 @@ describe('Serve Static Middleware', () => {
})
)

beforeEach(() => onNotFound.mockClear())

it('Should return static file correctly', async () => {
const res = await app.request(new Request('http://localhost/favicon.ico'))
await res.arrayBuffer()
Expand All @@ -110,12 +114,14 @@ describe('Serve Static Middleware', () => {
const res = await app.request(new Request('http://localhost/favicon-notfound.ico'))
expect(res.status).toBe(404)
expect(res.headers.get('X-Custom')).toBe('Bun')
expect(onNotFound).toHaveBeenCalledWith('./runtime_tests/bun/favicon-notfound.ico')
})

it('Should return 200 response - /static/plain.txt', async () => {
const res = await app.request(new Request('http://localhost/static/plain.txt'))
expect(res.status).toBe(200)
expect(await res.text()).toBe('Bun!')
expect(onNotFound).not.toHaveBeenCalled()
})

it('Should return 200 response - /dot-static/plain.txt', async () => {
Expand Down
1 change: 1 addition & 0 deletions runtime_tests/deno/deps.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { assert, assertEquals, assertMatch } from 'https://deno.land/[email protected]/testing/asserts.ts'
export { assertSpyCall, assertSpyCalls, spy } from 'https://deno.land/[email protected]/testing/mock.ts'
10 changes: 8 additions & 2 deletions runtime_tests/deno/middleware.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/** @jsxFrag Fragment */
import { basicAuth, jsx, Fragment, serveStatic, jwt } from '../../deno_dist/middleware.ts'
import { Hono } from '../../deno_dist/mod.ts'
import { assertEquals, assertMatch } from './deps.ts'
import { assertEquals, assertMatch, assertSpyCall, assertSpyCalls, spy } from './deps.ts'

// Test just only minimal patterns.
// Because others are already tested well in Cloudflare Workers environment.
Expand Down Expand Up @@ -66,10 +66,11 @@ Deno.test('JSX middleware', async () => {

Deno.test('Serve Static middleware', async () => {
const app = new Hono()
const onNotFound = spy(() => {})
app.all('/favicon.ico', serveStatic({ path: './runtime_tests/deno/favicon.ico' }))
app.all(
'/favicon-notfound.ico',
serveStatic({ path: './runtime_tests/deno/favicon-notfound.ico' })
serveStatic({ path: './runtime_tests/deno/favicon-notfound.ico', onNotFound })
)
app.use('/favicon-notfound.ico', async (c, next) => {
await next()
Expand All @@ -80,6 +81,7 @@ Deno.test('Serve Static middleware', async () => {
'/static/*',
serveStatic({
root: './runtime_tests/deno',
onNotFound,
})
)

Expand All @@ -100,6 +102,9 @@ Deno.test('Serve Static middleware', async () => {
assertEquals(res.status, 404)
assertMatch(res.headers.get('Content-Type') || '', /^text\/plain/)
assertEquals(res.headers.get('X-Custom'), 'Deno')
assertSpyCall(onNotFound, 0, {
args: ['./runtime_tests/deno/favicon-notfound.ico'],
})

res = await app.request('http://localhost/static/plain.txt')
assertEquals(res.status, 200)
Expand All @@ -108,6 +113,7 @@ Deno.test('Serve Static middleware', async () => {
res = await app.request('http://localhost/dot-static/plain.txt')
assertEquals(res.status, 200)
assertEquals(await res.text(), 'Deno!!')
assertSpyCalls(onNotFound, 1)
})

Deno.test('JWT Authentication middleware', async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/bun/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type ServeStaticOptions = {
root?: string
path?: string
rewriteRequestPath?: (path: string) => string
onNotFound?: (path: string) => void | Promise<void>
}

const DEFAULT_DOCUMENT = 'index.html'
Expand Down Expand Up @@ -49,7 +50,7 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }) => {
}
}

console.warn(`Static file: ${path} is not found`)
await options.onNotFound?.(path)
await next()
return
}
Expand Down
8 changes: 7 additions & 1 deletion src/adapter/cloudflare-workers/serve-static.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ Object.assign(global, {

describe('ServeStatic Middleware', () => {
const app = new Hono()
app.use('/static/*', serveStatic({ root: './assets' }))
const onNotFound = vi.fn(() => {})
app.use('/static/*', serveStatic({ root: './assets', onNotFound }))
app.use('/static-no-root/*', serveStatic())
app.use(
'/dot-static/*',
Expand All @@ -41,23 +42,28 @@ describe('ServeStatic Middleware', () => {
})
)

beforeEach(() => onNotFound.mockClear())

it('Should return plain.txt', async () => {
const res = await app.request('http://localhost/static/plain.txt')
expect(res.status).toBe(200)
expect(await res.text()).toBe('This is plain.txt')
expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')
expect(onNotFound).not.toHaveBeenCalled()
})

it('Should return hono.html', async () => {
const res = await app.request('http://localhost/static/hono.html')
expect(res.status).toBe(200)
expect(await res.text()).toBe('<h1>Hono!</h1>')
expect(res.headers.get('Content-Type')).toBe('text/html; charset=utf-8')
expect(onNotFound).not.toHaveBeenCalled()
})

it('Should return 404 response', async () => {
const res = await app.request('http://localhost/static/not-found.html')
expect(res.status).toBe(404)
expect(onNotFound).toHaveBeenCalledWith('assets/static/not-found.html')
})

it('Should return plan.txt', async () => {
Expand Down
8 changes: 5 additions & 3 deletions src/adapter/cloudflare-workers/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ServeStaticOptions = {
manifest?: object | string
namespace?: KVNamespace
rewriteRequestPath?: (path: string) => string
onNotFound?: (path: string) => void | Promise<void>
}

const DEFAULT_DOCUMENT = 'index.html'
Expand Down Expand Up @@ -40,17 +41,18 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }): Middlew
// @ts-ignore
namespace: options.namespace ? options.namespace : c.env ? c.env.__STATIC_CONTENT : undefined,
})

if (content) {
const mimeType = getMimeType(path)
if (mimeType) {
c.header('Content-Type', mimeType)
}
// Return Response object
return c.body(content)
} else {
console.warn(`Static file: ${path} is not found`)
await next()
}

await options.onNotFound?.(path)
await next()
return
}
}
7 changes: 4 additions & 3 deletions src/adapter/deno/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type ServeStaticOptions = {
root?: string
path?: string
rewriteRequestPath?: (path: string) => string
onNotFound?: (path: string) => void | Promise<void>
}

const DEFAULT_DOCUMENT = 'index.html'
Expand Down Expand Up @@ -50,10 +51,10 @@ export const serveStatic = (options: ServeStaticOptions = { root: '' }) => {
}
// Return Response object with stream
return c.body(file.readable)
} else {
console.warn(`Static file: ${path} is not found`)
await next()
}

await options.onNotFound?.(path)
await next()
return
}
}

0 comments on commit b128d02

Please sign in to comment.