Skip to content
This repository was archived by the owner on Jul 31, 2025. It is now read-only.

Commit 1a5a62d

Browse files
author
v1rtl
committed
add sendFile and append, improve send
1 parent f9dab33 commit 1a5a62d

File tree

10 files changed

+116
-23
lines changed

10 files changed

+116
-23
lines changed

app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Request } from './request.ts'
77
import { Response } from './response.ts'
88
import { getURLParams, getPathname } from './parseUrl.ts'
99
import { extendMiddleware } from './extend.ts'
10-
import { serve, Server } from 'https://deno.land/std@0.87.0/http/server.ts'
10+
import { serve, Server } from 'https://deno.land/std/http/server.ts'
1111

1212
const lead = (x: string) => (x.charCodeAt(0) === 47 ? x : '/' + x)
1313

egg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"entry": "./app.ts",
55
"description": "0-legacy, tiny & fast web framework as a replacement of Express",
66
"homepage": "https://github.com/talentlessguy/tinyhttp-deno",
7-
"version": "0.0.5",
7+
"version": "0.0.6",
88
"ignore": [
99
"./example/mod.ts"
1010
],

example/mod.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import { App } from '../app.ts'
22

3-
const app = new App()
4-
5-
app.use('/', (req, res, next) => {
6-
console.log(`${req.method} ${req.url}`)
3+
const __dirname = new URL('.', import.meta.url).pathname
74

8-
res.set('Test-Header', 'Value')
5+
const app = new App()
96

10-
next()
11-
})
7+
app.get('/', (_, res) => {
8+
const path = __dirname + 'mod.ts'
129

13-
app.get('/:name/', (req, res) => {
14-
res.send(`Hello on ${req.url} from Deno and tinyhttp! 🦕`)
10+
res.sendFile(path)
1511
})
1612

1713
app.listen(3000, () => console.log(`Started on :3000`))

extend.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@ import { NextFunction } from 'https://esm.sh/@tinyhttp/router'
22
import { App } from './app.ts'
33
import { Request } from './request.ts'
44
import { getRequestHeader, getFreshOrStale } from './extensions/req/headers.ts'
5-
import { send } from './extensions/res/send.ts'
6-
import { json } from './extensions/res/json.ts'
7-
import { end } from './extensions/res/end.ts'
8-
import { sendStatus } from './extensions/res/sendStatus.ts'
5+
import {
6+
send,
7+
json,
8+
sendStatus,
9+
setHeader,
10+
setLocationHeader,
11+
end,
12+
sendFile,
13+
getResponseHeader,
14+
append
15+
} from './extensions/res/mod.ts'
16+
917
import { Response } from './response.ts'
10-
import { setHeader, setLocationHeader } from './extensions/res/headers.ts'
1118

1219
export const extendMiddleware = <
1320
RenderOptions = unknown,
@@ -32,6 +39,7 @@ export const extendMiddleware = <
3239

3340
res.end = end(req, res)
3441
res.send = send<Req, Res>(req, res)
42+
res.sendFile = sendFile<Res>(res)
3543
res.sendStatus = sendStatus(req, res)
3644
res.json = json<Res>(res)
3745

@@ -40,5 +48,9 @@ export const extendMiddleware = <
4048

4149
res.location = setLocationHeader<Req, Res>(req, res)
4250

51+
res.get = getResponseHeader<Res>(res)
52+
53+
res.append = append<Res>(res)
54+
4355
next?.()
4456
}

extensions/res/append.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Response as Res } from '../../response.ts'
2+
import { setHeader, getResponseHeader } from './headers.ts'
3+
4+
export const append = <Response extends Res = Res>(res: Response) => (
5+
field: string,
6+
value: string | number | string[]
7+
): Response => {
8+
const prevVal = getResponseHeader<Res>(res)(field)
9+
let newVal = value
10+
11+
if (prevVal && typeof newVal !== 'number' && typeof prevVal !== 'number') {
12+
newVal = Array.isArray(prevVal)
13+
? prevVal.concat(newVal)
14+
: Array.isArray(newVal)
15+
? [prevVal].concat(newVal)
16+
: [prevVal, newVal]
17+
}
18+
setHeader(res)(field, newVal)
19+
return res
20+
}

extensions/res/headers.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export const setHeader = <Response extends Res = Res>(res: Response) => (
3535
return res
3636
}
3737

