forked from honojs/node-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
serve-static.ts
100 lines (82 loc) · 2.65 KB
/
serve-static.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import type { ReadStream } from 'fs'
import { createReadStream, existsSync, lstatSync } from 'fs'
import type { Context, MiddlewareHandler } from 'hono'
import { getFilePath } from 'hono/utils/filepath'
import { getMimeType } from 'hono/utils/mime'
export type ServeStaticOptions = {
/**
* Root path, relative to current working directory. (absolute paths are not supported)
*/
root?: string
path?: string
index?: string // default is 'index.html'
rewriteRequestPath?: (path: string) => string
onNotFound?: (path: string, c: Context) => void | Promise<void>
}
const createStreamBody = (stream: ReadStream) => {
const body = new ReadableStream({
start(controller) {
stream.on('data', (chunk) => {
controller.enqueue(chunk)
})
stream.on('end', () => {
controller.close()
})
},
cancel() {
stream.destroy()
},
})
return body
}
export const serveStatic = (options: ServeStaticOptions = { root: '' }): MiddlewareHandler => {
return async (c, next) => {
// Do nothing if Response is already set
if (c.finalized) {
return next()
}
const filename = options.path ?? decodeURIComponent(c.req.path)
let path = getFilePath({
filename: options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename,
root: options.root,
defaultDocument: options.index ?? 'index.html',
})
if (!path) {
return next()
}
path = `./${path}`
if (!existsSync(path)) {
await options.onNotFound?.(path, c)
return next()
}
const mimeType = getMimeType(path)
if (mimeType) {
c.header('Content-Type', mimeType)
}
const stat = lstatSync(path)
const size = stat.size
if (c.req.method == 'HEAD' || c.req.method == 'OPTIONS') {
c.header('Content-Length', size.toString())
c.status(200)
return c.body(null)
}
const range = c.req.header('range') || ''
if (!range) {
c.header('Content-Length', size.toString())
return c.body(createStreamBody(createReadStream(path)), 200)
}
c.header('Accept-Ranges', 'bytes')
c.header('Date', stat.birthtime.toUTCString())
const parts = range.replace(/bytes=/, '').split('-', 2)
const start = parts[0] ? parseInt(parts[0], 10) : 0
let end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1
if (size < end - start + 1) {
end = size - 1
}
const chunksize = end - start + 1
const stream = createReadStream(path, { start, end })
c.header('Content-Length', chunksize.toString())
c.header('Content-Range', `bytes ${start}-${end}/${stat.size}`)
return c.body(createStreamBody(stream), 206)
}
}