hono 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -14
- package/example/basic/index.js +23 -2
- package/example/basic/package.json +4 -4
- package/example/basic/wrangler.toml +1 -1
- package/package.json +1 -5
- package/src/compose.js +22 -0
- package/src/compose.test.js +42 -0
- package/src/hono.js +90 -65
- package/src/hono.test.js +66 -7
- package/src/middleware/filter.js +19 -0
- package/src/node.js +15 -11
- package/src/node.test.js +32 -1
- package/src/router.test.js +19 -11
- package/hono.mini.js +0 -1
- package/src/node.root.test.js +0 -17
package/README.md
CHANGED
|
@@ -15,11 +15,12 @@ app.fire()
|
|
|
15
15
|
- Fast - the router is implemented with Trie-Tree structure.
|
|
16
16
|
- Tiny - use only standard API.
|
|
17
17
|
- Portable - zero dependencies.
|
|
18
|
-
-
|
|
18
|
+
- Flexible - you can make your own middlewares.
|
|
19
|
+
- Optimized - for Cloudflare Workers.
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
|
22
|
-
```
|
|
23
|
+
```
|
|
23
24
|
$ yarn add hono
|
|
24
25
|
```
|
|
25
26
|
|
|
@@ -29,21 +30,42 @@ or
|
|
|
29
30
|
$ npm install hono
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
## Methods
|
|
34
|
+
|
|
35
|
+
- app.**HTTP_METHOD**(path, callback)
|
|
36
|
+
- app.**all**(path, callback)
|
|
37
|
+
- app.**route**(path)
|
|
38
|
+
- app.**use**(path, middleware)
|
|
39
|
+
|
|
32
40
|
## Routing
|
|
33
41
|
|
|
34
42
|
### Basic
|
|
35
43
|
|
|
44
|
+
`app.HTTP_METHOD`
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// HTTP Methods
|
|
48
|
+
app.get('/', () => new Response('GET /'))
|
|
49
|
+
app.post('/', () => new Response('POST /'))
|
|
50
|
+
|
|
51
|
+
// Wildcard
|
|
52
|
+
app.get('/wild/*/card', () => {
|
|
53
|
+
return new Reponse('GET /wild/*/card')
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`app.all`
|
|
58
|
+
|
|
36
59
|
```js
|
|
37
|
-
|
|
38
|
-
app.
|
|
39
|
-
app.get('/wild/*/card', () => 'GET /wild/*/card')
|
|
60
|
+
// Any HTTP methods
|
|
61
|
+
app.all('/hello', () => 'ALL Method /hello')
|
|
40
62
|
```
|
|
41
63
|
|
|
42
64
|
### Named Parameter
|
|
43
65
|
|
|
44
66
|
```js
|
|
45
|
-
app.get('/user/:name', (
|
|
46
|
-
const name = req.params('name')
|
|
67
|
+
app.get('/user/:name', (c) => {
|
|
68
|
+
const name = c.req.params('name')
|
|
47
69
|
...
|
|
48
70
|
})
|
|
49
71
|
```
|
|
@@ -51,9 +73,9 @@ app.get('/user/:name', (req) => {
|
|
|
51
73
|
### Regexp
|
|
52
74
|
|
|
53
75
|
```js
|
|
54
|
-
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (
|
|
55
|
-
const date = req.params('date')
|
|
56
|
-
const title = req.params('title')
|
|
76
|
+
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
|
|
77
|
+
const date = c.req.params('date')
|
|
78
|
+
const title = c.req.params('title')
|
|
57
79
|
...
|
|
58
80
|
```
|
|
59
81
|
|
|
@@ -62,15 +84,77 @@ app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (req) => {
|
|
|
62
84
|
```js
|
|
63
85
|
app
|
|
64
86
|
.route('/api/book')
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
.get(() => {...})
|
|
88
|
+
.post(() => {...})
|
|
89
|
+
.put(() => {...})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Middleware
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const logger = (c, next) => {
|
|
96
|
+
console.log(`[${c.req.method}] ${c.req.url}`)
|
|
97
|
+
next()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const addHeader = (c, next) => {
|
|
101
|
+
next()
|
|
102
|
+
c.res.headers.add('x-message', 'This is middleware!')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
app = app.use('*', logger)
|
|
106
|
+
app = app.use('/message/*', addHeader)
|
|
107
|
+
|
|
108
|
+
app.get('/message/hello', () => 'Hello Middleware!')
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Context
|
|
112
|
+
|
|
113
|
+
### req
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
app.get('/hello', (c) => {
|
|
117
|
+
const userAgent = c.req.headers('User-Agent')
|
|
118
|
+
...
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### res
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
app.use('/', (c, next) => {
|
|
126
|
+
next()
|
|
127
|
+
c.res.headers.append('X-Debug', 'Debug message')
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Request
|
|
132
|
+
|
|
133
|
+
### query
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
app.get('/search', (c) => {
|
|
137
|
+
const query = c.req.query('q')
|
|
138
|
+
...
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### params
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
app.get('/entry/:id', (c) => {
|
|
146
|
+
const id = c.req.params('id')
|
|
147
|
+
...
|
|
148
|
+
})
|
|
68
149
|
```
|
|
69
150
|
|
|
70
151
|
## Related projects
|
|
71
152
|
|
|
72
|
-
-
|
|
153
|
+
- koa <https://github.com/koajs/koa>
|
|
154
|
+
- express <https://github.com/expressjs/express>
|
|
73
155
|
- itty-router <https://github.com/kwhitley/itty-router>
|
|
156
|
+
- Sunder <https://github.com/SunderJS/sunder>
|
|
157
|
+
- goblin <https://github.com/bmf-san/goblin>
|
|
74
158
|
|
|
75
159
|
## Author
|
|
76
160
|
|
package/example/basic/index.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
const Hono = require('../../src/hono')
|
|
2
2
|
const app = Hono()
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Middleware
|
|
5
|
+
const logger = (c, next) => {
|
|
6
|
+
console.log(`[${c.req.method}] ${c.req.url}`)
|
|
7
|
+
next()
|
|
8
|
+
}
|
|
9
|
+
const addHeader = (c, next) => {
|
|
10
|
+
next()
|
|
11
|
+
c.res.headers.append('X-message', 'This is addHeader middleware!')
|
|
12
|
+
}
|
|
6
13
|
|
|
14
|
+
// Mount middleware
|
|
15
|
+
app.use('*', logger)
|
|
16
|
+
app.use('/hello', addHeader)
|
|
17
|
+
|
|
18
|
+
// Routing
|
|
19
|
+
app.get('/', () => new Response('Hono!!'))
|
|
20
|
+
app.get('/hello', () => new Response('This is /hello'))
|
|
21
|
+
|
|
22
|
+
app.get('/entry/:id', (c) => {
|
|
23
|
+
const id = c.req.params('id')
|
|
24
|
+
return new Response(`Your ID is ${id}`)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// addEventListener
|
|
7
28
|
app.fire()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
3
|
-
"version": "
|
|
2
|
+
"name": "hono-example-basic",
|
|
3
|
+
"version": "0.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
|
|
10
|
-
"license": "
|
|
11
|
-
}
|
|
10
|
+
"license": "MIT"
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Minimal web framework for Cloudflare Workers",
|
|
5
5
|
"main": "src/hono.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,9 +16,5 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"jest": "^27.4.5",
|
|
18
18
|
"node-fetch": "^2.6.6"
|
|
19
|
-
},
|
|
20
|
-
"dependencies": {
|
|
21
|
-
"global": "^4.4.0",
|
|
22
|
-
"wrangler": "^0.0.0-beta.6"
|
|
23
19
|
}
|
|
24
20
|
}
|
package/src/compose.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Based on the code in the MIT licensed `koa-compose` package.
|
|
2
|
+
const compose = (middleware) => {
|
|
3
|
+
return function (context, next) {
|
|
4
|
+
let index = -1
|
|
5
|
+
return dispatch(0)
|
|
6
|
+
function dispatch(i) {
|
|
7
|
+
if (i <= index)
|
|
8
|
+
return Promise.reject(new Error('next() called multiple times'))
|
|
9
|
+
index = i
|
|
10
|
+
let fn = middleware[i]
|
|
11
|
+
if (i === middleware.length) fn = next
|
|
12
|
+
if (!fn) return Promise.resolve()
|
|
13
|
+
try {
|
|
14
|
+
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return Promise.reject(err)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = compose
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const compose = require('./compose')
|
|
2
|
+
|
|
3
|
+
describe('compose middleware', () => {
|
|
4
|
+
const middleware = []
|
|
5
|
+
|
|
6
|
+
const a = (c, next) => {
|
|
7
|
+
c.req['log'] = 'log'
|
|
8
|
+
next()
|
|
9
|
+
}
|
|
10
|
+
middleware.push(a)
|
|
11
|
+
|
|
12
|
+
const b = (c, next) => {
|
|
13
|
+
next()
|
|
14
|
+
c.res['header'] = `${c.res.header}-custom-header`
|
|
15
|
+
}
|
|
16
|
+
middleware.push(b)
|
|
17
|
+
|
|
18
|
+
const handler = (c, next) => {
|
|
19
|
+
c.req['log'] = `${c.req.log} message`
|
|
20
|
+
next()
|
|
21
|
+
c.res = { message: 'new response' }
|
|
22
|
+
}
|
|
23
|
+
middleware.push(handler)
|
|
24
|
+
|
|
25
|
+
const request = {}
|
|
26
|
+
const response = {}
|
|
27
|
+
|
|
28
|
+
it('Request', () => {
|
|
29
|
+
const c = { req: request, res: response }
|
|
30
|
+
const composed = compose(middleware)
|
|
31
|
+
composed(c)
|
|
32
|
+
expect(c.req['log']).not.toBeNull()
|
|
33
|
+
expect(c.req['log']).toBe('log message')
|
|
34
|
+
})
|
|
35
|
+
it('Response', () => {
|
|
36
|
+
const c = { req: request, res: response }
|
|
37
|
+
const composed = compose(middleware)
|
|
38
|
+
composed(c)
|
|
39
|
+
expect(c.res['header']).not.toBeNull()
|
|
40
|
+
expect(c.res['message']).toBe('new response')
|
|
41
|
+
})
|
|
42
|
+
})
|
package/src/hono.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const Node = require('./node')
|
|
4
|
+
const compose = require('./compose')
|
|
5
|
+
const filter = require('./middleware/filter')
|
|
2
6
|
|
|
3
7
|
class Router {
|
|
4
8
|
constructor() {
|
|
@@ -6,98 +10,119 @@ class Router {
|
|
|
6
10
|
this.tempPath = '/'
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
return WrappedRouter(this)
|
|
13
|
+
add(method, path, ...handlers) {
|
|
14
|
+
this.node.insert(method, path, handlers)
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
17
|
+
match(method, path) {
|
|
18
|
+
method = method.toLowerCase()
|
|
19
|
+
return this.node.search(method, path)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const proxyHandler = {
|
|
24
|
+
get:
|
|
25
|
+
(target, prop) =>
|
|
26
|
+
(...args) => {
|
|
27
|
+
if (target.constructor.prototype.hasOwnProperty(prop)) {
|
|
28
|
+
return target[prop](...args)
|
|
29
|
+
} else {
|
|
30
|
+
if (args.length == 1) {
|
|
31
|
+
return target.addRoute(prop, target.router.tempPath, ...args)
|
|
32
|
+
}
|
|
33
|
+
return target.addRoute(prop, ...args)
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class App {
|
|
39
|
+
constructor() {
|
|
40
|
+
this.router = new Router()
|
|
41
|
+
this.middlewareRouter = new Router()
|
|
42
|
+
this.middleware = []
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addRoute(method, path, ...args) {
|
|
46
|
+
this.router.add(method, path, ...args)
|
|
47
|
+
return WrappedApp(this)
|
|
17
48
|
}
|
|
18
49
|
|
|
19
50
|
matchRoute(method, path) {
|
|
20
|
-
|
|
21
|
-
const res = this.node.search(method, path)
|
|
22
|
-
return res
|
|
51
|
+
return this.router.match(method, path)
|
|
23
52
|
}
|
|
24
53
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return event.respondWith(response)
|
|
54
|
+
route(path) {
|
|
55
|
+
this.router.tempPath = path
|
|
56
|
+
return WrappedApp(this)
|
|
29
57
|
}
|
|
30
58
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
59
|
+
use(path, middleware) {
|
|
60
|
+
middleware = [middleware]
|
|
61
|
+
const result = this.middlewareRouter.match('all', path)
|
|
62
|
+
if (result) {
|
|
63
|
+
middleware.push(...result.handler)
|
|
34
64
|
}
|
|
35
|
-
|
|
36
|
-
return new Response(JSON.stringify(result), {
|
|
37
|
-
status: 200,
|
|
38
|
-
headers: {
|
|
39
|
-
'Content-Type': 'application/json',
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
if (typeof result === 'string') {
|
|
44
|
-
return new Response(result, {
|
|
45
|
-
status: 200,
|
|
46
|
-
headers: {
|
|
47
|
-
'Content-Type': 'text/plain',
|
|
48
|
-
},
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
return this.notFound()
|
|
65
|
+
this.middlewareRouter.add('all', path, ...middleware)
|
|
52
66
|
}
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
// XXX
|
|
69
|
+
createContext(req, res) {
|
|
70
|
+
return { req: req, res: res }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async dispatch(request, response) {
|
|
55
74
|
const url = new URL(request.url)
|
|
56
|
-
const path = url.pathname
|
|
57
|
-
|
|
58
|
-
const
|
|
75
|
+
const [method, path] = [request.method, url.pathname]
|
|
76
|
+
|
|
77
|
+
const result = this.matchRoute(method, path)
|
|
78
|
+
if (!result) return this.notFound()
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
request.params = (key) => result.params[key]
|
|
81
|
+
|
|
82
|
+
const middleware = [filter]
|
|
83
|
+
const mwResult = this.middlewareRouter.match('all', path)
|
|
84
|
+
if (mwResult) {
|
|
85
|
+
middleware.push(...mwResult.handler)
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
88
|
+
let handler
|
|
89
|
+
for (const resultHandler of result.handler) {
|
|
90
|
+
if (resultHandler) {
|
|
91
|
+
handler = resultHandler
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let wrappedHandler = (context, next) => {
|
|
96
|
+
context.res = handler(context)
|
|
97
|
+
next()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
middleware.push(wrappedHandler)
|
|
101
|
+
const composed = compose(middleware)
|
|
102
|
+
const c = this.createContext(request, response)
|
|
103
|
+
|
|
104
|
+
composed(c)
|
|
105
|
+
|
|
106
|
+
return c.res
|
|
66
107
|
}
|
|
67
108
|
|
|
68
|
-
|
|
69
|
-
return new Response(
|
|
70
|
-
status: 404,
|
|
71
|
-
headers: {
|
|
72
|
-
'content-type': 'text/plain',
|
|
73
|
-
},
|
|
74
|
-
})
|
|
109
|
+
async handleEvent(event) {
|
|
110
|
+
return await this.dispatch(event.request, new Response())
|
|
75
111
|
}
|
|
76
112
|
|
|
77
113
|
fire() {
|
|
78
114
|
addEventListener('fetch', (event) => {
|
|
79
|
-
this.
|
|
115
|
+
event.respondWith(this.handleEvent(event))
|
|
80
116
|
})
|
|
81
117
|
}
|
|
82
|
-
}
|
|
83
118
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
(...args) => {
|
|
88
|
-
if (target.constructor.prototype.hasOwnProperty(prop)) {
|
|
89
|
-
return target[prop](...args)
|
|
90
|
-
} else {
|
|
91
|
-
if (args.length === 1) {
|
|
92
|
-
return target.addRoute(prop, target.tempPath, ...args)
|
|
93
|
-
}
|
|
94
|
-
return target.addRoute(prop, ...args)
|
|
95
|
-
}
|
|
96
|
-
},
|
|
119
|
+
notFound() {
|
|
120
|
+
return new Response('Not Found', { status: 404 })
|
|
121
|
+
}
|
|
97
122
|
}
|
|
98
123
|
|
|
99
|
-
const
|
|
124
|
+
const WrappedApp = (router = new App()) => {
|
|
100
125
|
return new Proxy(router, proxyHandler)
|
|
101
126
|
}
|
|
102
127
|
|
|
103
|
-
module.exports =
|
|
128
|
+
module.exports = WrappedApp
|
package/src/hono.test.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const Hono = require('./hono')
|
|
2
2
|
const fetch = require('node-fetch')
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
describe('GET match', () => {
|
|
4
|
+
describe('GET Request', () => {
|
|
5
|
+
const app = Hono()
|
|
7
6
|
app.get('/hello', () => {
|
|
8
7
|
return new fetch.Response('hello', {
|
|
9
8
|
status: 200,
|
|
@@ -14,16 +13,76 @@ describe('GET match', () => {
|
|
|
14
13
|
status: 404,
|
|
15
14
|
})
|
|
16
15
|
}
|
|
17
|
-
it('GET /hello is ok', () => {
|
|
16
|
+
it('GET /hello is ok', async () => {
|
|
18
17
|
let req = new fetch.Request('https://example.com/hello')
|
|
19
|
-
let res = app.dispatch(req)
|
|
18
|
+
let res = await app.dispatch(req, new fetch.Response())
|
|
20
19
|
expect(res).not.toBeNull()
|
|
21
20
|
expect(res.status).toBe(200)
|
|
21
|
+
expect(await res.text()).toBe('hello')
|
|
22
22
|
})
|
|
23
|
-
it('GET / is not found', () => {
|
|
23
|
+
it('GET / is not found', async () => {
|
|
24
24
|
let req = new fetch.Request('https://example.com/')
|
|
25
|
-
let res = app.dispatch(req)
|
|
25
|
+
let res = await app.dispatch(req, new fetch.Response())
|
|
26
26
|
expect(res).not.toBeNull()
|
|
27
27
|
expect(res.status).toBe(404)
|
|
28
28
|
})
|
|
29
29
|
})
|
|
30
|
+
|
|
31
|
+
describe('params and query', () => {
|
|
32
|
+
const app = Hono()
|
|
33
|
+
|
|
34
|
+
app.get('/entry/:id', async (c) => {
|
|
35
|
+
const id = await c.req.params('id')
|
|
36
|
+
return new fetch.Response(`id is ${id}`, {
|
|
37
|
+
status: 200,
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
app.get('/search', async (c) => {
|
|
42
|
+
const name = await c.req.query('name')
|
|
43
|
+
return new fetch.Response(`name is ${name}`, {
|
|
44
|
+
status: 200,
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('params of /entry/:id is found', async () => {
|
|
49
|
+
let req = new fetch.Request('https://example.com/entry/123')
|
|
50
|
+
let res = await app.dispatch(req)
|
|
51
|
+
expect(res.status).toBe(200)
|
|
52
|
+
expect(await res.text()).toBe('id is 123')
|
|
53
|
+
})
|
|
54
|
+
it('query of /search?name=sam is found', async () => {
|
|
55
|
+
let req = new fetch.Request('https://example.com/search?name=sam')
|
|
56
|
+
let res = await app.dispatch(req)
|
|
57
|
+
expect(res.status).toBe(200)
|
|
58
|
+
expect(await res.text()).toBe('name is sam')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('Middleware', () => {
|
|
63
|
+
const app = Hono()
|
|
64
|
+
|
|
65
|
+
const logger = (c, next) => {
|
|
66
|
+
console.log(`${c.req.method} : ${c.req.url}`)
|
|
67
|
+
next()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const customHeader = (c, next) => {
|
|
71
|
+
next()
|
|
72
|
+
c.res.headers.append('x-message', 'custom-header')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
app.use('*', logger)
|
|
76
|
+
app.use('/hello', customHeader)
|
|
77
|
+
app.get('/hello', () => {
|
|
78
|
+
return new fetch.Response('hello')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('logging and custom header', async () => {
|
|
82
|
+
let req = new fetch.Request('https://example.com/hello')
|
|
83
|
+
let res = await app.dispatch(req)
|
|
84
|
+
expect(res.status).toBe(200)
|
|
85
|
+
expect(await res.text()).toBe('hello')
|
|
86
|
+
expect(await res.headers.get('x-message')).toBe('custom-header')
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const filter = (c, next) => {
|
|
2
|
+
const url = new URL(c.req.url)
|
|
3
|
+
c.req.query = (key) => {
|
|
4
|
+
return url.searchParams.get(key)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
next()
|
|
8
|
+
|
|
9
|
+
if (typeof c.res === 'string') {
|
|
10
|
+
c.res = new Response(c.res, {
|
|
11
|
+
status: 200,
|
|
12
|
+
headers: {
|
|
13
|
+
'Content-Type': 'text/plain',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = filter
|
package/src/node.js
CHANGED
|
@@ -2,7 +2,7 @@ const methodNameOfAll = 'all'
|
|
|
2
2
|
|
|
3
3
|
class Result {
|
|
4
4
|
constructor({ handler, params } = {}) {
|
|
5
|
-
this.handler = handler
|
|
5
|
+
this.handler = handler || []
|
|
6
6
|
this.params = params || {}
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -32,9 +32,7 @@ class Node {
|
|
|
32
32
|
curNode = curNode.children[p]
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
curNode.method[method] = handler
|
|
37
|
-
}
|
|
35
|
+
curNode.method[method] = handler
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
splitPath(path) {
|
|
@@ -66,11 +64,17 @@ class Node {
|
|
|
66
64
|
|
|
67
65
|
search(method, path) {
|
|
68
66
|
let curNode = this
|
|
69
|
-
|
|
67
|
+
let [handler, params] = [, {}]
|
|
70
68
|
|
|
71
69
|
if (path === '/') {
|
|
72
70
|
const root = this.children['/']
|
|
73
|
-
if (!root.
|
|
71
|
+
if (!root) return this.noRoute()
|
|
72
|
+
// app.get('*', () => 'All')
|
|
73
|
+
const rootAsterisk = root.children['*']
|
|
74
|
+
if (rootAsterisk) {
|
|
75
|
+
handler =
|
|
76
|
+
rootAsterisk.method[method] || rootAsterisk.method[methodNameOfAll]
|
|
77
|
+
} else if (!root.method[method]) {
|
|
74
78
|
return this.noRoute()
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -109,14 +113,14 @@ class Node {
|
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
handler =
|
|
117
|
+
handler || curNode.method[methodNameOfAll] || curNode.method[method]
|
|
113
118
|
|
|
114
|
-
if (handler) {
|
|
115
|
-
const res = new Result({ handler: handler, params: params })
|
|
116
|
-
return res
|
|
117
|
-
} else {
|
|
119
|
+
if (!handler) {
|
|
118
120
|
return this.noRoute()
|
|
119
121
|
}
|
|
122
|
+
const res = new Result({ handler: handler, params: params })
|
|
123
|
+
return res
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
noRoute() {
|
package/src/node.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const Node = require('./node')
|
|
2
|
-
const node = new Node()
|
|
3
2
|
|
|
4
3
|
describe('Util Methods', () => {
|
|
4
|
+
const node = new Node()
|
|
5
5
|
it('node.splitPath', () => {
|
|
6
6
|
let ps = node.splitPath('/')
|
|
7
7
|
expect(ps[0]).toBe('/')
|
|
@@ -11,7 +11,34 @@ describe('Util Methods', () => {
|
|
|
11
11
|
})
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
+
describe('Root Node', () => {
|
|
15
|
+
const node = new Node()
|
|
16
|
+
node.insert('get', '/', 'get root')
|
|
17
|
+
it('get /', () => {
|
|
18
|
+
expect(node.search('get', '/')).not.toBeNull()
|
|
19
|
+
expect(node.search('get', '/hello')).toBeNull()
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('Root Node', () => {
|
|
24
|
+
const node = new Node()
|
|
25
|
+
node.insert('get', '/hello', 'get hello')
|
|
26
|
+
it('get /', () => {
|
|
27
|
+
expect(node.search('get', '/')).toBeNull()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('All', () => {
|
|
32
|
+
const node = new Node()
|
|
33
|
+
node.insert('get', '*', 'get all')
|
|
34
|
+
it('get /', () => {
|
|
35
|
+
expect(node.search('get', '/')).not.toBeNull()
|
|
36
|
+
expect(node.search('get', '/hello')).not.toBeNull()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
14
40
|
describe('Basic Usage', () => {
|
|
41
|
+
const node = new Node()
|
|
15
42
|
node.insert('get', '/hello', 'get hello')
|
|
16
43
|
node.insert('post', '/hello', 'post hello')
|
|
17
44
|
node.insert('get', '/hello/foo', 'get hello foo')
|
|
@@ -38,6 +65,7 @@ describe('Basic Usage', () => {
|
|
|
38
65
|
})
|
|
39
66
|
|
|
40
67
|
describe('Name path', () => {
|
|
68
|
+
const node = new Node()
|
|
41
69
|
it('get /entry/123', () => {
|
|
42
70
|
node.insert('get', '/entry/:id', 'get entry')
|
|
43
71
|
let res = node.search('get', '/entry/123')
|
|
@@ -65,6 +93,7 @@ describe('Name path', () => {
|
|
|
65
93
|
})
|
|
66
94
|
|
|
67
95
|
describe('Wildcard', () => {
|
|
96
|
+
const node = new Node()
|
|
68
97
|
it('/wildcard-abc/xxxxxx/wildcard-efg', () => {
|
|
69
98
|
node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')
|
|
70
99
|
res = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')
|
|
@@ -74,6 +103,7 @@ describe('Wildcard', () => {
|
|
|
74
103
|
})
|
|
75
104
|
|
|
76
105
|
describe('Regexp', () => {
|
|
106
|
+
const node = new Node()
|
|
77
107
|
node.insert(
|
|
78
108
|
'get',
|
|
79
109
|
'/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}',
|
|
@@ -97,6 +127,7 @@ describe('Regexp', () => {
|
|
|
97
127
|
})
|
|
98
128
|
|
|
99
129
|
describe('All', () => {
|
|
130
|
+
const node = new Node()
|
|
100
131
|
node.insert('all', '/all-methods', 'all methods')
|
|
101
132
|
it('/all-methods', () => {
|
|
102
133
|
res = node.search('get', '/all-methods')
|
package/src/router.test.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
const
|
|
2
|
-
let router = Router()
|
|
1
|
+
const App = require('./hono')
|
|
3
2
|
|
|
4
3
|
describe('Basic Usage', () => {
|
|
4
|
+
let router = App()
|
|
5
|
+
|
|
5
6
|
it('get, post hello', () => {
|
|
6
7
|
router.get('/hello', 'get hello')
|
|
7
8
|
router.post('/hello', 'post hello')
|
|
8
9
|
|
|
9
10
|
let res = router.matchRoute('GET', '/hello')
|
|
10
11
|
expect(res).not.toBeNull()
|
|
11
|
-
expect(res.handler).toBe('get hello')
|
|
12
|
+
expect(res.handler[0]).toBe('get hello')
|
|
12
13
|
|
|
13
14
|
res = router.matchRoute('POST', '/hello')
|
|
14
15
|
expect(res).not.toBeNull()
|
|
15
|
-
expect(res.handler).toBe('post hello')
|
|
16
|
+
expect(res.handler[0]).toBe('post hello')
|
|
16
17
|
|
|
17
18
|
res = router.matchRoute('PUT', '/hello')
|
|
18
19
|
expect(res).toBeNull()
|
|
@@ -23,24 +24,28 @@ describe('Basic Usage', () => {
|
|
|
23
24
|
})
|
|
24
25
|
|
|
25
26
|
describe('Complex', () => {
|
|
27
|
+
let router = App()
|
|
28
|
+
|
|
26
29
|
it('Named Param', () => {
|
|
27
30
|
router.get('/entry/:id', 'get entry')
|
|
28
31
|
res = router.matchRoute('GET', '/entry/123')
|
|
29
32
|
expect(res).not.toBeNull()
|
|
30
|
-
expect(res.handler).toBe('get entry')
|
|
33
|
+
expect(res.handler[0]).toBe('get entry')
|
|
31
34
|
expect(res.params['id']).toBe('123')
|
|
32
35
|
})
|
|
36
|
+
|
|
33
37
|
it('Wildcard', () => {
|
|
34
38
|
router.get('/wild/*/card', 'get wildcard')
|
|
35
39
|
res = router.matchRoute('GET', '/wild/xxx/card')
|
|
36
40
|
expect(res).not.toBeNull()
|
|
37
|
-
expect(res.handler).toBe('get wildcard')
|
|
41
|
+
expect(res.handler[0]).toBe('get wildcard')
|
|
38
42
|
})
|
|
43
|
+
|
|
39
44
|
it('Regexp', () => {
|
|
40
45
|
router.get('/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')
|
|
41
46
|
res = router.matchRoute('GET', '/post/20210101/hello')
|
|
42
47
|
expect(res).not.toBeNull()
|
|
43
|
-
expect(res.handler).toBe('get post')
|
|
48
|
+
expect(res.handler[0]).toBe('get post')
|
|
44
49
|
expect(res.params['date']).toBe('20210101')
|
|
45
50
|
expect(res.params['title']).toBe('hello')
|
|
46
51
|
res = router.matchRoute('GET', '/post/onetwothree')
|
|
@@ -51,25 +56,28 @@ describe('Complex', () => {
|
|
|
51
56
|
})
|
|
52
57
|
|
|
53
58
|
describe('Chained Route', () => {
|
|
59
|
+
let router = App()
|
|
60
|
+
|
|
54
61
|
it('Return rooter object', () => {
|
|
55
62
|
router = router.patch('/hello', 'patch hello')
|
|
56
63
|
expect(router).not.toBeNull()
|
|
57
64
|
router = router.delete('/hello', 'delete hello')
|
|
58
65
|
res = router.matchRoute('DELETE', '/hello')
|
|
59
66
|
expect(res).not.toBeNull()
|
|
60
|
-
expect(res.handler).toBe('delete hello')
|
|
67
|
+
expect(res.handler[0]).toBe('delete hello')
|
|
61
68
|
})
|
|
69
|
+
|
|
62
70
|
it('Chain with route method', () => {
|
|
63
71
|
router.route('/api/book').get('get book').post('post book').put('put book')
|
|
64
72
|
res = router.matchRoute('GET', '/api/book')
|
|
65
73
|
expect(res).not.toBeNull()
|
|
66
|
-
expect(res.handler).toBe('get book')
|
|
74
|
+
expect(res.handler[0]).toBe('get book')
|
|
67
75
|
res = router.matchRoute('POST', '/api/book')
|
|
68
76
|
expect(res).not.toBeNull()
|
|
69
|
-
expect(res.handler).toBe('post book')
|
|
77
|
+
expect(res.handler[0]).toBe('post book')
|
|
70
78
|
res = router.matchRoute('PUT', '/api/book')
|
|
71
79
|
expect(res).not.toBeNull()
|
|
72
|
-
expect(res.handler).toBe('put book')
|
|
80
|
+
expect(res.handler[0]).toBe('put book')
|
|
73
81
|
res = router.matchRoute('DELETE', '/api/book')
|
|
74
82
|
expect(res).toBeNull()
|
|
75
83
|
})
|
package/hono.mini.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const Router=require("./router");class Route{constructor(t,e){this.method=t;this.handler=e}}class App{constructor(){this.router=new Router}addRoute(t,e,n){this.router.add(e,new Route(t,n))}handle(t){const e=this.dispatch(t.request);return t.respondWith(e)}dispatch(t){const e=new URL(t.url);const n=e.pathname;const r=this.router.match(n);if(!r){return this.notFound()}const o=t.method.toLowerCase();const s=r[0];if(s.method==o){const i=s.handler;return i(t)}return this.notFound()}notFound(){return new Response("Not Found",{status:404,headers:{"content-type":"text/plain"}})}fire(){addEventListener("fetch",t=>{this.handle(t)})}}const proxyHandler={get:(e,n)=>(...t)=>{if(e.constructor.prototype.hasOwnProperty(n)){return e[n](t[0])}else{e.addRoute(n,t[0],t[1]);return}}};const app=new App;function Hono(){return new Proxy(app,proxyHandler)}module.exports=Hono;class Router{constructor(){this.node=new Node({label:"/"})}add(t,e){this.node.insert(t,e)}match(t){return this.node.search(t)}}class Node{constructor({label:t,stuff:e,children:n}={}){this.label=t||"";this.stuff=e||{};this.children=n||[]}insert(t,e){let n=this;if(t=="/"){n.label=t;n.stuff=e}const r=this.splitPath(t);for(const o of r){let t=n.children[o];if(t){n=t}else{n.children[o]=new Node({label:o,stuff:e,children:[]});n=n.children[o]}}}splitPath(t){const e=[];for(const n of t.split("/")){if(n){e.push(n)}}return e}getPattern(t){const e=t.match(/^\:.+?\{(.+)\}$/);if(e){return"("+e[1]+")"}return"(.+)"}getParamName(t){const e=t.match(/^\:([^\{\}]+)/);if(e){return e[1]}}noRoute(){return null}search(t){let e=this;const n={};for(const r of this.splitPath(t)){const o=e.children[r];if(o){e=o;continue}if(Object.keys(e.children).length==0){if(e.label!=r){return this.noRoute()}break}let t=false;for(const s in e.children){if(s=="*"){e=e.children[s];t=true;break}if(s.match(/^:/)){const i=this.getPattern(s);const c=r.match(new RegExp(i));if(c){const h=this.getParamName(s);n[h]=c[0];e=e.children[s];t=true;break}return this.noRoute()}}if(t==false){return this.noRoute()}}return[e.stuff,n]}}module.exports=Router;
|
package/src/node.root.test.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const Node = require('./node')
|
|
2
|
-
|
|
3
|
-
describe('Basic Usage', () => {
|
|
4
|
-
const node = new Node()
|
|
5
|
-
node.insert('get', '/', 'get root')
|
|
6
|
-
it('get /', () => {
|
|
7
|
-
expect(node.search('get', '/')).not.toBeNull()
|
|
8
|
-
})
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
describe('Basic Usage', () => {
|
|
12
|
-
const node = new Node()
|
|
13
|
-
node.insert('get', '/hello', 'get hello')
|
|
14
|
-
it('get /', () => {
|
|
15
|
-
expect(node.search('get', '/')).toBeNull()
|
|
16
|
-
})
|
|
17
|
-
})
|