Go の http.Client が送り直す要求と返すエラー

結論 http.Client は 301 / 302 / 303 の転送に従うと、DELETE・PUT・POST を本文なしの GET にして転送先へ送る。DELETE への 302 が一覧へ転送されると、一覧の GET が 200 を返し、何も消さずに成功として返る。307 / 308 はメソッドと本文を保つ 再利用した接続が応答の前に切られると、HTTP/1.1 の Transport は GET などと Idempotency-Key 付きの要求だけを、新しい接続で送り直す。HTTP/2 では GET でも送り直さず、connection reset by peer を返す 本文を送り直せるのは GetBody があるときだけで、http.NewRequest が GetBody を付けるのは *bytes.Buffer / *bytes.Reader / *strings.Reader だけ。それ以外の io.Reader では、307 は転送されずにそのまま返り、読み切った要求をもう一度 Do すると空の本文がエラーなしに届く HTTP/2 の接続は MaxIdleConnsPerHost の数を超えても閉じられない。取り置き 2 本の設定で、張った 8 本を残して使い回した。MaxConnsPerHost は同時に張りに行く接続を絞るが、HTTP/2 の接続の数の上限にはならない (サーバの同時ストリームが 2 のとき、1 にしても 4-6 本張った) 失敗は *url.Error で返り、Error() にクエリ込みの URL が入る。%w で包んでも、文字列にすれば URL が出る。接続リセットも接続拒否も net.Error を満たすので、タイムアウトかどうかは Timeout() で分ける 以下は go1.26.4 / linux/amd64 で実測した。サーバは net/http/httptest で手元に立て、HTTP/2 は EnableHTTP2 と StartTLS で話させた。各節のコードは main.go 1 つで、go run で動く。 ...

2026-10-26 ·  2026-10-26 · 10 分 · 1942 文字

golangci-lint が 0 issues で通す違反

結論 0 issues. / exit 0 は「違反が無い」ではなく「報告まで残った指摘が無い」。golangci-lint は ファイルの読み込み => linter の検査 => 検査後の後処理 の 3 段階で動き、指摘はどの段階でも消える 後処理: issues.new: true は差分で追加された行の指摘だけを残す。クリーンな checkout では HEAD~ との差分しか見ない。issues.fix: true は直せる指摘をファイルへ書き込み、報告から外す 抑止コメント: gosec の指摘を抑止するコメントは 2 種類ある。#nosec は gosec が読むので、gosec 単体でも golangci-lint 経由でも効く。//nolint:gosec は golangci-lint が読むので、golangci-lint 経由でだけ効く。# を抜いた //nosec は gosec も golangci-lint も読まず、どの実行でも指摘が出る。抑止されるのは名指しした ID だけ 読み込み: build tag 付きのファイルは、run.build-tags に入れないと型検査にも載らない 検査: staticcheck は ST の一部を既定で外す。forbidigo は既定で呼び出しの字面だけを照合し、exhaustive は名前付きの型の列挙だけを見る。gopls の analyzer の一部は golangci-lint に入っていない CI では --new=false --fix=false を明示する。設定を書いたら、違反を 1 つ置いて検出されることを確かめる 以下は golangci-lint 2.14.0 (go1.26.4, linux/amd64) と gopls v0.23.0 で実測した。設定は v2 の形式 (version: "2") で書く。 ...

2026-10-10 ·  2026-10-10 · 10 分 · 1958 文字

Firestore のトランザクションが守るのは読んだ文書だけ

