この記事何?
エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。
でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono
#66 perf: content length
マージではなくcloseされているためスキップ
#67 perf: content length
Content-Length 計算の高速化
変更前(PR #57)
// src/middleware/default.ts
export const defaultMiddleware = async (c: Context, next: Function) => {
// ...
await next()
// すべてのレスポンスで実行される(遅い)
if (c.res.body) {
const buffer = await c.res.arrayBuffer()
const res = new Response(buffer, {
status: c.res.status,
statusText: c.res.statusText,
headers: c.res.headers,
})
res.headers.append('Content-Length', buffer.byteLength.toString())
c.res = res
}
}
問題点:
- arrayBuffer() は重い
- すべてのレスポンスで実行される
- レスポンス生成後に追加処理が必要
変更後
// src/context.ts
newResponse(data: any, init: ResponseInit = {}): Response {
init.status = this._status || init.status
init.statusText = this._statusText || init.statusText
// Content-Length を計算(同期的)
const Encoder = new TextEncoder()
const length = data ? data.byteLength || Encoder.encode(data).byteLength : 0
// ↑ 文字列の場合はエンコードしてバイト数取得
init.headers = {
...init.headers,
...this._headers,
...{ 'Content-Length': String(length) }
}
return new Response(data, init)
}
// src/middleware/default.ts
export const defaultMiddleware = async (c: Context, next: Function) => {
// ...
await next()
// Content-Length 処理を削除!
}
- レスポンス生成時に直接計算
// c.text(), c.json(), c.html() などが newResponse() を呼ぶ
app.get('/', (c) => c.text('Hello'))
- 同期処理に変更
- デフォルトミドルウェアから削除
#68 なし
69 fix: Fixed context headers
Context のヘッダー処理のバグ修正
- ヘッダーとステータスの優先順位を修正
#70 fix: change c.body() api
// 変更前
export class Context {
body: (body: BodyInit, init?: ResponseInit) => Response
constructor(req: Request, opts?: { res: Response; env: Env; event: FetchEvent }) {
// ...
this.body = this.newResponse // ← newResponseのエイリアスだった
}
}
// 変更後
export class Context {
render: (template: string, params?: object, options?: object) => Promise<Response>
// ↑ renderが追加された
constructor(req: Request, opts?: { res: Response; env: Env; event: FetchEvent }) {
// ...
// this.body = this.newResponse // ← 削除
}
// c.body() が専用メソッドとして実装された
body(data: any, status: number = this._status, headers: Headers = this._headers): Response {
return this.newResponse(data, {
status: status,
headers: headers,
})
}
}
「前より遅くなっちゃったな〜」という箇所を見つけるのはbugfix的に見つけられそうだけど、こうしたら速くなる!という代替案はどうやって見つけてるんだろう。
arrayBuffer()よりTextEncoderにした方が速い、というのをAIに聞かないと全然わからないし、AIに聞いても内部処理を理解してないから「ほえー、そうなんだぁ」になる。
使う道具の良さが分ってないので「100均の道具で充分でしょ」ってところと「いや、質も考えたらメーカーのちゃんとしたもののほうがよく動くよ」が判断できない。うーむ。どうやったら鍛えられるんだろう。