Shogo's Blog

Jun 23, 2024 - 3 minute read - typescript

npmとjsrにパッケージを公開してみた (認証バッジ付)

先日、同時実行数を制限しながら並行実行する関数を書きました。 TypeScriptで同時実行数を制限しながら並行実行する 便利関数を作ったら他のプロジェクトから参照したいですよね。 そこでパッケージレジストリに登録してみました。 正直コピペで実装で十分なのでは?という分量ですが、パッケージ公開の練習です。 ソースコードを準備する まずは公開するソースコードを準備していきましょう。 ソースコードはGitHubで公開しました。 shogo82148/limit-concurrency - GitHub Denoの開発環境を整える TypeScriptの開発環境を整えたいのですが、Node+TypeScriptの組み合わせはプロジェクトの立ち上げは意外と面倒です。 そこで今回は Deno を使ってみることにしました。 DenoはTypeScriptの実行ランタイムとして開発されており、特別な設定なしでTypeScriptを実行できます。 Brewでインストールしました。 brew install deno 僕は最近エディターには VS Code を使っているので、Deno用の拡張機能をインストールしました。 denoland/vscode_deno インストールしただけでは有効化されません。 ワークスペースの設定ファイル .vscode/settings.json を編集して、明示的に有効化します。 { "deno.enable": true, "deno.lint": true, "editor.formatOnSave": true, "editor.defaultFormatter": "denoland.vscode-deno", "[typescript]": { "editor.defaultFormatter": "denoland.vscode-deno" }, } 開発環境が整ったらパッケージ本体のソースコードを書いていきます。 GitHub Actionsでテストを実行する パッケージとして公開するのであれば、テストを書いて、CIを回しておきたいですよね。 Deno公式がセットアップするためのアクションを公開しているので、これを利用します。 denoland/setup-deno あとは deno test コマンドを実行するだけです。 on: push: pull_request: jobs: deno: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v1 with: deno-version: v1.

Jun 22, 2024 - 2 minute read - typescript

TypeScriptで同時実行数を制限しながら並行実行する

