この記事何?
エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。
でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono
#81 feat: builtin middleware as sub module
ビルトインミドルウェアをサブモジュールとして分離。必要なものだけインポートできるように変更。
BREAKING CHANGES
- package.json に exports を追加
package.json:
{
"name": "hono",
"main": "dist/index.js",
"types": "dist/index.d.ts",
// ← これを追加
+ "exports": {
+ ".": "./dist/index.js",
+ "./basic-auth": "./dist/middleware/basic-auth/basic-auth.js",
+ "./body-parse": "./dist/middleware/body-parse/body-parse.js",
+ "./cookie": "./dist/middleware/cookie/cookie.js",
+ "./cors": "./dist/middleware/cors/cors.js",
+ "./logger": "./dist/middleware/logger/logger.js",
+ "./mustache": "./dist/middleware/mustache/mustache.js",
+ "./powered-by": "./dist/middleware/powered-by/powered-by.js",
+ "./serve-static": "./dist/middleware/serve-static/serve-static.js"
+ },
// TypeScript の型定義も追加
+ "typesVersions": {
+ "*": {
+ "basic-auth": ["./dist/middleware/basic-auth/basic-auth.d.ts"],
+ "body-parse": ["./dist/middleware/body-parse/body-parse.d.ts"],
+ "cookie": ["./dist/middleware/cookie/cookie.d.ts"],
+ "cors": ["./dist/middleware/cors/cors.d.ts"],
+ "logger": ["./dist/middleware/logger/logger.d.ts"],
+ "mustache": ["./dist/middleware/mustache/mustache.d.ts"],
+ "powered-by": ["./dist/middleware/powered-by/powered-by.d.ts"],
+ "serve-static": ["./dist/middleware/serve-static/serve-static.d.ts"]
+ }
+ }
}
- Middleware クラスからミドルウェアを削除
src/middleware.ts
import { defaultMiddleware } from './middleware/default'
-import { poweredBy } from './middleware/powered-by/powered-by'
-import { logger } from './middleware/logger/logger'
-import { basicAuth } from './middleware/basic-auth/basic-auth'
-import { bodyParse } from './middleware/body-parse/body-parse'
-import { cors } from './middleware/cors/cors'
-import { cookie } from './middleware/cookie/cookie'
-import { mustache } from './middleware/mustache/mustache'
-import { serveStatic } from './middleware/serve-static/serve-static'
export class Middleware {
static default = defaultMiddleware
- static poweredBy = poweredBy
- static logger = logger
- static basicAuth = basicAuth
- static bodyParse = bodyParse
- static cors = cors
- static cookie = cookie
- static mustache = mustache
- static serveStatic = serveStatic
}
使い方がどう変わるか
変更前(v0.2.4以前)
import { Hono, Middleware } from 'hono'
const app = new Hono()
app.use('*', Middleware.poweredBy())
app.use('*', Middleware.logger())
app.use('*', Middleware.cors())
問題: 使わないミドルウェアもバンドルに含まれる。
変更後(v0.3.0以降)
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
const app = new Hono()
app.use('*', poweredBy())
app.use('*', logger())
app.use('*', cors())
メリット: 必要なミドルウェアだけバンドルされる。
#82 なし
#83 fix: basic auth
Basic Auth で Buffer が使えない環境に対応。buffer パッケージを動的に読み込むように修正。
問題
// Basic Auth の実装(変更前)
function decodeBase64(str: string) {
return Buffer.from(str, 'base64').toString()
// ↑ Buffer が存在しない環境でエラー
}
Buffer が存在しない環境ってなに?と思ってチャッピーに聞くと以下のような環境らしい。ほんとか裏付けはしてない。
- Cloudflare Workers(Buffer がない)
- Webpack 5(ポリフィルが自動で含まれない)
修正内容
src/middleware/basic-auth/basic-auth.ts:
// グローバル変数で Buffer を保持
let b: BufferConstructor
function decodeBase64(str: string) {
- return Buffer.from(str, 'base64').toString()
+ return b.from(str, 'base64').toString() // ← グローバル変数を使用
}
export const basicAuth = (options: { username: string; password: string; realm?: string }) => {
+ // まずグローバルな Buffer を試す
+ try {
+ b = Buffer
+ } catch {}
+
+ // なければ buffer パッケージを require
+ if (b === undefined) {
+ try {
+ const { Buffer } = require('buffer')
+ b = Buffer
+ } catch (e) {
+ throw new Error('If you want to use Basic Auth Middleware, install "buffer" package.')
+ }
+ }
if (!options.realm) {
options.realm = 'Secure Area'
}
return async (ctx: Context, next: Function) => {
// ...
}
}
動作の流れ
-
グローバルな Buffer が存在するか確認
↓ あれば使う
b = Buffer -
なければ buffer パッケージを require
↓
const { Buffer } = require('buffer')
b = Buffer -
それでもなければエラー
↓
throw new Error('install "buffer" package')
#84 fix: use atob for basic auth
Buffer の代わりに atob を使用
これかな?
atob() は Window インターフェイスのメソッドで、 Base64 エンコーディングでエンコードされたデータの文字列をデコードします。 Window.btoa() メソッドを使用して、通信に問題が発生する可能性のあるデータをエンコードして送信し、送信した後に atob() メソッドを使用して再度デコードすることができます。例えば、ASCII の 0 から 31 までのコードような制御文字をエンコードして送信し、デコードすることができます。
ぴんと来てないけど、Buffer の代わりになって、Web APIなのでより軽量で、Cloudflare Workers でも動作するのね。
#85 fix: Unicode problem about atob/btoa
atob の Unicode 文字対応。PR #84 のレビューで指摘された問題を修正。
レビューコメント↓
こんにちは、btoa(および対応するatob)は、1バイトのバイナリデータ(つまり、正確に8ビットのコード単位を持つバイナリ文字列)に収まらない非ASCII Unicode文字でクラッシュします。
参照:https ://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_1_%E2%80%93_escaping_the_string_before_encoding_
atob は ASCII 文字しか正しく扱えない。
// ❌ Unicode 文字(日本語など)が含まれると壊れる
const username = '太郎'
const password = 'パスワード'
const credentials = btoa(`${username}:${password}`)
// → 壊れた Base64 文字列
const decoded = atob(credentials)
// → 文字化け
原因:
- atob は各文字を 1バイトとして扱う
- Unicode 文字(日本語など)は複数バイトなので正しくデコードできない
src/middleware/basic-auth/basic-auth.ts:
function decodeBase64(str: string) {
if (atob) {
- return atob(str) // ← Unicode に対応していない
+ // Unicode 対応のデコード処理
+ const text = atob(str)
+ const length = text.length
+ const bytes = new Uint8Array(length)
+
+ // 各文字をバイトコードに変換
+ for (let i = 0; i < length; i++) {
+ bytes[i] = text.charCodeAt(i)
+ }
+
+ // UTF-8 としてデコード
+ const decoder = new TextDecoder()
+ return decoder.decode(bytes)
} else {
const { Buffer } = require('buffer')
return Buffer.from(str, 'base64').toString()
}
}
処理の流れ
// 入力: Base64 文字列
const base64 = '5aSq6YOO77ya44OR44K544Ov44O844OJ'
// ステップ1: atob でバイナリ文字列に変換
const text = atob(base64)
// → '\xe5\xa4\xaa\xe9\x83\x8e\x...'(バイナリ文字列)
// ステップ2: 各文字をバイトコードに変換
const bytes = new Uint8Array(text.length)
for (let i = 0; i < text.length; i++) {
bytes[i] = text.charCodeAt(i)
}
// → Uint8Array [229, 164, 170, 233, 131, 142, ...]
// ステップ3: UTF-8 としてデコード
const decoder = new TextDecoder()
const result = decoder.decode(bytes)
// → '太郎:パスワード'(正しくデコードされる)
むじゅかしい