結論 Firestore のトランザクションが競合として扱うのは、その中で tx.Get した文書だけ。 トランザクションの外で読んだ値、読まずに書いた文書、クエリで「無かった」ことは守らない 悲観 (Standard の既定) と楽観 (Enterprise の既定) で違うのは決着の時点だけで、守る範囲は同じ。 判定に使う値をコールバックの中で読み直せば、同じコードがどちらでも通る 「1 件だけ」を守りたい値はドキュメント ID にして名指しで読む。件数を数える判定は、エミュレータでも条件次第で 2 件できた SDK はコールバックの中での生のクライアントの書き込みも、外側の ctx で張った RunTransaction も止めない。どちらもトランザクションの外で確定する 複数の文書を「全部か、無しか」で書くなら RunTransaction。BulkWriter はアトミックでない 以下は cloud.google.com/go/firestore v1.26.0 / Firestore エミュレータ v1.22.0 / Go 1.26.4 で実測した。 エミュレータは --database-edition=standard と --database-edition=enterprise の 2 つを立てた。 本番の Firestore では試していない。 前提: 競合として扱うのは、トランザクション内で読んだ文書だけ 公式ドキュメントは、並行制御の 2 つの方式をどちらも「読んだ文書」で説明している ( Data contention in transactions )。 悲観: “transactions place locks on the documents they read” 楽観: 読んだ文書を追跡し、“only if none of those documents changed” のときだけ書き込みを確定する 読んでいない文書はロックも照合もされない。 書くだけの文書、トランザクションの外で読んだ値、クエリの結果に入らなかった文書は、守る対象に入らない。 以下の節は、どれもこの 1 点から出てくる。 ...

2026-10-08 ·  2026-10-08 · 7 分 · 1290 文字

Go のキャッシュが追わない入力と巻き込む範囲

結論 go test の結果キャッシュは、テストバイナリ・cacheable なフラグ・テストが読んだ環境変数・モジュールの中で開いたファイルで照合する。モジュールの外のファイルは、書き換えても (cached) のまま緑になる 境界はパッケージのディレクトリではなくモジュールルート。外のフィクスチャは、モジュール内に張った symlink 越しに読めば追跡される -coverprofile / -coverpkg 付きの実行もキャッシュされ、カバレッジ率も前回の値が出る GOFLAGS=-count=1 は、そのフラグを持たない go build / vet / list / fmt / run では無視され、エラーにならない。Makefile で export すれば、全ターゲットの go test をキャッシュから外せる CGO_ENABLED を変えると、cgo のファイルを持つ net と、それを import するパッケージがすべて別のキーで作り直される ビルドキャッシュはユーザ単位で、全リポジトリが共有する。1 つのリポジトリの go clean -cache / -testcache が、同じマシンの全プロジェクトに効く 以下は Go 1.26.4 (linux/amd64) で実測した。実験では GOCACHE を一時ディレクトリに向け、手元のキャッシュには触れていない。 前提: テスト結果のキャッシュは何で照合するか go help test は次のとおり書いている。 The rule for a match in the cache is that the run involves the same test binary and the flags on the command line come entirely from a restricted set of ‘cacheable’ test flags, defined as -benchtime, -coverprofile, -cpu, -failfast, -fullpath, -list, -outputdir, -parallel, -run, -short, -skip, -timeout and -v. If a run of go test has any test or non-test flags outside this set, the result is not cached. (中略) The idiomatic way to disable test caching explicitly is to use -count=1. Tests that open files within the package’s module or that consult environment variables only match future runs in which the files and environment variables are unchanged. ...

2026-10-07 ·  2026-10-07 · 7 分 · 1278 文字

gin の binding タグをすり抜ける値と経路

結論 gin が実行時に読む検証タグは binding だけ。validate タグはコンパイルも lint も通り、検証は一度も走らない 検証は ShouldBind* の中でだけ、デコード直後の値に掛かる。c.JSON はレスポンスを検証せず、ハンドラで trim した後の値も検証しない required は「ゼロ値でない」の検査で、型によって意味が変わる。値型は "" / 0 / false を弾き、ポインタとスライスは nil だけを弾く (&"" と [] は通る) タグは左から評価され、最初に落ちたタグ 1 つだけが返る。required,min=1 の int に 0 を送ると、返るのは min でなく required required は値の形式を見ない。URL なら net/url で分解して、scheme と host を許可リストと完全一致で比べる 以下は Go 1.26.4 / gin v1.12.0 / go-playground/validator v10.30.1 で実測した。 前提: gin は go-playground/validator をタグ名 binding で呼ぶ gin の ShouldBindJSON / ShouldBindQuery は、デコードした struct を go-playground/validator に渡す。 validator 本体の既定タグ名は validate だが、gin は初期化時にタグ名を binding へ差し替えている。 ...

2026-10-05 ·  2026-10-05 · 5 分 · 972 文字