タスクがたくさんあって並行実行することを考えます。 何も考えずにすべてのタスクを並行実行すると負荷が高すぎるので、 同時実行数を制限したいことがありました。 ググってみるといくつか実装例が見つかりますが、その多くは配列を受け入れるものです。 AsyncIterator を受け入れるバージョンが欲しいなと思い、 他の人の記事を参考に実装してみました。 IteratorまたはIterableを受け入れる版 いきなりAsyncIterator版を実装するのは大変なので、Iterator版で練習してみました。 以下の関数 limitConcurrency は、タスクのIteratorまたはIterableを受け取って、並行実行します。 async function limitConcurrency<T>( iter: Iterator<() => Promise<T>> | Iterable<() => Promise<T>>, limit: number ) { const iterator = Symbol.iterator in iter ? iter[Symbol.iterator]() : iter; async function runNext(): Promise<void> { for (;;) { const { value: task, done } = iterator.next(); if (done) { return; } await task(); } } try { const initialTasks: Promise<void>[] = []; for (let i = 0; i < limit; i++) { initialTasks.

Mar 27, 2024 - 2 minute read -

鍵生成には暗号論的に安全な乱数を使おう

SSHの鍵生成には暗号論的に安全な疑似乱数を使おうという話。 暗号論的に安全ではない疑似乱数がどれだけ危険かというのを、簡単なCTFを解くことで検証してみました。 背景 SSH公開鍵に自分の好きな文字列を入れる、という記事を読みました。 かっこいいSSH鍵が欲しい 例えばこのSSH公開鍵、末尾に私の名前(akiym)が入っています。 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFC90x6FIu8iKzJzvGOYOn2WIrCPTbUYOE+eGi/akiym そんなかっこいいssh鍵が欲しいと思いませんか? かっこいい!真似してみたい! そこまではいいんですが、問題は実装です。 秘密鍵を生成する際の乱数生成には高速化のために Goのmath/randを使っていますが、乱数が用いられるのは公開しない秘密鍵自体であり、このアルゴリズム自体はLagged Fibonacci generatorのようなので変に乱数に偏りがない限りは大丈夫だろうと思います(追記: これは乱数予測を主とした話)。 (強調 は筆者によるもの) おっとそれはまずい。 Goの math/rand パッケージは暗号論的に安全な疑似乱数 ではありません 。 SSH秘密鍵のようなセキュリティーが重要な場面では使用してはいけません。 Capture the Flag Challenge ブログ記事の筆者も math/rand を使ったらまずいことは認識したようで、 かっこいいSSH鍵生成プログラムのREADMEに以下のような追記がありました。 A Small CTF Challenge This is a joke software, but there was a serious bug in commit 7db96da05684a86bdbea18319ecc39097d0320d4. Can you recover the private key generated by the following command? Of course, it can be recovered in about an hour.

Mar 20, 2024 - 2 minute read - mysql go golang

GoでMySQLを使ったテストを書くときにつかうユーティリティーライブラリを作った

GoでMySQLを使ったテストを書く場合、MySQLのデータベースを初期化する処理や、使い終わったデータベースを削除する処理が必要になります。 毎回似たような処理を書いているので、そろそろライブラリとして切り出せそうだなと思って書いてみました。 shogo82148/go-mysql-pool 背景 弊社ではデータベースに関連したテストを書く場合、ローカルでMySQLを起動し、実際にMySQLへ接続する手法を取っています。 SQLの文法エラーを検知するには、実際にMySQLで処理するのが手っ取り早いからです。 この方法を採用する場合、次の問題は「いつMySQLのデータベースを初期化するか」です。 Goでは TestMain 関数を用意することで、テストの開始前の処理、テストの終了後の処理を書けます。 初期化しているコードは CREATE DATABASE するだけの単純なものです。 そんな分量もないので、プロジェクト毎にコピー&ペーストして使っていました。 // こんな感じのイメージ。 // 実際に使っているコードとは異なります。 package example_test import ( "database/sql" "testing" ) // 各ユニットテストで使い回す var db *sql.DB func TestMain(m *testing.M) { var cleanup func() db, cleanup = setup() defer cleanup() m.Run() } func setup() (*sql.DB, func()) { // MySQLに接続して新しいデータベースを作る db0, err := sql.Open("mysql", "user:password@/") if err != nil { panic(err) } _, err = db0.Exec("CREATE DATABASE dbname") if err !

Mar 12, 2024 - 1 minute read - mysql go golang

GoのMySQLドライバーを使うときはmysql.NewConnectorとsql.OpenDBを使おう

これから新規に書くコードでは mysql.NewConnector と sql.OpenDBを使ったほうが良さそう、という話。 昔からある書き方 go-sql-driver/mysql のDSNを文字列結合で実現するのは意外と大変なので、 某所ではDSN(Data Source Name)の生成を mysql.Config を使って行っています。 // DSNの生成 cfg := mysql.NewConfig() cfg.User = "user" cfg.Passwd = "password" cfg.DBName = "dbname" dsn := cfg.FormatDSN() // 接続 db, err := sql.Open("mysql", dsn) if err != nil { panic(err) } 新しい書き方 実はDSNを生成しなくとも、 mysql.Configから *sql.DB を取得できます。 cfg := mysql.NewConfig() cfg.User = "user" cfg.Passwd = "password" cfg.DBName = "dbname" // *mysql.Config を直接渡す conn, err := mysql.NewConnector(cfg) if err != nil { panic(err) } db := sql.

Mar 11, 2024 - 2 minute read - aws mysql go golang

GoのMySQLドライバーにBeforeConnectが追加されました

先日 go-sql-driver/mysql v1.8.0 がリリースされ、 いくつかのオプションが追加されました。 その中からひとつ BeforeConnect を紹介したいと思います。 Add BeforeConnect callback to configuration object #1469 何が嬉しいの? パスワード以外の方法で MySQL にログインするのが簡単になります。 BeforeConnect を使わない従来の方法 たとえば、AWS では IAM 認証を使ってログインする方法を提供しています。 IAM の情報を使って短期間だけ有効なトークンを発行し、そのトークンを使ってログインします。 MariaDB、MySQL、および PostgreSQL の IAM データベース認証 トークンの有効期限は短いので、接続を開始する直前にトークンを発行し接続設定を書き換えなければいけません。 しかし、Go はコネクションプールを採用しているため、実際に接続を開始するタイミングを知るのは意外と難しいです。 頑張ってそれを実現するためにわざわざドライバーを書いたこともありました。 IAM 認証で AWS RDS へ接続する MySQL ドライバを作った shogo82148/rdsmysql BeforeConnect を使った方法 BeforeConnect は、接続を開始する直前に接続設定を書き換える機能です。 shogo82148/rdsmysql を使用せずとも、簡単に IAM 認証を実現できます。 package main import ( "context" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/feature/rds/auth" "github.com/go-sql-driver/mysql" ) func main() { mycnf := mysql.NewConfig() mycnf.TLSConfig = "true" mycnf.

Feb 6, 2024 - 2 minute read - aws aws-lambda go golang

AWS Lambda Function URLsのイベントをnet/httpで扱えるridgenativeとlambtripを書いた

AWS Lambda関数をGoの標準ライブラリのインターフェイスに変換するライブラリを作りました。 http.Handler 実装: shogo82148/ridgenative http.RoundTripper 実装: shogo82148/lambtrip ridgenative Lambda Function URLsではリクエストとレスポンスがJSON形式になって渡ってきます。 これらをGo標準ライブラリの net/http.Request と net/http.ResponseWriter で扱えるように書いたアダプターが shogo82148/ridgenativeです。 同じコードをHTTPサーバーやAWS Lambda上で動かす これのうれしいポイントは net/http.Handler の実装をひとつ用意すれば、 普通のHTTPサーバーとしても、AWS Lambda上でも動かすことができるという点です。 たとえば以下のコードは go run main.go のように実行すると、 8080ポート上で動く普通のHTTPサーバーになります。 package main import ( "fmt" "net/http" "github.com/shogo82148/ridgenative" ) func main() { http.HandleFunc("/hello", handleRoot) // httpの代わりにridgenativeを呼び出す ridgenative.ListenAndServe(":8080", nil) } func handleRoot(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") fmt.Fprintln(w, "Hello World") } bootstrap という名前でコンパイルして、zipで固めてアップロードすれば、 Lambda Function URLsやAPI Gatewayで動くHTTP APIの完成です。 GOOS=linux GOARCH=arm64 CGOENABLED=0 go build -o bootstrap main.

Feb 4, 2024 - 1 minute read - github github-actions mysql redis

actions-setup-mysqlとactions-setup-redisがApple M1上で動くようになりました

GitHub Actions に Apple Silicon がやってきました! GitHub Actions: Introducing the new M1 macOS runner available to open source! GitHub Actions: macOS 14 (Sonoma) is now available 先日Perlをビルドして遊んでみました。 actions-setup-perlがApple M1上で動くようになりました 今回はMySQLとRedisをビルドしてみたお話です。 shogo82148/actions-setup-mysql shogo82148/actions-setup-redis actions-setup-mysql v1.31.0, actions-setup-redis リリースのお知らせ actions-setup-mysql v1.31.0, actions-setup-redis v1.33.0 から M1 macOS に対応しています。 runs-on: キーに macos-14 を指定すると M1 を利用できます。 jobs: build: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Set up MySQL uses: shogo82148/actions-setup-mysql@v1.31.0 M1による高速化 今日現在(2024-02-04)のMySQL最新安定版リリースは 8.0.36 です。 MySQL 8.

Feb 1, 2024 - 1 minute read - github github-actions perl

actions-setup-perlがApple M1上で動くようになりました

GitHub Actions に Apple Silicon がやってきました! GitHub Actions: Introducing the new M1 macOS runner available to open source! GitHub Actions: macOS 14 (Sonoma) is now available 新しいコンピューティング環境がでてやることといえばアレですよね。 Perlのビルド。 というわけでやっていきましょう。 actions-setup-perl v1.28.0 リリースのお知らせ actions-setup-perl v1.28.0 から M1 macOS に対応しています。 runs-on: キーに macos-14 を指定すると M1 を利用できます。 jobs: build: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Set up perl uses: shogo82148/actions-setup-perl@v1.28.0 M1による高速化 今回のリリースにあたり、バージョン違いコンパイルオプション違いの 全148種類 のPerlバイナリを再ビルドしました! Perl 5.38.2 のビルド時間で比較すると、x64では 11m 27s かかっていたビルドが、M1では 4m 6s へと大きく改善しました。 64.

Jan 11, 2024 - 1 minute read - go golang

GoでForwarded Headerのパーサーを作った

ちょっとした用事で Forwarded ヘッダーの解析をしたくなったので、解析ライブラリを書いてみました。 shogo82148/forwarded-header 背景 Amazon API GatewayのHTTP Proxy Integrationを利用しているプロジェクトで、 クライアントのIPアドレスを知りたい用件がありました。 リバースプロキシの運用に慣れている人なら、「クライアントのIPアドレスを取得したい」と聞いて真っ先に思いつくのは X-Forwarded-For ヘッダーでしょう。 しかし、HTTP Proxy Integrationはこのヘッダーを付与しません。 なんとかIPアドレスを取得できないかと調べてみると、HTTP Proxy IntegrationはForwardedヘッダーを付与することがわかりました。 Amazon API Gateway: Explaining HTTP Proxy in HTTP API Forwardedヘッダーは RFC 7239 で標準化されているヘッダーです。 リバースプロキシによって失われてしまう情報を補完するために利用します。 RFC 7239: Forwarded HTTP Extension RFC 7239: Forwarded HTTP拡張 Forwarded - MDN Forwardedヘッダーを見ればクライアントのIPアドレスもわかります。 たとえば以下の例では 192.0.2.60 がクライアントのIPアドレスです。 Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43 X-Forwarded-Forヘッダーは単純なカンマ区切りのテキストだったので、Split関数で十分でした。 一方Forwardedヘッダーは構造化されているため、パーサーが必要です。 そこまで複雑ではないので、実装してみました。 使い方 解析は Parse関数を呼び出すだけです。 package main import ( "fmt" "net/http" "os" forwardedheader "github.com/shogo82148/forwarded-header" ) func main() { header := make(http.