0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HonoのマージされたPRを読んでいく#81 - #85

0
Last updated at Posted at 2025-12-16

この記事何?

エンジニア歴1年半。業務で利用しているHonoが大好きだが、TSもプログラミング知識も弱々すぎて上手く使いこなせない。速いはずが多分生かせてない。
ちゃんと理解するためにソースコードリーディングしたい。

でも目的なくソースコード眺めても意味ないし……ねむい……
そうだ!PRを全部読めば、変遷やなぜ変更されているかストーリー的に分かるのでは。
と思ったのでチャレンジしてみる。PR2000以上あるので、全部できるかは知らん。
https://github.com/honojs/hono

#81 feat: builtin middleware as sub module

ビルトインミドルウェアをサブモジュールとして分離。必要なものだけインポートできるように変更。
BREAKING CHANGES

  1. 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"]
  +    }
  +  }
  }
  1. 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) => {
      // ...
    }
  }

動作の流れ

  1. グローバルな Buffer が存在するか確認
    ↓ あれば使う
    b = Buffer

  2. なければ buffer パッケージを require
    ↓
    const { Buffer } = require('buffer')
    b = Buffer

  3. それでもなければエラー
    ↓
    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)
  // → '太郎:パスワード'(正しくデコードされる)

むじゅかしい

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?