hono 0.4.2 → 0.5.2
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 +72 -55
- package/dist/compose.d.ts +2 -1
- package/dist/compose.js +20 -22
- package/dist/hono.d.ts +6 -2
- package/dist/hono.js +22 -16
- package/dist/middleware/basic-auth/basic-auth.d.ts +1 -0
- package/dist/middleware/basic-auth/basic-auth.js +4 -3
- package/dist/middleware/body-parse/body-parse.js +2 -18
- package/dist/middleware/etag/etag.d.ts +6 -0
- package/dist/middleware/etag/etag.js +28 -0
- package/dist/utils/body.d.ts +1 -0
- package/dist/utils/body.js +24 -0
- package/dist/utils/buffer.d.ts +1 -3
- package/dist/utils/buffer.js +7 -46
- package/dist/utils/crypto.d.ts +10 -0
- package/dist/utils/crypto.js +58 -0
- package/dist/utils/mime.js +3 -3
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -23,25 +23,25 @@ app.fire()
|
|
|
23
23
|
|
|
24
24
|
- **Ultrafast** - the router does not use linear loops.
|
|
25
25
|
- **Zero-dependencies** - using only Web standard API.
|
|
26
|
-
- **Middleware** -
|
|
26
|
+
- **Middleware** - built-in middleware and ability to extend with your own middleware.
|
|
27
27
|
- **Optimized** - for Cloudflare Workers.
|
|
28
28
|
|
|
29
29
|
## Benchmark
|
|
30
30
|
|
|
31
|
-
**Hono is fastest
|
|
31
|
+
**Hono is fastest**, compared to other routers for Cloudflare Workers.
|
|
32
32
|
|
|
33
33
|
```plain
|
|
34
|
-
hono x
|
|
35
|
-
itty-router x
|
|
36
|
-
sunder x
|
|
37
|
-
worktop x
|
|
34
|
+
hono x 809,503 ops/sec ±6.94% (73 runs sampled)
|
|
35
|
+
itty-router x 157,310 ops/sec ±4.31% (87 runs sampled)
|
|
36
|
+
sunder x 328,350 ops/sec ±2.30% (95 runs sampled)
|
|
37
|
+
worktop x 209,758 ops/sec ±4.28% (83 runs sampled)
|
|
38
38
|
Fastest is hono
|
|
39
|
-
✨ Done in
|
|
39
|
+
✨ Done in 60.66s.
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Hono in 1 minute
|
|
43
43
|
|
|
44
|
-
A demonstration to create an application
|
|
44
|
+
A demonstration to create an application for Cloudflare Workers with Hono.
|
|
45
45
|
|
|
46
46
|

|
|
47
47
|
|
|
@@ -71,6 +71,8 @@ An instance of `Hono` has these methods.
|
|
|
71
71
|
- app.**all**(path, handler)
|
|
72
72
|
- app.**route**(path)
|
|
73
73
|
- app.**use**(path, middleware)
|
|
74
|
+
- app.**notFound**(handler)
|
|
75
|
+
- app.**onError**(err, handler)
|
|
74
76
|
- app.**fire**()
|
|
75
77
|
- app.**fetch**(request, env, event)
|
|
76
78
|
|
|
@@ -126,10 +128,10 @@ book.post('/', (c) => c.text('Create Book')) // POST /book
|
|
|
126
128
|
|
|
127
129
|
### no strict
|
|
128
130
|
|
|
129
|
-
If `strict` is set
|
|
131
|
+
If `strict` is set false, `/hello`and`/hello/` are treated the same.
|
|
130
132
|
|
|
131
133
|
```js
|
|
132
|
-
const app = new Hono({ strict: false })
|
|
134
|
+
const app = new Hono({ strict: false }) // Default is true
|
|
133
135
|
|
|
134
136
|
app.get('/hello', (c) => c.text('/hello or /hello/'))
|
|
135
137
|
```
|
|
@@ -145,7 +147,7 @@ app.get('/fetch-url', async (c) => {
|
|
|
145
147
|
|
|
146
148
|
## Middleware
|
|
147
149
|
|
|
148
|
-
###
|
|
150
|
+
### Built-in Middleware
|
|
149
151
|
|
|
150
152
|
```js
|
|
151
153
|
import { Hono } from 'hono'
|
|
@@ -166,7 +168,7 @@ app.use(
|
|
|
166
168
|
)
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
Available
|
|
171
|
+
Available built-in middleware is listed on [src/middleware](https://github.com/yusukebe/hono/tree/master/src/middleware).
|
|
170
172
|
|
|
171
173
|
### Custom Middleware
|
|
172
174
|
|
|
@@ -182,12 +184,33 @@ app.use('*', async (c, next) => {
|
|
|
182
184
|
// Add a custom header
|
|
183
185
|
app.use('/message/*', async (c, next) => {
|
|
184
186
|
await next()
|
|
185
|
-
|
|
187
|
+
c.header('x-message', 'This is middleware!')
|
|
186
188
|
})
|
|
187
189
|
|
|
188
190
|
app.get('/message/hello', (c) => c.text('Hello Middleware!'))
|
|
189
191
|
```
|
|
190
192
|
|
|
193
|
+
## Not Found
|
|
194
|
+
|
|
195
|
+
`app.notFound` for customizing Not Found Response.
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
app.notFound((c) => {
|
|
199
|
+
return c.text('Custom 404 Message', 404)
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
|
|
205
|
+
`app.onError` handle the error and return the customized Response.
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
app.onError((err, c) => {
|
|
209
|
+
console.error(`${err}`)
|
|
210
|
+
return c.text('Custom Error Message', 500)
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
191
214
|
## Context
|
|
192
215
|
|
|
193
216
|
To handle Request and Reponse, you can use Context object.
|
|
@@ -224,10 +247,12 @@ app.get('/entry/:id', (c) => {
|
|
|
224
247
|
|
|
225
248
|
```js
|
|
226
249
|
app.get('/welcome', (c) => {
|
|
250
|
+
// Set headers
|
|
227
251
|
c.header('X-Message', 'Hello!')
|
|
228
252
|
c.header('Content-Type', 'text/plain')
|
|
253
|
+
// Set HTTP status code
|
|
229
254
|
c.status(201)
|
|
230
|
-
|
|
255
|
+
// Return the response body
|
|
231
256
|
return c.body('Thank you for comming')
|
|
232
257
|
})
|
|
233
258
|
```
|
|
@@ -248,7 +273,7 @@ new Response('Thank you for comming', {
|
|
|
248
273
|
|
|
249
274
|
### c.text()
|
|
250
275
|
|
|
251
|
-
Render
|
|
276
|
+
Render text as `Content-Type:text/plain`.
|
|
252
277
|
|
|
253
278
|
```js
|
|
254
279
|
app.get('/say', (c) => {
|
|
@@ -278,7 +303,7 @@ app.get('/', (c) => {
|
|
|
278
303
|
|
|
279
304
|
### c.notFound()
|
|
280
305
|
|
|
281
|
-
Return the
|
|
306
|
+
Return the `404 Not Found` Response.
|
|
282
307
|
|
|
283
308
|
```js
|
|
284
309
|
app.get('/notfound', (c) => {
|
|
@@ -327,26 +352,6 @@ app.get('*', async c => {
|
|
|
327
352
|
})
|
|
328
353
|
```
|
|
329
354
|
|
|
330
|
-
## Not Found
|
|
331
|
-
|
|
332
|
-
You can write the default `404 Not Found` Response.
|
|
333
|
-
|
|
334
|
-
```js
|
|
335
|
-
app.notFound = (c) => {
|
|
336
|
-
return c.text('This is default 404 Not Found', 404)
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
## Error handling
|
|
341
|
-
|
|
342
|
-
You can handle errors in your way.
|
|
343
|
-
|
|
344
|
-
```js
|
|
345
|
-
app.onError = (err, c) => {
|
|
346
|
-
return c.text(`This is error message: ${err.mssage}`, 500)
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
355
|
## fire
|
|
351
356
|
|
|
352
357
|
`app.fire()` do this.
|
|
@@ -359,7 +364,7 @@ addEventListener('fetch', (event) => {
|
|
|
359
364
|
|
|
360
365
|
## fetch
|
|
361
366
|
|
|
362
|
-
`app.fetch
|
|
367
|
+
`app.fetch` for Cloudflare Module Worker syntax.
|
|
363
368
|
|
|
364
369
|
```js
|
|
365
370
|
export default {
|
|
@@ -376,19 +381,22 @@ export default app
|
|
|
376
381
|
|
|
377
382
|
## Cloudflare Workers with Hono
|
|
378
383
|
|
|
379
|
-
Using
|
|
384
|
+
Using [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/) or [Miniflare](https://miniflare.dev), you can develop the application locally and publish it with few commands.
|
|
380
385
|
|
|
381
386
|
Let's write your first code for Cloudflare Workers with Hono.
|
|
382
387
|
|
|
383
|
-
|
|
388
|
+
---
|
|
384
389
|
|
|
385
|
-
|
|
390
|
+
### Caution
|
|
386
391
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
392
|
+
**Wrangler 1.x** does not support importing middleware. We recommend two ways:
|
|
393
|
+
|
|
394
|
+
1. Use [Wragler 2.0 Beta](https://github.com/cloudflare/wrangler2).
|
|
395
|
+
2. Build without webpack 4.x. For example, you can use esbuild. See [the starter template](https://github.com/yusukebe/hono-minimal).
|
|
396
|
+
|
|
397
|
+
---
|
|
390
398
|
|
|
391
|
-
###
|
|
399
|
+
### 1. `npm init`
|
|
392
400
|
|
|
393
401
|
Make a npm skeleton directory.
|
|
394
402
|
|
|
@@ -398,15 +406,23 @@ cd hono-example
|
|
|
398
406
|
npm init -y
|
|
399
407
|
```
|
|
400
408
|
|
|
401
|
-
###
|
|
409
|
+
### 2. `wrangler init`
|
|
402
410
|
|
|
403
|
-
|
|
411
|
+
Initialize as a wrangler project.
|
|
404
412
|
|
|
405
|
-
```
|
|
406
|
-
$ wrangler init
|
|
413
|
+
```
|
|
414
|
+
$ npx wrangler@beta init
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Answer the questions. If you want, you can answer `y`.
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
Would you like to install wrangler into your package.json? (y/n) <--- n
|
|
421
|
+
Would you like to use TypeScript? (y/n) <--- n
|
|
422
|
+
Would you like to create a Worker at src/index.js? (y/n) <--- n
|
|
407
423
|
```
|
|
408
424
|
|
|
409
|
-
###
|
|
425
|
+
### 3. `npm install hono`
|
|
410
426
|
|
|
411
427
|
Install `hono` from the npm registry.
|
|
412
428
|
|
|
@@ -414,11 +430,12 @@ Install `hono` from the npm registry.
|
|
|
414
430
|
$ npm i hono
|
|
415
431
|
```
|
|
416
432
|
|
|
417
|
-
###
|
|
433
|
+
### 4. Write your app
|
|
418
434
|
|
|
419
435
|
Only 4 lines!!
|
|
420
436
|
|
|
421
437
|
```js
|
|
438
|
+
// index.js
|
|
422
439
|
import { Hono } from 'hono'
|
|
423
440
|
const app = new Hono()
|
|
424
441
|
|
|
@@ -427,25 +444,25 @@ app.get('/', (c) => c.text('Hello! Hono!'))
|
|
|
427
444
|
app.fire()
|
|
428
445
|
```
|
|
429
446
|
|
|
430
|
-
###
|
|
447
|
+
### 5. Run
|
|
431
448
|
|
|
432
449
|
Run the development server locally. Then, access `http://127.0.0.1:8787/` in your Web browser.
|
|
433
450
|
|
|
434
451
|
```sh
|
|
435
|
-
$ wrangler dev
|
|
452
|
+
$ npx wrangler@beta dev index.js
|
|
436
453
|
```
|
|
437
454
|
|
|
438
|
-
###
|
|
455
|
+
### 6. Publish
|
|
439
456
|
|
|
440
457
|
Deploy to Cloudflare. That's all!
|
|
441
458
|
|
|
442
459
|
```sh
|
|
443
|
-
$ wrangler publish
|
|
460
|
+
$ npx wrangler@beta publish index.js
|
|
444
461
|
```
|
|
445
462
|
|
|
446
463
|
## Starter template
|
|
447
464
|
|
|
448
|
-
You can start making your
|
|
465
|
+
You can start making your Cloudflare Workers application with [the starter template](https://github.com/yusukebe/hono-minimal). It is really minimal using TypeScript, esbuild, and Miniflare.
|
|
449
466
|
|
|
450
467
|
To generate a project skelton, run this command.
|
|
451
468
|
|
package/dist/compose.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ErrorHandler } from './hono';
|
|
2
|
+
export declare const compose: <C>(middleware: Function[], onError?: ErrorHandler) => (context: C) => Promise<C>;
|
package/dist/compose.js
CHANGED
|
@@ -4,33 +4,31 @@ exports.compose = void 0;
|
|
|
4
4
|
const context_1 = require("./context");
|
|
5
5
|
// Based on the code in the MIT licensed `koa-compose` package.
|
|
6
6
|
const compose = (middleware, onError) => {
|
|
7
|
-
|
|
8
|
-
return function (context, next) {
|
|
7
|
+
return function (context) {
|
|
9
8
|
let index = -1;
|
|
10
9
|
return dispatch(0);
|
|
11
10
|
async function dispatch(i) {
|
|
12
|
-
if (i
|
|
13
|
-
return
|
|
14
|
-
index = i;
|
|
15
|
-
let fn = middleware[i];
|
|
16
|
-
if (i === middleware.length)
|
|
17
|
-
fn = next;
|
|
18
|
-
if (!fn)
|
|
19
|
-
return Promise.resolve();
|
|
20
|
-
try {
|
|
21
|
-
return Promise.resolve(fn(context, dispatch.bind(null, i + 1))).catch((e) => {
|
|
22
|
-
errors.push(e);
|
|
23
|
-
if (onError && context instanceof context_1.Context) {
|
|
24
|
-
context.res = onError(errors[0], context);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
throw errors[0];
|
|
28
|
-
}
|
|
29
|
-
});
|
|
11
|
+
if (i === middleware.length) {
|
|
12
|
+
return context;
|
|
30
13
|
}
|
|
31
|
-
|
|
32
|
-
return Promise.reject(
|
|
14
|
+
if (i <= index) {
|
|
15
|
+
return Promise.reject(new Error('next() called multiple times'));
|
|
33
16
|
}
|
|
17
|
+
const handler = middleware[i];
|
|
18
|
+
index = i;
|
|
19
|
+
return Promise.resolve(handler(context, dispatch.bind(null, i + 1)))
|
|
20
|
+
.then(() => {
|
|
21
|
+
return context;
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
if (onError && context instanceof context_1.Context) {
|
|
25
|
+
context.res = onError(err, context);
|
|
26
|
+
return context;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
34
32
|
}
|
|
35
33
|
};
|
|
36
34
|
};
|
package/dist/hono.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare global {
|
|
|
12
12
|
}
|
|
13
13
|
export declare type Handler<RequestParamKeyType = string> = (c: Context<RequestParamKeyType>, next?: Function) => Response | Promise<Response>;
|
|
14
14
|
export declare type MiddlewareHandler = (c: Context, next: Function) => Promise<void>;
|
|
15
|
+
export declare type NotFoundHandler = (c: Context) => Response;
|
|
16
|
+
export declare type ErrorHandler = (err: Error, c: Context) => Response;
|
|
15
17
|
declare type ParamKeyName<NameWithPattern> = NameWithPattern extends `${infer Name}{${infer _Pattern}` ? Name : NameWithPattern;
|
|
16
18
|
declare type ParamKey<Component> = Component extends `:${infer NameWithPattern}` ? ParamKeyName<NameWithPattern> : never;
|
|
17
19
|
declare type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}` ? ParamKey<Component> | ParamKeys<Rest> : ParamKey<Path>;
|
|
@@ -24,6 +26,8 @@ export declare class Hono {
|
|
|
24
26
|
middlewareRouters: Router<MiddlewareHandler>[];
|
|
25
27
|
tempPath: string;
|
|
26
28
|
constructor(init?: Partial<Pick<Hono, 'routerClass' | 'strict'>>);
|
|
29
|
+
notFoundHandler: NotFoundHandler;
|
|
30
|
+
errorHandler: ErrorHandler;
|
|
27
31
|
get<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
28
32
|
post<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
29
33
|
put<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
@@ -34,13 +38,13 @@ export declare class Hono {
|
|
|
34
38
|
all<Path extends string>(path: Path, handler: Handler<ParamKeys<Path>>): Hono;
|
|
35
39
|
route(path: string): Hono;
|
|
36
40
|
use(path: string, middleware: MiddlewareHandler): void;
|
|
41
|
+
onError(handler: ErrorHandler): Hono;
|
|
42
|
+
notFound(handler: NotFoundHandler): Hono;
|
|
37
43
|
addRoute(method: string, path: string, handler: Handler): Hono;
|
|
38
44
|
matchRoute(method: string, path: string): Promise<Result<Handler>>;
|
|
39
45
|
dispatch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
40
46
|
handleEvent(event: FetchEvent): Promise<Response>;
|
|
41
47
|
fetch(request: Request, env?: Env, event?: FetchEvent): Promise<Response>;
|
|
42
48
|
fire(): void;
|
|
43
|
-
onError(err: Error, c: Context): Response;
|
|
44
|
-
notFound(c: Context): Response;
|
|
45
49
|
}
|
|
46
50
|
export {};
|
package/dist/hono.js
CHANGED
|
@@ -10,6 +10,15 @@ class Hono {
|
|
|
10
10
|
constructor(init = {}) {
|
|
11
11
|
this.routerClass = trie_router_1.TrieRouter;
|
|
12
12
|
this.strict = true; // strict routing - default is true
|
|
13
|
+
this.notFoundHandler = (c) => {
|
|
14
|
+
const message = '404 Not Found';
|
|
15
|
+
return c.text(message, 404);
|
|
16
|
+
};
|
|
17
|
+
this.errorHandler = (err, c) => {
|
|
18
|
+
console.error(`${err.message}`);
|
|
19
|
+
const message = 'Internal Server Error';
|
|
20
|
+
return c.text(message, 500);
|
|
21
|
+
};
|
|
13
22
|
Object.assign(this, init);
|
|
14
23
|
this.router = new this.routerClass();
|
|
15
24
|
this.middlewareRouters = [];
|
|
@@ -53,6 +62,14 @@ class Hono {
|
|
|
53
62
|
router.add(router_1.METHOD_NAME_OF_ALL, path, middleware);
|
|
54
63
|
this.middlewareRouters.push(router);
|
|
55
64
|
}
|
|
65
|
+
onError(handler) {
|
|
66
|
+
this.errorHandler = handler;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
notFound(handler) {
|
|
70
|
+
this.notFoundHandler = handler;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
56
73
|
// addRoute('get', '/', handler)
|
|
57
74
|
addRoute(method, path, handler) {
|
|
58
75
|
method = method.toUpperCase();
|
|
@@ -82,7 +99,7 @@ class Hono {
|
|
|
82
99
|
const url = new URL(c.req.url);
|
|
83
100
|
return url.searchParams.get(key);
|
|
84
101
|
};
|
|
85
|
-
const handler = result ? result.handler : this.
|
|
102
|
+
const handler = result ? result.handler : this.notFoundHandler;
|
|
86
103
|
const middleware = [];
|
|
87
104
|
for (const mr of this.middlewareRouters) {
|
|
88
105
|
const mwResult = mr.match(router_1.METHOD_NAME_OF_ALL, path);
|
|
@@ -99,11 +116,11 @@ class Hono {
|
|
|
99
116
|
await next();
|
|
100
117
|
};
|
|
101
118
|
middleware.push(wrappedHandler);
|
|
102
|
-
const composed = (0, compose_1.compose)(middleware, this.
|
|
119
|
+
const composed = (0, compose_1.compose)(middleware, this.errorHandler);
|
|
103
120
|
const c = new context_1.Context(request, { env: env, event: event, res: null });
|
|
104
|
-
c.notFound = () => this.
|
|
105
|
-
await composed(c);
|
|
106
|
-
return
|
|
121
|
+
c.notFound = () => this.notFoundHandler(c);
|
|
122
|
+
const context = await composed(c);
|
|
123
|
+
return context.res;
|
|
107
124
|
}
|
|
108
125
|
async handleEvent(event) {
|
|
109
126
|
return this.dispatch(event.request, {}, event);
|
|
@@ -116,16 +133,5 @@ class Hono {
|
|
|
116
133
|
event.respondWith(this.handleEvent(event));
|
|
117
134
|
});
|
|
118
135
|
}
|
|
119
|
-
// Default error Response
|
|
120
|
-
onError(err, c) {
|
|
121
|
-
console.error(`${err.message}`);
|
|
122
|
-
const message = 'Internal Server Error';
|
|
123
|
-
return c.text(message, 500);
|
|
124
|
-
}
|
|
125
|
-
// Default 404 not found Response
|
|
126
|
-
notFound(c) {
|
|
127
|
-
const message = 'Not Found';
|
|
128
|
-
return c.text(message, 404);
|
|
129
|
-
}
|
|
130
136
|
}
|
|
131
137
|
exports.Hono = Hono;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.basicAuth = void 0;
|
|
4
4
|
const buffer_1 = require("../../utils/buffer");
|
|
5
|
+
const crypto_1 = require("../../utils/crypto");
|
|
5
6
|
const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/;
|
|
6
7
|
const USER_PASS_REGEXP = /^([^:]*):(.*)$/;
|
|
7
8
|
const auth = (req) => {
|
|
@@ -18,7 +19,7 @@ const auth = (req) => {
|
|
|
18
19
|
if (!match) {
|
|
19
20
|
return undefined;
|
|
20
21
|
}
|
|
21
|
-
const userPass = USER_PASS_REGEXP.exec((0,
|
|
22
|
+
const userPass = USER_PASS_REGEXP.exec((0, crypto_1.decodeBase64)(match[1]));
|
|
22
23
|
if (!userPass) {
|
|
23
24
|
return undefined;
|
|
24
25
|
}
|
|
@@ -36,8 +37,8 @@ const basicAuth = (options, ...users) => {
|
|
|
36
37
|
const requestUser = auth(ctx.req);
|
|
37
38
|
if (requestUser) {
|
|
38
39
|
for (const user of users) {
|
|
39
|
-
const usernameEqual = await (0, buffer_1.timingSafeEqual)(user.username, requestUser.username);
|
|
40
|
-
const passwordEqual = await (0, buffer_1.timingSafeEqual)(user.password, requestUser.password);
|
|
40
|
+
const usernameEqual = await (0, buffer_1.timingSafeEqual)(user.username, requestUser.username, options.hashFunction);
|
|
41
|
+
const passwordEqual = await (0, buffer_1.timingSafeEqual)(user.password, requestUser.password, options.hashFunction);
|
|
41
42
|
if (usernameEqual && passwordEqual) {
|
|
42
43
|
// Authorized OK
|
|
43
44
|
return next();
|
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.bodyParse = void 0;
|
|
4
|
+
const body_1 = require("../../utils/body");
|
|
4
5
|
const bodyParse = () => {
|
|
5
6
|
return async (ctx, next) => {
|
|
6
|
-
|
|
7
|
-
if (contentType.includes('application/json')) {
|
|
8
|
-
ctx.req.parsedBody = await ctx.req.json();
|
|
9
|
-
}
|
|
10
|
-
else if (contentType.includes('application/text')) {
|
|
11
|
-
ctx.req.parsedBody = await ctx.req.text();
|
|
12
|
-
}
|
|
13
|
-
else if (contentType.includes('text/html')) {
|
|
14
|
-
ctx.req.parsedBody = await ctx.req.text();
|
|
15
|
-
}
|
|
16
|
-
else if (contentType.includes('form')) {
|
|
17
|
-
const form = {};
|
|
18
|
-
const data = [...(await ctx.req.formData())].reduce((acc, cur) => {
|
|
19
|
-
acc[cur[0]] = cur[1];
|
|
20
|
-
return acc;
|
|
21
|
-
}, form);
|
|
22
|
-
ctx.req.parsedBody = data;
|
|
23
|
-
}
|
|
7
|
+
ctx.req.parsedBody = await (0, body_1.parseBody)(ctx.req);
|
|
24
8
|
await next();
|
|
25
9
|
};
|
|
26
10
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.etag = void 0;
|
|
4
|
+
const crypto_1 = require("../../utils/crypto");
|
|
5
|
+
const body_1 = require("../../utils/body");
|
|
6
|
+
const etag = (options = { weak: false }) => {
|
|
7
|
+
return async (c, next) => {
|
|
8
|
+
const ifNoneMatch = c.req.header('If-None-Match') || c.req.header('if-none-match');
|
|
9
|
+
await next();
|
|
10
|
+
const clone = c.res.clone();
|
|
11
|
+
const body = await (0, body_1.parseBody)(c.res);
|
|
12
|
+
const hash = await (0, crypto_1.sha1)(body);
|
|
13
|
+
const etag = options.weak ? `W/"${hash}"` : `"${hash}"`;
|
|
14
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
15
|
+
await clone.blob(); // Force using body
|
|
16
|
+
c.res = new Response(null, {
|
|
17
|
+
status: 304,
|
|
18
|
+
statusText: 'Not Modified',
|
|
19
|
+
});
|
|
20
|
+
c.res.headers.delete('Content-Length');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
c.res = new Response(clone.body, clone);
|
|
24
|
+
c.res.headers.append('ETag', etag);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
exports.etag = etag;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const parseBody: (r: Request | Response) => Promise<string | object | Record<string, string | File>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBody = void 0;
|
|
4
|
+
const parseBody = async (r) => {
|
|
5
|
+
const contentType = r.headers.get('Content-Type') || '';
|
|
6
|
+
if (contentType.includes('application/json')) {
|
|
7
|
+
return await r.json();
|
|
8
|
+
}
|
|
9
|
+
else if (contentType.includes('application/text')) {
|
|
10
|
+
return r.text();
|
|
11
|
+
}
|
|
12
|
+
else if (contentType.startsWith('text')) {
|
|
13
|
+
return r.text();
|
|
14
|
+
}
|
|
15
|
+
else if (contentType.includes('form')) {
|
|
16
|
+
const form = {};
|
|
17
|
+
const data = [...(await r.formData())].reduce((acc, cur) => {
|
|
18
|
+
acc[cur[0]] = cur[1];
|
|
19
|
+
return acc;
|
|
20
|
+
}, form);
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.parseBody = parseBody;
|
package/dist/utils/buffer.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
1
|
export declare const equal: (a: ArrayBuffer, b: ArrayBuffer) => boolean;
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const sha256: (a: string | object | boolean) => Promise<string>;
|
|
4
|
-
export declare const timingSafeEqual: (a: string | object | boolean, b: string | object | boolean) => Promise<boolean>;
|
|
2
|
+
export declare const timingSafeEqual: (a: string | object | boolean, b: string | object | boolean, hashFunction?: Function) => Promise<boolean>;
|
package/dist/utils/buffer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.timingSafeEqual = exports.
|
|
3
|
+
exports.timingSafeEqual = exports.equal = void 0;
|
|
4
|
+
const crypto_1 = require("./crypto");
|
|
4
5
|
const equal = (a, b) => {
|
|
5
6
|
if (a === b) {
|
|
6
7
|
return true;
|
|
@@ -19,52 +20,12 @@ const equal = (a, b) => {
|
|
|
19
20
|
return true;
|
|
20
21
|
};
|
|
21
22
|
exports.equal = equal;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const length = text.length;
|
|
26
|
-
const bytes = new Uint8Array(length);
|
|
27
|
-
for (let i = 0; i < length; i++) {
|
|
28
|
-
bytes[i] = text.charCodeAt(i);
|
|
29
|
-
}
|
|
30
|
-
const decoder = new TextDecoder();
|
|
31
|
-
return decoder.decode(bytes);
|
|
32
|
-
}
|
|
33
|
-
catch (_a) { }
|
|
34
|
-
try {
|
|
35
|
-
const { Buffer } = require('buffer');
|
|
36
|
-
return Buffer.from(str, 'base64').toString();
|
|
37
|
-
}
|
|
38
|
-
catch (e) {
|
|
39
|
-
console.error('If you want to do "decodeBase64", polyfill "buffer" module.');
|
|
40
|
-
throw e;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
exports.decodeBase64 = decodeBase64;
|
|
44
|
-
const sha256 = async (a) => {
|
|
45
|
-
if (crypto && crypto.subtle) {
|
|
46
|
-
const buffer = await crypto.subtle.digest({
|
|
47
|
-
name: 'SHA-256',
|
|
48
|
-
}, new TextEncoder().encode(String(a)));
|
|
49
|
-
const hash = Array.prototype.map
|
|
50
|
-
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
|
|
51
|
-
.join('');
|
|
52
|
-
return hash;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
const crypto = require('crypto');
|
|
56
|
-
const hash = crypto.createHash('sha256').update(a).digest('hex');
|
|
57
|
-
return hash;
|
|
23
|
+
const timingSafeEqual = async (a, b, hashFunction) => {
|
|
24
|
+
if (!hashFunction) {
|
|
25
|
+
hashFunction = crypto_1.sha256;
|
|
58
26
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
throw e;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
exports.sha256 = sha256;
|
|
65
|
-
const timingSafeEqual = async (a, b) => {
|
|
66
|
-
const sa = await (0, exports.sha256)(a);
|
|
67
|
-
const sb = await (0, exports.sha256)(b);
|
|
27
|
+
const sa = await hashFunction(a);
|
|
28
|
+
const sb = await hashFunction(b);
|
|
68
29
|
return sa === sb && a === b;
|
|
69
30
|
};
|
|
70
31
|
exports.timingSafeEqual = timingSafeEqual;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare type Algorithm = {
|
|
2
|
+
name: string;
|
|
3
|
+
alias: string;
|
|
4
|
+
};
|
|
5
|
+
declare type Data = string | object | boolean;
|
|
6
|
+
export declare const sha256: (data: Data) => Promise<string>;
|
|
7
|
+
export declare const sha1: (data: Data) => Promise<string>;
|
|
8
|
+
export declare const createHash: (data: Data, algorithm: Algorithm) => Promise<string>;
|
|
9
|
+
export declare const decodeBase64: (str: string) => any;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeBase64 = exports.createHash = exports.sha1 = exports.sha256 = void 0;
|
|
4
|
+
const sha256 = async (data) => {
|
|
5
|
+
const algorithm = { name: 'SHA-256', alias: 'sha256' };
|
|
6
|
+
const hash = await (0, exports.createHash)(data, algorithm);
|
|
7
|
+
return hash;
|
|
8
|
+
};
|
|
9
|
+
exports.sha256 = sha256;
|
|
10
|
+
const sha1 = async (data) => {
|
|
11
|
+
const algorithm = { name: 'SHA-1', alias: 'sha1' };
|
|
12
|
+
const hash = await (0, exports.createHash)(data, algorithm);
|
|
13
|
+
return hash;
|
|
14
|
+
};
|
|
15
|
+
exports.sha1 = sha1;
|
|
16
|
+
const createHash = async (data, algorithm) => {
|
|
17
|
+
if (crypto && crypto.subtle) {
|
|
18
|
+
const buffer = await crypto.subtle.digest({
|
|
19
|
+
name: algorithm.name,
|
|
20
|
+
}, new TextEncoder().encode(String(data)));
|
|
21
|
+
const hash = Array.prototype.map
|
|
22
|
+
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
|
|
23
|
+
.join('');
|
|
24
|
+
return hash;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const crypto = require('crypto');
|
|
28
|
+
const hash = crypto.createHash(algorithm.alias).update(data).digest('hex');
|
|
29
|
+
return hash;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error(`If you want to create hash ${algorithm.name}, polyfill "crypto" module.`);
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
exports.createHash = createHash;
|
|
37
|
+
const decodeBase64 = (str) => {
|
|
38
|
+
try {
|
|
39
|
+
const text = atob(str);
|
|
40
|
+
const length = text.length;
|
|
41
|
+
const bytes = new Uint8Array(length);
|
|
42
|
+
for (let i = 0; i < length; i++) {
|
|
43
|
+
bytes[i] = text.charCodeAt(i);
|
|
44
|
+
}
|
|
45
|
+
const decoder = new TextDecoder();
|
|
46
|
+
return decoder.decode(bytes);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) { }
|
|
49
|
+
try {
|
|
50
|
+
const { Buffer } = require('buffer');
|
|
51
|
+
return Buffer.from(str, 'base64').toString();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error('If you want to do "decodeBase64", polyfill "buffer" module.');
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
exports.decodeBase64 = decodeBase64;
|
package/dist/utils/mime.js
CHANGED
|
@@ -4,11 +4,10 @@ exports.getMimeType = void 0;
|
|
|
4
4
|
const getMimeType = (filename) => {
|
|
5
5
|
const regexp = /\.([a-zA-Z0-9]+?)$/;
|
|
6
6
|
const match = filename.match(regexp);
|
|
7
|
-
if (!match)
|
|
7
|
+
if (!match)
|
|
8
8
|
return;
|
|
9
|
-
}
|
|
10
9
|
let mimeType = mimes[match[1]];
|
|
11
|
-
if (mimeType.startsWith('text') || mimeType === 'application/json') {
|
|
10
|
+
if ((mimeType && mimeType.startsWith('text')) || mimeType === 'application/json') {
|
|
12
11
|
mimeType += '; charset=utf-8';
|
|
13
12
|
}
|
|
14
13
|
return mimeType;
|
|
@@ -43,6 +42,7 @@ const mimes = {
|
|
|
43
42
|
js: 'text/javascript',
|
|
44
43
|
json: 'application/json',
|
|
45
44
|
jsonld: 'application/ld+json',
|
|
45
|
+
map: 'application/json',
|
|
46
46
|
mid: 'audio/x-midi',
|
|
47
47
|
midi: 'audio/x-midi',
|
|
48
48
|
mjs: 'text/javascript',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "[炎] Ultrafast web framework for Cloudflare Workers.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -78,11 +78,13 @@
|
|
|
78
78
|
],
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@cloudflare/workers-types": "^3.3.0",
|
|
81
|
+
"@types/crypto-js": "^4.1.1",
|
|
81
82
|
"@types/jest": "^27.4.0",
|
|
82
83
|
"@types/mustache": "^4.1.2",
|
|
83
84
|
"@types/node": "^17.0.8",
|
|
84
85
|
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
|
85
86
|
"@typescript-eslint/parser": "^5.9.0",
|
|
87
|
+
"crypto-js": "^4.1.1",
|
|
86
88
|
"eslint": "^7.26.0",
|
|
87
89
|
"eslint-config-prettier": "^8.3.0",
|
|
88
90
|
"eslint-define-config": "^1.2.1",
|