はじめに
TIG DXユニット真野です。Go 1.18連載の2本目です。
Go Release Notes に記載があったMinor changes to the libraryにあったnet/httpの3点の更新について触れていきます。
- WebAssemblyの改善について
- Cookie.Valid()の追加
- MaxBytesHandlerの追加
なお、2022年2月6日にGo 1.18 beta2で調べていますのでご注意ください。また、登場するコードはここにコミットしています。
(1)WebAssemblyの改善について
Go 1.18からWebAssemblyでDialContext、DialTLS、DialTLSContext が正しく動くようになりました。
リリースノートから引用します。
On WebAssembly targets, the Dial, DialContext, DialTLS and DialTLSContext method fields in Transport will now be correctly used, if specified, for making HTTP requests.
https://tip.golang.org/doc/go1.18#minor_library_changes
net/httpはサーバー・クライアントの両方を含んでいますがWebAssemblyというだけあってクライアントの話です。トランスポートのDial、DialContext、DialTLS、およびDialTLSContextメソッドフィールドが指定されている場合、HTTPリクエストが正しく使用されるようになりました。..ということです。そのままですが詳しく説明していきます。
どういうことか
net/http: Expose the normal transport RoundTripper for WASM/js #27495のIssueで触れられています。
http.Clientを拡張する手段としてトランスポートがあります。例えば必ずエラーになるような拡張を行うと、HTTP GETが必ず失敗するはずです。
func main() { |
実際動かすと、Get "https://api.ipify.org/": あえてエラーにします
が出力されるでしょう。
このコードをGo 1.17で wasm 版で動かすと動かないよーということでした。
実際に動かしてみますが、Goのwasm対応は少しお作法が多く、先程のコードもお作法にそって修正する必要があります。
GoのWASMがライブラリではなくアプリケーションであること の記事を参考にしました。
package main |
コード中にコメントにも書いていますが、wasmでHTTPリクエストを送る場合にブロッキングさせると wasm: fatal error: all goroutines are asleep - deadlock! #34478 にあるようにdeadlock! と表示されます。回避するためには別goroutineを利用する必要があるので、ひと手間ラップしています。appendHTMLBody
は見たままですが、bodyに <p>
タグを追加してIP情報(HTTPレスポンス)かエラーメッセージを表示します。
これをビルドします。
$ go version |
続いて以下のようなHTMLを作成し、さきほどのw main.1.17.wasmと、main.1.18beta2.wasm と同じ階層に配備し何かしらのWebサーバでホストさせます。wasm_exec.jsはGoインストールしたフォルダに準備されているのでコピーして持ってきます。
<html> |
実行結果を比較すると、当然ながらGo 1.18ではトランスポートが正しく機能している(あえて発生させたエラーが表示される)ことが分かります。
1.17実行結果
カスタムラウンドトリッパーが無視され、普通に通信が行われます。
1.18実行結果
カスタムラウンドトリッパーが有効に動き、想定通りエラーメッセージが表示されます。
標準パッケージのどのような修正だったかというと概ね以下の修正方針だったそうで、goosがjsだったときはデフォルトのラウンドトリッパーを使わず、jsRoundTripperという構造体を新たに使うようになったようです。
https://github.com/golang/go/commit/e8050da2dd93f4ff00a590c14f94c31da3c3159b
(2)Cookie.Valid()の追加
HTTPリクエストヘッダーからCookieを読み取るロジックでは、Cookieのキーや値のパースに失敗した場合は切り捨てる(標準エラーに出力する)設計だったそうです。切り捨てられたかどうか判定するためにValid()の関数を追加しようよという提案でした。
https://github.com/golang/go/blob/master/src/net/http/cookie.go#L288-L303
もとのIssueはnet/http: add Cookie.Valid method #46370 で、かなり議論が長いです。そもそもValidかどうか知ってもハンドリングできないだろうとか、無効な値を送信することがそもそも~とか、Serialize()関数を追加すべきとか、RFC 6265準拠について誤解が無いようにしようなど、いろいろな意見があり興味深いです。
(3)MaxBytesHandlerの追加
net/http: add MaxBytesHandler(h Handler, n int64) Handler #39567 で提案されています。
みなさん、HTTPリクエストボディをjson.Unmarshal() なんかで読み取るコードは覚えきれないほど書いて来たかと思いますが、何かしら悪意のあるクライアントが大容量のペイロードを送信してきたときはメモリ溢れ(オーバーフローなど)が起こる懸念があります。DDOS攻撃の一種かと思うので、セキュリティ的な改善につながるかと思います。
すでにいくつかのブログで紹介されていました。詳しい..。
こういったストリームの読み取り時は、io.CopyN() を使ってまるごと読み取らないようにしようよといったお決まりがありましたが、それがさらに標準化されたのは良い流れかなと思います。ぜひ使っていきましょう。
さいごに
Go1.16のときはgo installについてまとめ、Go 1.17連載のときはencoding/csv について調べました。マイナーチェンジ系もIssueなどの議論を追っていくと自分にとってちょうど良いサイズで、学びがありオススメです。
この記事では、wasmのお作法をあまり理解せずかなりハマりましたが、こういう機会でないと使わないので楽しかったです。
最後まで読んでいただき、ありがとうございます!