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

Commit 732a9f8

Browse files
author
v1rtl
committed
add res.vary, res.redirect, res.format, req network exts
1 parent 6c42cc5 commit 732a9f8

File tree

19 files changed

+565
-16
lines changed

19 files changed

+565
-16
lines changed

app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NextFunction, Router, Handler, Middleware, UseMethodParams } from 'http
33
import { onErrorHandler, ErrorHandler } from './onError.ts'
44
// import { setImmediate } from 'https://deno.land/std@0.88.0/node/timers.ts'
55
import rg from 'https://esm.sh/regexparam'
6-
import { Request } from './request.ts'
6+
import { Request, getRouteFromApp } from './request.ts'
77
import { Response } from './response.ts'
88
import { getURLParams, getPathname } from './parseUrl.ts'
99
import { extendMiddleware } from './extend.ts'
@@ -230,6 +230,8 @@ export class App<
230230

231231
req.path = getPathname(req.url)
232232

233+
if (this.settings?.enableReqRoute) req.route = getRouteFromApp(this as any, handler)
234+
233235
if (type === 'route') req.params = getURLParams(regex, pathname)
234236

235237
await applyHandler<Req, Res>((handler as unknown) as Handler<Req, Res>)(req, res, next)

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.8",
7+
"version": "0.0.9",
88
"ignore": ["./examples/**/*.ts"],
99
"files": ["./**/*.ts", "README.md"],
1010
"checkFormat": false,

examples/basic/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { App } from '../../app.ts'
22

33
const app = new App()
44