38+
export const getResponseHeader = <Response extends Res = Res>(res: Response) => (
39+
field: string
40+
): string | number | string[] | null => {
41+
return res.headers.get(field)
42+
}
43+
3844
export const setLocationHeader = <Request extends Req = Req, Response extends Res = Res>(
3945
req: Request,
4046
res: Response

extensions/res/mod.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export * from './end.ts'
2+
export * from './json.ts'
3+
export * from './send.ts'
4+
export * from './sendStatus.ts'
5+
export * from './headers.ts'
6+
export * from './sendFile.ts'
7+
export * from './append.ts'

extensions/res/send.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createETag, setCharset } from './utils.ts'
55
import { end } from './end.ts'
66

77
export const send = <Request extends Req = Req, Response extends Res = Res>(req: Request, res: Response) => (
8-
body: unknown
8+
body: any
99
) => {
1010
let bodyToSend = body
1111

@@ -38,22 +38,27 @@ export const send = <Request extends Req = Req, Response extends Res = Res>(req:
3838
}
3939

4040
if (req.method === 'HEAD') {
41-
end(req, res)('')
41+
end(req, res)(body)
4242
return res
4343
}
4444

4545
if (typeof body === 'object') {
4646
if (body == null) {
4747
end(req, res)('')
4848
return res
49-
} /* else if (Buffer.isBuffer(body)) {
50-
if (!res.headers.get('Content-Type')) res.headers.set('content-type', 'application/octet-stream')
51-
} */ else
49+
} else if (typeof body?.read !== 'undefined') {
50+
console.log('here')
51+
52+
if (!res.headers.get('Content-Type')) req.headers.set('content-type', 'application/octet-stream')
53+
54+
end(req, res)(body)
55+
} else {
5256
json(res)(bodyToSend)
57+
}
5358
} else {
5459
if (typeof bodyToSend !== 'string') bodyToSend = (bodyToSend as string).toString()
5560

56-
end(req, res)(bodyToSend)
61+
end(req, res)(body)
5762
}
5863

5964
return res

extensions/res/sendFile.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Response as Res } from '../../response.ts'
2+
import { isAbsolute, join } from 'https://deno.land/std/path/mod.ts'
3+
4+
export type SendFileOptions = Partial<{
5+
root: string
6+
headers: Record<string, any>
7+
}> &
8+
Deno.OpenOptions
9+
10+
/**
11+
* Sends a file by piping a stream to response.
12+
*
13+
* It also checks for extension to set a proper `Content-Type` header.
14+
*
15+
* Path argument must be absolute. To use a relative path, specify the `root` option first.
16+
*
17+
* @param res Response
18+
*/
19+
export const sendFile = <Response extends Res = Res>(res: Response) => (
20+
path: string,
21+
opts: SendFileOptions = {},
22+
cb?: (err?: unknown) => void
23+
) => {
24+
const { root, headers, ...options } = opts
25+
26+
if (!path || typeof path !== 'string') throw new TypeError('path must be a string to res.sendFile')
27+
28+
if (headers) for (const [k, v] of Object.entries(headers)) res.setHeader(k, v)
29+
30+
if (!isAbsolute(path) && !root) throw new TypeError('path must be absolute')
31+
32+
const filePath = root ? join(root, path) : path
33+
34+
res.set('Content-Type', 'text/html; charset=UTF-8')
35+
res.set('Content-Security-Policy', "default-src 'none'")
36+
res.set('X-Content-Type-Options', 'nosniff')
37+
38+
const file = Deno.openSync(filePath, { read: true, ...options })
39+
40+
res.send(file)
41+
42+
return res
43+
}

response.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Response as ServerResponse } from 'https://deno.land/std@0.87.0/http/server.ts'
2-
2+
import type { SendFileOptions } from './extensions/res/sendFile.ts'
33
export interface Response extends ServerResponse {
44
headers: Headers
55
send(body: unknown): Response
6+
sendFile(path: string, options?: SendFileOptions, cb?: (err?: any) => void): Response
67
end(body: unknown): Response
78
json(body: unknown): Response
89
sendStatus(status: number): Response
@@ -12,4 +13,7 @@ export interface Response extends ServerResponse {
1213
): Response
1314
set(field: string | Record<string, string | number | string[]>, val?: string | number | readonly string[]): Response
1415
location(url: string): Response
16+
status: number
17+
get(field: string): string | number | string[] | null
18+
append(field: string, value: any): Response
1519
}

0 commit comments

Comments
 (0)