Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ app.use('/static/*', serveStatic({ root: './static' }))

Notice that `root` here is not relative to `src/index.ts`, rather to `my-hono-project`.

Only `GET` and `HEAD` requests are served. Requests with other methods are passed to the next middleware.

### Options

#### `allowPercentInPath`
Expand Down
6 changes: 5 additions & 1 deletion src/serve-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ export const serveStatic = <E extends Env = any>(
return next()
}

if (c.req.method !== 'GET' && c.req.method !== 'HEAD') {
return next()
}

let filename: string

if (optionPath) {
Expand Down Expand Up @@ -195,7 +199,7 @@ export const serveStatic = <E extends Env = any>(
const range = c.req.header('range') || ''
c.header('Last-Modified', stats.mtime.toUTCString())

if (c.req.method == 'HEAD' || c.req.method == 'OPTIONS') {
if (c.req.method === 'HEAD') {
c.header('Content-Length', size.toString())
c.status(200)
result = c.body(null)
Expand Down
57 changes: 57 additions & 0 deletions test/serve-static.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Hono } from 'hono'
import type * as fs from 'node:fs'
import {
chmodSync,
createReadStream,
mkdirSync,
mkdtempSync,
rmSync,
Expand All @@ -14,6 +16,15 @@ import { serveStatic } from './../src/serve-static'
import { createAdaptorServer } from './../src/server'
import { requestServer } from './helpers/request'

vi.mock('node:fs', async (importOriginal) => {
const originalFs = await importOriginal<typeof fs>()
return {
...originalFs,
statSync: vi.fn(originalFs.statSync),
createReadStream: vi.fn(originalFs.createReadStream),
}
})

describe('Serve Static Middleware', () => {
const app = new Hono<{
Variables: {
Expand Down Expand Up @@ -76,6 +87,52 @@ describe('Serve Static Middleware', () => {

const server = createAdaptorServer(app)

describe('HTTP methods', () => {
beforeEach(() => {
vi.mocked(statSync).mockClear()
vi.mocked(createReadStream).mockClear()
})

it.each(['POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'PROPFIND'])(
'Should pass %s requests to the next handler without accessing files',
async (method) => {
const rewriteRequestPath = vi.fn((path: string) => path)
const onFound = vi.fn()
const onNotFound = vi.fn()
const app = new Hono().use(
'/static/*',
serveStatic({ root: './test/assets', rewriteRequestPath, onFound, onNotFound })
)
app.on(method, '/static/plain.txt', (c) => c.text('Handled downstream', 202))

const res = await requestServer(createAdaptorServer(app), {
method,
path: '/static/plain.txt',
})

expect(res.status).toBe(202)
expect(await res.text()).toBe('Handled downstream')
expect(rewriteRequestPath).not.toHaveBeenCalled()
expect(statSync).not.toHaveBeenCalled()
expect(createReadStream).not.toHaveBeenCalled()
expect(onFound).not.toHaveBeenCalled()
expect(onNotFound).not.toHaveBeenCalled()
}
)

it.each(['/static/plain.txt', '/static/does-not-exist.html'])(
'Should return 404 for an unhandled POST request regardless of file existence - %s',
async (path) => {
const res = await requestServer(server, { method: 'POST', path })

expect(res.status).toBe(404)
expect(await res.text()).toBe('404 Not Found')
expect(statSync).not.toHaveBeenCalled()
expect(createReadStream).not.toHaveBeenCalled()
}
)
})

it('Should return index.html', async () => {
const res = await requestServer(server, { method: 'GET', path: '/static/' })
expect(res.status).toBe(200)
Expand Down
Loading