5-
app.get('/:name/', (req, res) => {
5+
app.get('/', (req, res) => {
66
res.send(`Hello on ${req.url} from Deno v${Deno.version.deno} and tinyhttp! 🦕`)
77
})
88

extend.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import {
88
getAcceptsCharsets,
99
getAcceptsEncodings,
1010
getAcceptsLanguages,
11-
getRangeFromHeader
11+
getRangeFromHeader,
12+
checkIfXMLHttpRequest,
13+
reqIs,
14+
getHostname,
15+
getIP,
16+
getIPs,
17+
getProtocol,
18+
getSubdomains
1219
} from './extensions/req/mod.ts'
1320
import {
1421
send,
@@ -21,7 +28,9 @@ import {
2128
getResponseHeader,
2229
append,
2330
setLinksHeader,
24-
setContentType
31+
setContentType,
32+
formatResponse,
33+
setVaryHeader
2534
} from './extensions/res/mod.ts'
2635

2736
import { Response, renderTemplate } from './response.ts'
@@ -32,7 +41,7 @@ export const extendMiddleware = <
3241
Res extends Response = Response
3342
>(
3443
app: App
35-
) => (req: Req, res: Res, next?: NextFunction) => {
44+
) => (req: Req, res: Res, next: NextFunction) => {
3645
const { settings } = app
3746

3847
// Request extensions
@@ -54,6 +63,20 @@ export const extendMiddleware = <
5463
req.acceptsLanguages = getAcceptsLanguages(req)
5564

5665
req.range = getRangeFromHeader(req)
66+
req.xhr = checkIfXMLHttpRequest(req)
67+
req.is = reqIs(req)
68+
69+
req.ip = getIP(req)
70+
req.ips = getIPs(req)
71+
72+
if (settings?.networkExtensions) {
73+
req.protocol = getProtocol(req)
74+
req.secure = req.protocol === 'https'
75+
req.hostname = getHostname(req)
76+
req.subdomains = getSubdomains(req, settings.subdomainOffset)
77+
req.ip = getIP(req)
78+
req.ips = getIPs(req)
79+
}
5780

5881
// Response extensions
5982

@@ -62,21 +85,16 @@ export const extendMiddleware = <
6285
res.sendFile = sendFile<Res>(res)
6386
res.sendStatus = sendStatus(req, res)
6487
res.json = json<Res>(res)
65-
6688
res.setHeader = setHeader<Res>(res)
6789
res.set = setHeader<Res>(res)
68-
6990
res.location = setLocationHeader<Req, Res>(req, res)
70-
7191
res.get = getResponseHeader<Res>(res)
72-
7392
res.append = append<Res>(res)
74-
7593
res.render = renderTemplate<RenderOptions, Res>(res, app)
76-
7794
res.links = setLinksHeader<Res>(res)
78-
7995
res.type = setContentType<Res>(res)
96+
res.format = formatResponse(req, res, next)
97+
res.vary = setVaryHeader(res)
8098

8199
next?.()
82100
}

extensions/req/headers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Request } from '../../request.ts'
22
import { Response } from '../../response.ts'
33
import parseRange, { Options } from 'https://esm.sh/range-parser'
44
import fresh from 'https://deno.land/x/fresh/mod.ts'
5+
import { is } from 'https://deno.land/x/type_is/mod.ts'
56

67
export const getRequestHeader = (req: Request) => (header: string): string | string[] | null => {
78
const lc = header.toLowerCase()
@@ -42,3 +43,7 @@ export const getFreshOrStale = (req: Request, res: Response) => {
4243

4344
return false
4445
}
46+
47+
export const checkIfXMLHttpRequest = (req: Request) => req.headers.get('X-Requested-With') === 'XMLHttpRequest'
48+
49+
export const reqIs = (req: Request) => (...types: string[]) => is(req.headers.get('content-type') as string, types)

extensions/req/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './accepts.ts'
22
export * from './headers.ts'
3+
export * from './security.ts'

extensions/req/security.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { isIP } from 'https://deno.land/x/isIP/mod.ts'
2+
import { Request } from '../../request.ts'
3+
import { compile, proxyaddr, all } from '../../utils/proxyAddr.ts'
4+
import { Protocol } from '../../request.ts'
5+
6+
export const trustRemoteAddress = (req: Request) => {
7+
const val = (req.conn.remoteAddr as Deno.NetAddr).hostname
8+
9+
if (typeof val === 'function') return val
10+
11+
if (typeof val === 'boolean' && val === true) return () => true
12+
13+
if (typeof val === 'number') return (_: unknown, i: number) => (val ? i < val : undefined)
14+
15+
if (typeof val === 'string') return compile(val.split(',').map((x) => x.trim()))
16+
17+
return compile(val)
18+
}
19+
20+
export const getProtocol = (req: Request): Protocol => {
21+
const proto = req.proto.includes('https') ? 'https' : 'http'
22+
23+
const header = (req.get('X-Forwarded-Proto') as string) ?? proto
24+
const index = header.indexOf(',')
25+
26+
if (!trustRemoteAddress(req)) return proto
27+
28+
// Note: X-Forwarded-Proto is normally only ever a
29+
// single value, but this is to be safe.
30+
31+
return (index !== -1 ? header.substring(0, index).trim() : header.trim()) as Protocol
32+
}
33+
34+
export const getHostname = (req: Request): string | undefined => {
35+
let host: string = req.get('X-Forwarded-Host') as string
36+
37+
if (!host || !trustRemoteAddress(req)) {
38+
host = (req.get('Host') as string) || (req.conn.remoteAddr as Deno.NetAddr).hostname
39+
}
40+
41+
if (!host) return
42+
43+
// IPv6 literal support
44+
const index = host.indexOf(':', host[0] === '[' ? host.indexOf(']') + 1 : 0)
45+
46+
return index !== -1 ? host.substring(0, index) : host
47+
}
48+
export const getIP = (req: Request): string | undefined => proxyaddr(req, trustRemoteAddress(req)).replace(/^.*:/, '') // striping the redundant prefix addeded by OS to IPv4 address
49+
50+
export const getIPs = (req: Request): string[] | undefined => all(req, trustRemoteAddress(req))
51+
52+
export const getSubdomains = (req: Request, subdomainOffset = 2): string[] => {
53+
const hostname = getHostname(req)
54+
55+
if (!hostname) return []
56+
57+
const subdomains = isIP(hostname) ? [hostname] : hostname.split('.').reverse()
58+
59+
return subdomains.slice(subdomainOffset)
60+
}

extensions/res/end.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Request } from '../../request.ts'
22
import { Response } from '../../response.ts'
33

4-
export const end = (req: Request, res: Response) => (body: any) => {
4+
export const end = (req: Request, res: Response) => (body: any = '') => {
55
req.respond({ ...res, body })
66
return res
77
}

extensions/res/format.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Handler } from 'https://esm.sh/@tinyhttp/router'
2+
import { normalizeType, normalizeTypes } from './utils.ts'
3+
import { setVaryHeader } from './headers.ts'
4+
import { getAccepts } from '../req/mod.ts'
5+
import { Request as Req } from '../../request.ts'
6+
import { Response as Res } from '../../response.ts'
7+
8+
export type FormatProps = {
9+
default?: () => void
10+
} & Record<string, Handler>
11+
12+
export type FormatError = Error & {
13+
status: number
14+
statusCode: number
15+
types: ReturnType<typeof normalizeTypes>[]
16+
}
17+
18+
type next = (err?: FormatError) => void
19+
20+
export const formatResponse = <Request extends Req = Req, Response extends Res = Res, Next extends next = next>(
21+
req: Request,
22+
res: Response,
23+
next: Next
24+
) => (obj: FormatProps) => {
25+
const fn = obj.default
26+
27+
if (fn) delete obj.default
28+
29+
const keys = Object.keys(obj)
30+
31+
const key = keys.length > 0 ? (getAccepts(req)(...keys) as string) : false
32+
33+
setVaryHeader(res)('Accept')
34+
35+
if (key) {
36+
res.setHeader('Content-Type', normalizeType(key).value)
37+
obj[key](req, res, next)
38+
} else if (fn) {
39+
fn()
40+
} else {
41+
const err = new Error('Not Acceptable') as FormatError
42+
err.status = err.statusCode = 406
43+
err.types = normalizeTypes(keys).map((o) => o.value)
44+
45+
next(err)
46+
}
47+
48+
return res
49+
}

extensions/res/headers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as mime from 'https://esm.sh/es-mime-types'
22
import { encodeUrl } from 'https://esm.sh/@tinyhttp/encode-url'
3+
import { vary } from '../../utils/vary.ts'
34
import { Response as Res } from '../../response.ts'
45
import { Request as Req } from '../../request.ts'
56
import { getRequestHeader } from '../req/headers.ts'
@@ -71,6 +72,12 @@ export const setLinksHeader = <Response extends Res = Res>(res: Response) => (li
7172
return res
7273
}
7374

75+
export const setVaryHeader = <Response extends Res = Res>(res: Response) => (field: string): Response => {
76+
vary<Response>(res, field)
77+
78+
return res
79+
}
80+
7481
export const setContentType = <Response extends Res = Res>(res: Response) => (type: string): Response => {
7582
const ct = type.indexOf('/') === -1 ? mime.lookup(type) : type
7683

0 commit comments

Comments
 (0)