- 投稿日:2020-02-08T23:55:17+09:00
Go の x/text/transform で独自構造パケットを華麗に処理する
ネットワーク上に流すデータ(パケット)はよく次のような構造になります。
可変長のデータを格納できるように、先頭に長さを入れておくという方式ですね。
Goでこのパケットの読み書きを行う
このようなパケットの読み書き処理をGoで素直に書くと次のようになります。
// 1パケット分読み出す func readPacket(r io.Reader) ([]byte, error) { var size uint16 if err := binary.Read(r, binary.LittleEndian, &size); err != nil { return nil, err } p := make([]byte, size) if _, err := io.ReadFull(r, p); err != nil { return nil, err } return p, nil } // 1パケット分書き出す func writePacket(w io.Writer, p []byte) error { if err := binary.Write(w, binary.LittleEndian, uint16(len(p))); err != nil { return err } if _, err := w.Write(p); err != nil { return err } return nil }これを使って、例えば
"hello, world"という文字列をパケットにすると次のようになります。func main() { var b bytes.Buffer payload := []byte("hello, world") writePacket(&b, payload) print(hex.Dump(b.Bytes())) }先頭に
0c 00(payloadの長さ)が付加されていますね!00000000 0c 00 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 |..hello, world|パケット構造の仕様が増える場合
ここで、「Payloadを暗号化してほしい!」という追加の仕様がきたとします。
そうなると暗号化の処理が追加で必要となります。暗号化の関数を
encrypt()とすると、パケットを書き出す処理は次のようになってきます。func writePacket(w io.Writer, p []byte) error { p = encrypt(p) if err := binary.Write(w, binary.LittleEndian, uint16(len(p))); err != nil { return err } if _, err := w.Write(p); err != nil { return err } return nil }さらに、「暗号化するかどうかはON/OFFできるようにしてほしい!」という追加の仕様がきたら、暗号化をするかどうかのフラグも追加が必要となり、だんだん
writePacket関数が複雑化していきます・・・。func writePacket(w io.Writer, p []byte, useEncryption bool) error { if (useEncryption) { p := encrypt(p) } if err := binary.Write(w, binary.LittleEndian, uint16(len(p))); err != nil { return err } if _, err := w.Write(p); err != nil { return err } return nil }x/text/transformer でスマートな設計に
パケットを読み書きする関数が複雑化していくのはできれば避けたいところです。
もっとスマートな設計にできないでしょうか?Goにはx/text/transformというパッケージが用意されています。
その中にあるTransformerインターフェースが今回使う重要なものとなります。Transformerは簡潔に言うと、入力した[]byteに何らかの変換処理をかけるものとなります。
さらに、複数のTransformerをつないで1つのTransformerにできます。このインターフェースを利用して次の2つのTransformerを作ると要件が実現できそうです。
- 先頭に長さをつけるTransformer (PacketWriteTransformer)
- 暗号化するTransformer (EncryptTransformer)
type PacketWriteTransformer struct{ transform.NopResetter } func (p *PacketWriteTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { nSrc = len(src) binary.LittleEndian.PutUint16(dst, uint16(nSrc)) nDst = copy(dst[2:], src) if nDst < nSrc { err = transform.ErrShortDst } nDst += 2 return } type EncryptTransformer struct { transform.NopResetter } func (p *EncryptTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { encrypt(dst, src) nSrc = len(src) nDst = len(dst) return }では、この2つのTransformerを使ってPayloadが暗号化されたパケットを書き出してみます!
func main() { var b bytes.Buffer payload := []byte("hello, world") w := transform.NewWriter(&b, transform.Chain(&EncryptTransformer{}, &PacketWriteTransformer{}) w.Write(&b, payload) print(hex.Dump(b.Bytes())) // 暗号化されたパケットになる }transform.Chainで2つのTransformerをつないでいるところがポイントです。
もし暗号化をOFFにしたくなったら、Chainの中からEncryptTransformerを外すだけでOKです。スマートですね!読み出しのTransformerも作る
ここまでで、パケットの書き出し(Payload単体からパケットを作る)処理をTransformerで実現できました。
あとは、逆の処理をするTransformerを作れば、パケットの読み出しも実現できます。
- 先頭に長さがついたパケットからPayloadを取り出すTransformer (PacketReadTransformer)
- 復号化するTransformer (DecryptTransformer)
実際に書いてみると次のようになりました。
type PacketReadTransformer struct { rest []byte } func (p *PacketReadTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { npRest := len(p.rest) if npRest > 0 { src = append(p.rest, src...) p.rest = nil } if atEOF && len(src) == 0 { return } if len(src) < 2 { err = transform.ErrShortSrc return } size := int(binary.LittleEndian.Uint16(src[:2])) if len(src[2:]) < size { err = transform.ErrShortSrc return } nDst = copy(dst, src[2:2+size]) nSrc = 2 + nDst - npRest if nDst < size { err = transform.ErrShortDst return } nRest := len(src[2+size:]) if nRest > 0 { p.rest = make([]byte, nRest) nSrc += copy(p.rest, src[2+size:]) } return } func (p *PacketReadTransformer) Reset() { p.rest = nil } type DecryptTransformer struct { transform.NopResetter } func (p *DecryptTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { decrypt(dst, src) nSrc = len(src) nDst = len(dst) return }PacketReadTransformerは、書き出し側と比べるとちょっと複雑です。
なぜなら、TCPなどのストリーム指向でデータを読み出している場合、複数のパケットが連結された状態で受け取る可能性もあるからです。後続のパケットがくっついている場合は、後続分は
p.restという場所に保持しておいて、次のTransformのときに先頭にくっつけることで対応しています。これで、パケット読み出しのTransformerもできました!
受け取ったパケットの中身を取り出す処理は次のようなコードになります。func main() { var b bytes.Buffer packet := []byte{ ... } // 受け取ったパケット r := transform.NewReader(&b, transform.Chain(&PacketReadTransformer{}, &DecryptTransformer{}) payload := make([]byte, 1024) n, _ := r.Read(&b, payload) print(hex.Dump(payload[:n]) // 受け取ったパケットの中身がみえる }io.Reader/io.Writerとの連携
以上で、パケットの読み書きがすべてTransformerで実現できました。
図で表すと次のようなデータの流れになります。transform.Transformerを使う最大の利点は、transformer.Reader/transformer.Writerがio.Reader/io.Writerを実装していることです。
io.Readerはシンプルながらかなりの応用が効くGoらしいインターフェースとなっています。詳しい利点や応用例については次の記事などを読むとよいでしょう。
とにかく、Transformerを組み合わせることで、間にどれだけ処理を挟んでも、Go標準のio.Reader/io.Writerで読み書きできるため、TCPソケットやUDPソケット(net.Conn)にそのまま接続することも可能です。
すばらしいですね!
- 投稿日:2020-02-08T21:30:17+09:00
Go 1.14 リリースノート
この記事は https://tip.golang.org/doc/go1.14 を日本語訳したものです. 前のバージョンはこちら: Go 1.13 リリースノート
ドラフトリリースノート - Go 1.14 の紹介
Go 1.14 はまだリリースされていません. これらは書いている途中のリリースノートになります. Go 1.14 は2020年2月にリリースされる予定です.
go コマンドのモジュールサポートを本番で使用する準備ができましたので、依存性管理のためにすべてのユーザが Go モジュールに移行することをおすすめします.
もし Go のツールチェーンの問題のせいで移行できない場合には、その問題が 未解決の問題 として起票されていることを確認してください. (もし問題が
Go1.15マイルストーンにない場合には、適切に優先順位付けできるよう、移行を妨げている理由をお知らせください.)言語への変更
オーバーラップするインターフェイスの提案 により、Go 1.14 ではオーバーラップするメソッドセットを持つインターフェイスの埋め込みが可能になりました. 埋め込みインターフェイスのメソッドは、(埋め込み)インターフェイスにすでに存在するメソッドと同じ名前と同一のシグネチャを持つことができます. これにより、ダイアモンド形状の埋め込みグラフで典型的に発生する(しかしそれだけに限らない)問題が解決されます. インタフェースで明示的に宣言されたメソッドは、今までと同様に一意でなければなりません.
ポート
Darwin
Go 1.14 は macOS 10.11 El Capitan 上で走る最後のリリースです. Go 1.15 は macOS 10.12 Sierra 以降を必要とする予定です.
Go 1.14 は maxOS (
darwin/386ポート) 上で32ビットバイナリをサポートする最後のリリースです. 32ビットバイナリは macOS 10.15 (Catalina) 以降、macOS ではサポートされなくなりました. Go は64ビットのdarwin/amd64ポートのサポートを継続します.Go 1.14 はおそらく iOS, iPadOS, watchOS, tvOS (
darwin/armポート)上で32ビットバイナリをサポートする最後のGoリリースになります. Go は64ビットのdarwin/arm64ポートのサポートを継続します.Windows
Windows 上の Go バイナリは DEP (Data Execution Prevention) が有効になりました.
WebAssembly
js.Valueオブジェクト経由で Go から参照した JavaScript の値がガベージコレクトされるようになりました.
js.Valueの値は==演算子で比較することができなくなり、代わりにEqualメソッドを使って比較されなくてはなりません.
js.ValueはIsUndefined、IsNull、IsNaNメソッドを持つようになりました.RISC-V
Go 1.14 は Linux (
GOOS=linux,GOARCH=riscv64) 上の 64ビット RISC-V の実験的サポートを含んでいる. パフォーマンス、アセンブリ構文の安定性、場合によっては正確性でさえ、作業中であることに注意してください.FreeBSD
Go は FreeBDS で64ビット ARM アーキテクチャ(
freebsd/arm64ポート)をサポートしました.Native Client (NaCl)
Go 1.13 のリリースノートでアナウンス した通り、Go 1.14 は Native Client プラットフォーム (
GOOS=nacl) のサポートを破棄します.Illumos
ランタイムは、
runtime.NumCPUとGOMAXPROCSのデフォルト値のためのゾーン CPU の上限(zone.cpu-capリソースコントロール)を考慮するようになりました.ツール
Go コマンド
ベンダリング
メインモジュールにトップレベルの
vendorディレクトリが含まれており、そのgo.modファイルでgo1.14以上が指定されている場合、goコマンドは、フラグを受け入れる操作に対してデフォルトで-mod=vendorが設定されるようになりました. このフラグの新しい値-mod=modを指定すると、goコマンドは(vendorディレクトリが存在しない時のように)代わりにモジュールキャッシュからモジュールをロードします.(明示的であれデフォルトであれ)
-mod=vendorが設定されている場合、goコマンドはメインモジュールのvendor/modules.txtファイルとgo.modファイルの整合性を確認するようになりました.
golist-mは、vendorディレクトリの中のパッケージに提供されない推移的な依存関係を暗黙的に省略しなくなりました.-mod=vendorが設定され、vendor/modules.txtに記述されていないモジュールに対して情報が要求された場合、明示的に失敗するようになります.フラグ
gogetコマンドは-modフラグを受け付けなくなりました. 以前はフラグの設定が無視されたか、ビルドの失敗を引き起こしていました.
go.modファイルが読み取り専用で、トップレベルのベンダーディレクトリが存在しない場合、-mod=readonlyがデフォルトで設定されるようになりました.
-modcacherwは、新しく作成されたディレクトリを読み取り専用にするのではなく、デフォルトのアクセス権のままでモジュールキャッシュに残すようにgoコマンドに指示する新しいフラグです. このフラグを使用すると、テストやその他のツールによって、モジュールの検証済みチェックサムに含まれていないファイルが誤って追加される可能性が高くなります. ただし、(goclean-modcacheの代わりに)rm-rfを使用してモジュールキャッシュを削除できます.
-modfile=fileは新しいフラグで、goコマンドにモジュールのルートディレクトリの中のgo.modファイルの代わりに代替のファイルを読み込む(可能であれば書き込みも)ように指示します. モジュールのルートディレクトリを特定するために、go.modという名前のファイルはまだ存在している必要がありますが、アクセスはされません.-modfileを指定すると、代替のgo.sumファイルも使用されます. そのパスは.mod拡張子を削除して.sumを追加することにより、-modfileフラグから導出されます.環境変数
GOINSECUREは新しい環境変数で、goコマンドが特定のモジュールをオリジンから直接取得するときに、HTTPS 接続を必要としないよう、証明書の検証をスキップするよう指示します. 既存のGOPRIVATEのように、GOINSECUREの値は、glob パターンのカンマ区切りのリストです.モジュール外のコマンド
(
GO111MODULE=onを設定することによって)module-aware モードが明示的に有効な場合、go.modファイルが存在しない場合、ほとんどのモジュールコマンドの機能が制限されます. 例えば、gobuild、gorun、およびその他のビルドコマンドは、標準ライブラリ内のパッケージと、コマンド行で .go ファイルとして指定されたパッケージだけをビルドできます.以前は、
goコマンドはそれぞれのパッケージパスをモジュールの最新バージョンに解決しますが、モジュールのパスやバージョンは記録しませんでした. その結果、遅くて、再現できないビルドとなりました.
gogetは以前と同じように動作しますし、バージョンを明示的したgomoddownloadとgolist-mも同様です.
+incompatibleバージョンモジュールの最新バージョンに
go.modファイルが含まれている場合、gogetは、そのバージョンが明示的に要求されているか、すでに必要とされていない限り、そのモジュールの互換性のないメジャーバージョンにはアップグレードしません.golistもバージョン管理から直接取得する際にそのようなモジュールの互換性のないメジャーバージョンを省略しますが、プロキシから報告された場合にはそれらを含めるかもしれません.
go.modファイルメンテナンス
gomodtidy以外のgoコマンドは、メインモジュールの他の(推移的な)依存関係によってすでに暗示されている間接的な依存関係のバージョンを指定するrequireディレクティブを削除しなくなりました.gomodtidy以外のgoコマンドは、変更が表面的なものだけならばgo.modファイルを編集しなくなります.変更が表面的なものである場合、go mod tidy以外のgoコマンドはgo_modファイルを編集しません.
-mod=readonlyを設定すると、goディレクティブがなかったり、誤った// indirectコメントを原因として失敗することがなくなります.モジュールダウンロード
goコマンドはモジュールモードで Subversion レポジトリをサポートしました.
goコマンドには、モジュールプロキシや他の HTTP サーバからのプレインテキストのエラーメッセージのスニペットが含まれるようになりました. エラーメッセージは、有効な UTF-8 で、グラフィック文字とスペースだけで構成されている場合にのみ表示されます.テスト
go test -vはすべてのテストの終了時ではなく、発生時にt.Log出力をストリームするようになりました.ランタイム
このリリースでは、
deferされた関数を直接呼び出す場合と比較して、大半のdefer使用のパフォーマンスがほぼオーバヘッド無しに改善しました. 結果として、deferはオーバーヘッドを気にすることなく、パフォーマンスがクリティカルなコードで使用できるようになりました.Goroutine が非同期でプリエンプティブ可能になりました. 結果として、関数呼び出しのないループでスケジューラがデッドロックしたり、ガベージコレクションが大幅に遅延する可能性がなくなりました. これは
windows/arm、darwin/arm、js/wasm、plan9/*を除く全てのプラットフォームでサポートされています.プリエンプションの実装の結果として、Linux や macOS システムを含む Unix システムでは、Go 1.14 でビルドされたプログラムは以前のリリースでビルドされたプログラムよりも多くのシグナルを受け取ります. このことは、
syscallやgolang.org/x/sys/unixのようなパッケージを使うプログラムで遅いシステムコールがEINTRで失敗するのをより目撃するようになることを意味します. それらのプログラムは、ループでシステムコールに再びトライする可能性が高いと思いますが、何らかの方法でエラーを処理する必要があります. この件についての情報が知りたければ、Linux システムのためのman 7 signalや他のシステムのための同様のドキュメントを見てください.ページアロケータは
GOMAXPROCSが高い値のときに、より効率的で大幅にロック競合が少なくなりました. 並列かつ高い頻度での大規模な割当では、より低いレイテンシと高いスループットが顕著になります.
time.After、time.Tick、net.Conn.SetDeadline等で使われる内部タイマーは、ロック競合とコンテキストスイッチが少なくなり、より効率的になりました. これはユーザの目に見える変更のないパフォーマンスの改善です.コンパイラ
このリリースは、Go コードが
unsafe.Pointer安全ルールに従っているかを動的にチェックするための測定を追加する、コンパイル時のオプションとして-d=checkptrが追加されました. このオプションは-raceまたは-msanフラグでデフォルトで有効になり(Windows を除く)、-gcflags=all=-d=checkptr=0で無効にすることができます. 特に-d=checkptrは以下をチェックします:
unsafe.Pointerを*Tに変換するときは、結果のポインタがTに適切に整合する必要があります.- ポインタ演算の結果が Go ヒープオブジェクトを指している場合、
unsafe.Pointer型のオペランドの一つが同じオブジェクトを指している必要があります.
-d=checkptrを使うと、標準ライブラリで誤ったアラートが発生するので、現在のところは Windows での使用が推奨されていません.コンパイラは
-jsonフラグによって、インライニング、エスケープ解析、境界チェック削除、nil チェック削除を含む、主要な最適化の機械可読ログを出力できるようになりました.詳細なエスケープ解析診断 (
-m=2) が再び動作するようになりました. これは、以前のリリースの新しいエスケープ解析の実装で破棄されていました.macOS バイナリでの Go のすべてのシンボルは、プラットフォームの規約に従って、アンダースコアで始まるようになりました.
このリリースには、ファジングのための、コンパイラがカバレッジのために挿入する測定の実験的サポートが含まれています. 詳細については 課題を参照してください. この API は将来のリリースで変更される可能性があります.
境界チェックの削除でスライス作成からの情報が使用されるようになり、
intより小さい型のインデックスのチェックを削除できるようになりました.コアライブラリ
標準ライブラリへのすべての変更は小さなものでした.
ライブラリへの小さな変更
いつものように、Go 1の 互換性の約束を念頭に置いて行われた、ライブラリに対するさまざまな小さな変更と更新があります.
hash/maphash
この新しいパッケージはバイトシーケンスのハッシュ関数を提供します. これらのハッシュ関数は、任意の文字列またはバイトシーケンスを整数の均一な分布にマップする必要があるハッシュテーブルまたはその他のデータ構造を実装するために使われることを意図しています.
このハッシュ関数は衝突耐性がありますが、暗号学的に安全ではありません.
crypto/tls
SSL バージョン 3.0 (SSLv3) のサポートは削除されました. SSLv3 は、TLS 以前の危殆化した暗号プロトコルであることに注意されたい.
TLS 1.3 は
GODEBUG環境変数で無効にすることができなくなりました. TLS のバージョンを設定するにはConfig.MaxVersionフィールドを使ってください.
Config.Certificatesフィールドを通して複数の証明書チェーンを提供する場合、ピアと互換性のある最初の証明書チェーンが自動的に選択されるようになりました. これにより、たとえば ECDSA と RSA 証明書を提供し、パッケージが自動的に最適なものを選択できるようになります.Certificate.Leafフィールドが設定されていない場合、この選択のパフォーマンスは低下することにご注意ください.新しい
CipherSuites関数とInsecureCipherSuites関数は、現在実装されている暗号スイートのリストを返します. 新しい [CipherSuiteName] 関数は、暗号スイート ID の名前を返します.新しい
(*ClientHelloInfo).SupportsCertificateと(*CertificateRequestInfo).SupportsCertificateメソッドは、ピアが特定の証明書をサポートしているかどうかを公開します.
tlsパッケージは Next Protocol Negotiation (NPN) 拡張をサポートしなくなり、現在は ALPN のみをサポートしています. 以前のリリースでは両方をサポートしていました. API に変更はなく、アプリケーションは以前と同じように機能するはずです. 他のクライアントとサーバのほとんどは、標準化された ALPN を支持していて、すでに NPN サポートを削除しています.TLS 1.2 ハンドシェイクでサポートされる場合、RSA-PSS 署名が使用されるようになりました. これはほとんどのアプリケーションには影響しませんが、RSA-PSS 署名をサポートしていないカスタム
Certificate.PrivateKey実装は、それらを無効にするために新しいCertificate.SupportedSignatureAlgorithmsフィールドを使用する必要があります.debug/dwarf
debug/dwarfパッケージは、DWARF バージョン 5 の読み込みをサポートしました.新しいメソッド
(*Data).AddSectionは、入力ファイルから DWARFDataへの任意の新しい DWARF セクションの追加をサポートします.新しいメソッド
(*Reader).ByteOrderは、現在のコンパイル単位のバイトオーダーを返します. これは、場所の記述など、ネイティブの順序でエンコードされた属性を解釈するために使用することができます.新しいメソッド
(*LineReader).Filesは、ラインリーダーからファイル名テーブルを返します. これは、AttrDeclFileなどのDWARF 属性の値を解釈するために使用することができます.encoding/asn1
Unmarshalは、新しいTagBMPString定数で表される、ASN.1 文字列型の BMPString をサポートするようになりました.encoding/json
Decoder型は、現在のデコーダ位置の入力ストリームのバイトオフセットを返す新しいメソッドInputOffsetをサポートします.
Compactは、文書化されていない、U+2028とU+2029文字のエスケープをしなくなりました. 正しいエスケープについては、HTMLEscapeを参照してください.go/build
Context型はビルドの作業ディレクトリを設定するために使用できる新しいフィールドDirを持っています. デフォルトは実行中のプロセスのカレントディレクトリです. モジュールモードではメインモジュールの位置を特定するのに使用します.io/ioutil
TempDirは、予測可能なプレフィックスとサフィックスを名前に持つディレクトリを作成できるようになりました. TempFile と同様に、パターンに '*' が含まれている場合は、最後の '*' がランダムな文字列に置き換えられます.log
新しい
Lmsgprefixフラグを使用すると、オプションの出力プレフィックスを行の先頭ではなく、ログメッセージの直前に出力するようにロギング関数に指示することができます.go/doc
新しい関数
NewFromFilesは、*ast.Fileのリストからパッケージドキュメントを計算し、適切なパッケージ要素に例を関連付けます. 新しい情報はPackage、Type、Func型の新しいExamplesフィールドと、Example型の新しいSuffixフィールドにあります.math
新しい
FMA関数は、x*y計算の中間丸めを行わずに、x*y+zを浮動小数点で計算します. いくつかのアーキテクチャでは、専用のハードウェア命令を使用してこの計算を実装し、更なるパフォーマンスを得ています.math/big
GCD関数は入力のaとbに0または負の数を受け付けるようになりました.math/bits
新しい関数
Rem、Rem32、Rem64は商がオーバーフローした場合でも剰余の計算をサポートします.mime
.jsファイルと.mjsファイルのデフォルトタイプはapplication/javascriptではなくtext/javascriptになりました. これは、application/javascriptを廃止されたものとして扱うIETF ドラフトに従っています.mime/multipart
Readerの新しいメソッドNextRawPartはquoted-printableのデータを透過的にデコードすることなしに、次の MIME パートを取得することをサポートしています.net/http
Headerの新しいメソッドValuesは正規化されたキーに関連付けられたすべての値を取得することに使えます.
Transportの新しいフィールドDialTLSContextは、プロキシを使わない HTTPS 要求のための TLS 接続を作成するためのオプションのダイヤル関数を指定することに使えます. この新しいフィールドは、いまや廃止となったDialTLSの代わりに使うことができます.DialTLSは引き続き動作しますが、新しいコードでは不要になったらすぐにトランスポートがダイヤルをキャンセルできるDialTLSContextを使うべきです.Windows 上で
ServeFileは 2GB を超えるファイルを正しく処理するようになりました.net/http/httptest
Serverの新しいフィールドEnableHTTP2はテストサーバで HTTP/2 を有効にすることをサポートします.net/textproto
MIMEHeaderの新しいメソッドValuesは正規化されたキーに関連付けられたすべての値を取得することに使えます.os/signal
Windows では、Control-C と Control-Break が
syscall.SIGINTを生成するように、CTRL_CLOSE_EVENT、CTRL_LOGOFF_EVENT、CTRL_SHUTDOWN_EVENTイベントはsyscall.SIGTERMシグナルを生成するようになりました.plugin
pluginパッケージはfreebsd/amd64をサポートしました.reflect
StructOfは、StructField要素にPkgPathフィールドを設定することによって、エクスポートされていないフィールドを持つ構造体の型の生成をサポートしました.runtime
再帰的な
panic、recoverによってruntime.Goexitが中止することはなくなりました.macOS では、
SIGPIPEは Go ランタイムが初期化される前にインストールされたシグナルハンドラに転送されなくなりました. これが必要なのは、macOS がSIGPIPEを閉じたパイプに書き込むのではなく、メインスレッドに渡すからです.runtime/pprof
生成されたプロファイルには、インラインマークに使用される擬似 PC は含まれなくなりました. インライン関数のシンボル情報は、pprof ツールが想定するフォーマットでエンコードされます. これは、最近のリリースで導入されたリグレッションの修正です.
strconv
NumError型に、変換が失敗した理由を取得するのに使えるUnwrapメソッドが追加されました. これは、根本のエラーがstrconv.ErrRangeなのか、strconv.ErrSyntaxなのかを確認することに、errors.IsでNumErrorの値を使うことをサポートします.sync
非常に競合の激しい
Mutexをロック解除すると、そのMutexを待っている次の goroutine に直接 CPU が渡されるようになりました. これにより、CPU 数の多いマシンで、競合の多い mutex のパフォーマンスが大幅に向上しました.testing
テストまたはベンチマークの終了後に対応する
T.CleanupまたはB.Cleanupを呼ぶことによって、テストパッケージはクリーンアップ機能をサポートしました.text/template
text/template パッケージは、括弧で括られた引数が関数として使用されているときに、エラーを正しく報告するようになりました. これは、
{{if (eq .F "a") or (eq .F "b")}}のような誤ったケースで最もよく見られます. これは{{if or (eq .F "a") (eq .F "b")}}と書かれなくてはなりません. 誤ったケースは期待どおりには動作せず、エラーcan't give argument to non-functionで報告されます.unicode
unicodeパッケージとシステム全体の関連サポートは Unicode 11.0 から、新たに4つのスクリプトを含む554の文字と61の絵文字を追加する Unicode 12.0 にアップグレードされました.
- 投稿日:2020-02-08T21:30:17+09:00
Go 1.14 リリースノート 日本語訳
この記事は https://tip.golang.org/doc/go1.14 を日本語訳したものです. 前のバージョンはこちら: Go 1.13 リリースノート 日本語訳
ドラフトリリースノート - Go 1.14 の紹介
Go 1.14 はまだリリースされていません. これらは書いている途中のリリースノートになります. Go 1.14 は2020年2月にリリースされる予定です.
go コマンドのモジュールサポートを本番で使用する準備ができましたので、依存性管理のためにすべてのユーザが Go モジュールに移行することをおすすめします.
もし Go のツールチェーンの問題のせいで移行できない場合には、その問題が 未解決の問題 として起票されていることを確認してください. (もし問題が
Go1.15マイルストーンにない場合には、適切に優先順位付けできるよう、移行を妨げている理由をお知らせください.)言語への変更
オーバーラップするインターフェイスの提案 により、Go 1.14 ではオーバーラップするメソッドセットを持つインターフェイスの埋め込みが可能になりました. 埋め込みインターフェイスのメソッドは、(埋め込み)インターフェイスにすでに存在するメソッドと同じ名前と同一のシグネチャを持つことができます. これにより、ダイアモンド形状の埋め込みグラフで典型的に発生する(しかしそれだけに限らない)問題が解決されます. インタフェースで明示的に宣言されたメソッドは、今までと同様に一意でなければなりません.
ポート
Darwin
Go 1.14 は macOS 10.11 El Capitan 上で走る最後のリリースです. Go 1.15 は macOS 10.12 Sierra 以降を必要とする予定です.
Go 1.14 は maxOS (
darwin/386ポート) 上で32ビットバイナリをサポートする最後のリリースです. 32ビットバイナリは macOS 10.15 (Catalina) 以降、macOS ではサポートされなくなりました. Go は64ビットのdarwin/amd64ポートのサポートを継続します.Go 1.14 はおそらく iOS, iPadOS, watchOS, tvOS (
darwin/armポート)上で32ビットバイナリをサポートする最後のGoリリースになります. Go は64ビットのdarwin/arm64ポートのサポートを継続します.Windows
Windows 上の Go バイナリは DEP (Data Execution Prevention) が有効になりました.
WebAssembly
js.Valueオブジェクト経由で Go から参照した JavaScript の値がガベージコレクトされるようになりました.
js.Valueの値は==演算子で比較することができなくなり、代わりにEqualメソッドを使って比較されなくてはなりません.
js.ValueはIsUndefined、IsNull、IsNaNメソッドを持つようになりました.RISC-V
Go 1.14 は Linux (
GOOS=linux,GOARCH=riscv64) 上の 64ビット RISC-V の実験的サポートを含んでいる. パフォーマンス、アセンブリ構文の安定性、場合によっては正確性でさえ、作業中であることに注意してください.FreeBSD
Go は FreeBDS で64ビット ARM アーキテクチャ(
freebsd/arm64ポート)をサポートしました.Native Client (NaCl)
Go 1.13 のリリースノートでアナウンス した通り、Go 1.14 は Native Client プラットフォーム (
GOOS=nacl) のサポートを破棄します.Illumos
ランタイムは、
runtime.NumCPUとGOMAXPROCSのデフォルト値のためのゾーン CPU の上限(zone.cpu-capリソースコントロール)を考慮するようになりました.ツール
Go コマンド
ベンダリング
メインモジュールにトップレベルの
vendorディレクトリが含まれており、そのgo.modファイルでgo 1.14以上が指定されている場合、goコマンドは、フラグを受け入れる操作に対してデフォルトで-mod=vendorが設定されるようになりました. このフラグの新しい値-mod=modを指定すると、goコマンドは(vendorディレクトリが存在しない時のように)代わりにモジュールキャッシュからモジュールをロードします.(明示的であれデフォルトであれ)
-mod=vendorが設定されている場合、goコマンドはメインモジュールのvendor/modules.txtファイルとgo.modファイルの整合性を確認するようになりました.
go list -mは、vendorディレクトリの中のパッケージに提供されない推移的な依存関係を暗黙的に省略しなくなりました.-mod=vendorが設定され、vendor/modules.txtに記述されていないモジュールに対して情報が要求された場合、明示的に失敗するようになります.フラグ
go getコマンドは-modフラグを受け付けなくなりました. 以前はフラグの設定が無視されたか、ビルドの失敗を引き起こしていました.
go.modファイルが読み取り専用で、トップレベルのベンダーディレクトリが存在しない場合、-mod=readonlyがデフォルトで設定されるようになりました.
-modcacherwは、新しく作成されたディレクトリを読み取り専用にするのではなく、デフォルトのアクセス権のままでモジュールキャッシュに残すようにgoコマンドに指示する新しいフラグです. このフラグを使用すると、テストやその他のツールによって、モジュールの検証済みチェックサムに含まれていないファイルが誤って追加される可能性が高くなります. ただし、(go clean -modcacheの代わりに)rm -rfを使用してモジュールキャッシュを削除できます.
-modfile=fileは新しいフラグで、goコマンドにモジュールのルートディレクトリの中のgo.modファイルの代わりに代替のファイルを読み込む(可能であれば書き込みも)ように指示します. モジュールのルートディレクトリを特定するために、go.modという名前のファイルはまだ存在している必要がありますが、アクセスはされません.-modfileを指定すると、代替のgo.sumファイルも使用されます. そのパスは.mod拡張子を削除して.sumを追加することにより、-modfileフラグから導出されます.環境変数
GOINSECUREは新しい環境変数で、goコマンドが特定のモジュールをオリジンから直接取得するときに、HTTPS 接続を必要としないよう、証明書の検証をスキップするよう指示します. 既存のGOPRIVATEのように、GOINSECUREの値は、glob パターンのカンマ区切りのリストです.モジュール外のコマンド
(
GO111MODULE=onを設定することによって)module-aware モードが明示的に有効な場合、go.modファイルが存在しない場合、ほとんどのモジュールコマンドの機能が制限されます. 例えば、go build、go run、およびその他のビルドコマンドは、標準ライブラリ内のパッケージと、コマンド行で .go ファイルとして指定されたパッケージだけをビルドできます.以前は、
goコマンドはそれぞれのパッケージパスをモジュールの最新バージョンに解決しますが、モジュールのパスやバージョンは記録しませんでした. その結果、遅くて、再現できないビルドとなりました.
go getは以前と同じように動作しますし、バージョンを明示したgo mod downloadとgo list -mも同様です.
+incompatibleバージョンモジュールの最新バージョンに
go.modファイルが含まれている場合、go getは、そのバージョンが明示的に要求されているか、すでに必要とされていない限り、そのモジュールの互換性のないメジャーバージョンにはアップグレードしません.go listもバージョン管理から直接取得する際にそのようなモジュールの互換性のないメジャーバージョンを省略しますが、プロキシから報告された場合にはそれらを含めるかもしれません.
go.modファイルメンテナンス
go mod tidy以外のgoコマンドは、メインモジュールの他の(推移的な)依存関係によってすでに暗示されている間接的な依存関係のバージョンを指定するrequireディレクティブを削除しなくなりました.
go mod tidy以外のgoコマンドは、変更が表面的なものだけならばgo.modファイルを編集しなくなります.
-mod=readonlyを設定すると、goディレクティブがなかったり、誤った// indirectコメントを原因として失敗することがなくなります.モジュールダウンロード
goコマンドはモジュールモードで Subversion レポジトリをサポートしました.
goコマンドには、モジュールプロキシや他の HTTP サーバからのプレインテキストのエラーメッセージのスニペットが含まれるようになりました. エラーメッセージは、有効な UTF-8 で、グラフィック文字とスペースだけで構成されている場合にのみ表示されます.テスト
go test -vはすべてのテストの終了時ではなく、発生時にt.Log出力をストリームするようになりました.ランタイム
このリリースでは、
deferされた関数を直接呼び出す場合と比較して、大半のdefer使用のパフォーマンスがほぼオーバヘッド無しに改善しました. 結果として、deferはオーバーヘッドを気にすることなく、パフォーマンスがクリティカルなコードで使用できるようになりました.Goroutine が非同期でプリエンプティブ可能になりました. 結果として、関数呼び出しのないループでスケジューラがデッドロックしたり、ガベージコレクションが大幅に遅延する可能性がなくなりました. これは
windows/arm、darwin/arm、js/wasm、plan9/*を除く全てのプラットフォームでサポートされています.プリエンプションの実装の結果として、Linux や macOS システムを含む Unix システムでは、Go 1.14 でビルドされたプログラムは以前のリリースでビルドされたプログラムよりも多くのシグナルを受け取ります. このことは、
syscallやgolang.org/x/sys/unixのようなパッケージを使うプログラムで遅いシステムコールがEINTRで失敗するのをより目撃するようになることを意味します. それらのプログラムは、ループでシステムコールに再びトライする可能性が高いと思いますが、何らかの方法でエラーを処理する必要があります. この件についての情報が知りたければ、Linux システムのためのman 7 signalや他のシステムのための同様のドキュメントを見てください.ページアロケータは
GOMAXPROCSが高い値のときに、より効率的で大幅にロック競合が少なくなりました. 並列かつ高い頻度での大規模な割当では、より低いレイテンシと高いスループットが顕著になります.
time.After、time.Tick、net.Conn.SetDeadline等で使われる内部タイマーは、ロック競合とコンテキストスイッチが少なくなり、より効率的になりました. これはユーザの目に見える変更のないパフォーマンスの改善です.コンパイラ
このリリースは、Go コードが
unsafe.Pointer安全ルールに従っているかを動的にチェックするための測定を追加する、コンパイル時のオプションとして-d=checkptrが追加されました. このオプションは-raceまたは-msanフラグでデフォルトで有効になり(Windows を除く)、-gcflags=all=-d=checkptr=0で無効にすることができます. 特に-d=checkptrは以下をチェックします:
unsafe.Pointerを*Tに変換するときは、結果のポインタがTに適切に整合する必要があります.- ポインタ演算の結果が Go ヒープオブジェクトを指している場合、
unsafe.Pointer型のオペランドの一つが同じオブジェクトを指している必要があります.
-d=checkptrを使うと、標準ライブラリで誤ったアラートが発生するので、現在のところは Windows での使用が推奨されていません.コンパイラは
-jsonフラグによって、インライニング、エスケープ解析、境界チェック削除、nil チェック削除を含む、主要な最適化の機械可読ログを出力できるようになりました.詳細なエスケープ解析診断 (
-m=2) が再び動作するようになりました. これは、以前のリリースの新しいエスケープ解析の実装で破棄されていました.macOS バイナリでの Go のすべてのシンボルは、プラットフォームの規約に従って、アンダースコアで始まるようになりました.
このリリースには、ファジングのための、コンパイラがカバレッジのために挿入する測定の実験的サポートが含まれています. 詳細については 課題を参照してください. この API は将来のリリースで変更される可能性があります.
境界チェックの削除でスライス作成からの情報が使用されるようになり、
intより小さい型のインデックスのチェックを削除できるようになりました.コアライブラリ
標準ライブラリへのすべての変更は小さなものでした.
ライブラリへの小さな変更
いつものように、Go 1の 互換性の約束を念頭に置いて行われた、ライブラリに対するさまざまな小さな変更と更新があります.
crypto/tls
SSL バージョン 3.0 (SSLv3) のサポートは削除されました. SSLv3 は、TLS 以前の危殆化した暗号プロトコルであることに注意されたい.
TLS 1.3 は
GODEBUG環境変数で無効にすることができなくなりました. TLS のバージョンを設定するにはConfig.MaxVersionフィールドを使ってください.
Config.Certificatesフィールドを通して複数の証明書チェーンを提供する場合、ピアと互換性のある最初の証明書チェーンが自動的に選択されるようになりました. これにより、たとえば ECDSA と RSA 証明書を提供し、パッケージが自動的に最適なものを選択できるようになります.Certificate.Leafフィールドが設定されていない場合、この選択のパフォーマンスは低下することにご注意ください.新しい
CipherSuites関数とInsecureCipherSuites関数は、現在実装されている暗号スイートのリストを返します. 新しい [CipherSuiteName] 関数は、暗号スイート ID の名前を返します.新しい
(*ClientHelloInfo).SupportsCertificateと(*CertificateRequestInfo).SupportsCertificateメソッドは、ピアが特定の証明書をサポートしているかどうかを公開します.
tlsパッケージは Next Protocol Negotiation (NPN) 拡張をサポートしなくなり、現在は ALPN のみをサポートしています. 以前のリリースでは両方をサポートしていました. API に変更はなく、アプリケーションは以前と同じように機能するはずです. 他のクライアントとサーバのほとんどは、標準化された ALPN を支持していて、すでに NPN サポートを削除しています.TLS 1.2 ハンドシェイクでサポートされる場合、RSA-PSS 署名が使用されるようになりました. これはほとんどのアプリケーションには影響しませんが、RSA-PSS 署名をサポートしていないカスタム
Certificate.PrivateKey実装は、それらを無効にするために新しいCertificate.SupportedSignatureAlgorithmsフィールドを使用する必要があります.
Config.GetConfigForClientが設定されている場合、Config.CertificatesとConfig.GetCertificateの両方を nil にできるようになりました. コールバックが証明書もエラーも返さない場合、unrecognized_nameが送信されます.新しい
CertificateRequestInfo.Versionフィールドは、クライアント証明書コールバックに TLS バージョンを提供します.以前は
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305とTLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305と呼ばれていた暗号スイートの最終的な名前として、新しいTLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256定数とTLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256定数を使います.crypto/x509
Certificate.CreateCRLは Ed25519 の発行者をサポートしました.debug/dwarf
debug/dwarfパッケージは、DWARF バージョン 5 の読み込みをサポートしました.新しいメソッド
(*Data).AddSectionは、入力ファイルから DWARFDataへの任意の新しい DWARF セクションの追加をサポートします.新しいメソッド
(*Reader).ByteOrderは、現在のコンパイル単位のバイトオーダーを返します. これは、場所の記述など、ネイティブの順序でエンコードされた属性を解釈するために使用することができます.新しいメソッド
(*LineReader).Filesは、ラインリーダーからファイル名テーブルを返します. これは、AttrDeclFileなどのDWARF 属性の値を解釈するために使用することができます.encoding/asn1
Unmarshalは、新しいTagBMPString定数で表される、ASN.1 文字列型の BMPString をサポートするようになりました.encoding/json
Decoder型は、現在のデコーダ位置の入力ストリームのバイトオフセットを返す新しいメソッドInputOffsetをサポートします.
Compactは、文書化されていない、U+2028とU+2029文字のエスケープをしなくなりました. 正しいエスケープについては、HTMLEscapeを参照してください.go/build
Context型はビルドの作業ディレクトリを設定するために使用できる新しいフィールドDirを持っています. デフォルトは実行中のプロセスのカレントディレクトリです. モジュールモードではメインモジュールの位置を特定するのに使用します.go/doc
新しい関数
NewFromFilesは、*ast.Fileのリストからパッケージドキュメントを計算し、適切なパッケージ要素に例を関連付けます. 新しい情報はPackage、Type、Func型の新しいExamplesフィールドと、Example型の新しいSuffixフィールドにあります.hash/maphash
この新しいパッケージはバイトシーケンスのハッシュ関数を提供します. これらのハッシュ関数は、任意の文字列またはバイトシーケンスを整数の均一な分布にマップする必要があるハッシュテーブルまたはその他のデータ構造を実装するために使われることを意図しています.
このハッシュ関数は衝突耐性がありますが、暗号学的に安全ではありません.
io/ioutil
TempDirは、予測可能なプレフィックスとサフィックスを名前に持つディレクトリを作成できるようになりました.TempFileと同様に、パターンに '*' が含まれている場合は、最後の '*' がランダムな文字列に置き換えられます.log
新しい
Lmsgprefixフラグを使用すると、オプションの出力プレフィックスを行の先頭ではなく、ログメッセージの直前に出力するようにロギング関数に指示することができます.math
新しい
FMA関数は、x*y計算の中間丸めを行わずに、x*y+zを浮動小数点で計算します. いくつかのアーキテクチャでは、専用のハードウェア命令を使用してこの計算を実装し、更なるパフォーマンスを得ています.math/big
GCD関数は入力のaとbに0または負の数を受け付けるようになりました.math/bits
新しい関数
Rem、Rem32、Rem64は商がオーバーフローした場合でも剰余の計算をサポートします.mime
.jsファイルと.mjsファイルのデフォルトタイプはapplication/javascriptではなくtext/javascriptになりました. これは、application/javascriptを廃止されたものとして扱うIETF ドラフトに従っています.mime/multipart
Readerの新しいメソッドNextRawPartはquoted-printableのデータを透過的にデコードすることなしに、次の MIME パートを取得することをサポートしています.net/http
Headerの新しいメソッドValuesは正規化されたキーに関連付けられたすべての値を取得することに使えます.
Transportの新しいフィールドDialTLSContextは、プロキシを使わない HTTPS 要求のための TLS 接続を作成するためのオプションのダイヤル関数を指定することに使えます. この新しいフィールドは、いまや廃止となったDialTLSの代わりに使うことができます.DialTLSは引き続き動作しますが、新しいコードでは不要になったらすぐにトランスポートがダイヤルをキャンセルできるDialTLSContextを使うべきです.Windows 上で
ServeFileは 2GB を超えるファイルを正しく処理するようになりました.net/http/httptest
Serverの新しいフィールドEnableHTTP2はテストサーバで HTTP/2 を有効にすることをサポートします.net/textproto
MIMEHeaderの新しいメソッドValuesは正規化されたキーに関連付けられたすべての値を取得することに使えます.os/signal
Windows では、Control-C と Control-Break が
syscall.SIGINTを生成するように、CTRL_CLOSE_EVENT、CTRL_LOGOFF_EVENT、CTRL_SHUTDOWN_EVENTイベントはsyscall.SIGTERMシグナルを生成するようになりました.plugin
pluginパッケージはfreebsd/amd64をサポートしました.reflect
StructOfは、StructField要素にPkgPathフィールドを設定することによって、エクスポートされていないフィールドを持つ構造体の型の生成をサポートしました.runtime
再帰的な
panic、recoverによってruntime.Goexitが中止することはなくなりました.macOS では、
SIGPIPEは Go ランタイムが初期化される前にインストールされたシグナルハンドラに転送されなくなりました. これが必要なのは、macOS がSIGPIPEを閉じたパイプに書き込むのではなく、メインスレッドに渡すからです.runtime/pprof
生成されたプロファイルには、インラインマークに使用される擬似 PC は含まれなくなりました. インライン関数のシンボル情報は、pprof ツールが想定するフォーマットでエンコードされます. これは、最近のリリースで導入されたリグレッションの修正です.
strconv
NumError型に、変換が失敗した理由を取得するのに使えるUnwrapメソッドが追加されました. これは、根本のエラーがstrconv.ErrRangeなのか、strconv.ErrSyntaxなのかを確認することに、errors.IsでNumErrorの値を使うことをサポートします.sync
非常に競合の激しい
Mutexをロック解除すると、そのMutexを待っている次の goroutine に直接 CPU が渡されるようになりました. これにより、CPU 数の多いマシンで、競合の多い mutex のパフォーマンスが大幅に向上しました.testing
テストまたはベンチマークの終了後に対応する
T.CleanupまたはB.Cleanupを呼ぶことによって、テストパッケージはクリーンアップ機能をサポートしました.text/template
text/template パッケージは、括弧で括られた引数が関数として使用されているときに、エラーを正しく報告するようになりました. これは、
{{if (eq .F "a") or (eq .F "b")}}のような誤ったケースで最もよく見られます. これは{{if or (eq .F "a") (eq .F "b")}}と書かれなくてはなりません. 誤ったケースは期待どおりには動作せず、エラーcan't give argument to non-functionで報告されます.unicode
unicodeパッケージとシステム全体の関連サポートは Unicode 11.0 から、新たに4つのスクリプトを含む554の文字と61の絵文字を追加する Unicode 12.0 にアップグレードされました.
- 投稿日:2020-02-08T20:25:10+09:00
GO言語で全てのリクエストで共通処理
共通処理
Go言語ではコンストラクタや継承が存在しないため、全てのリクエストで共通処理を入れたい場合は少し工夫が必要になります。
以下のようにすることでルーティング前に共通処理を入れることができます。func main(){ // ルーティングを設定 mux := http.NewServeMux() mux.HandleFunc("/foo", handler) http.ListenAndServe(":8080", http.HandlerFunc(func (w http.ResponseWriter, r *http.Request){ // 共通処理をここに書く mux.ServeHTTP(w,r) }上記はServeMuxの場合ですが、julienschmidt/httprouterでも同様にできます。
ぜひお試しください!
- 投稿日:2020-02-08T20:02:02+09:00
待って、その Defer 本当�に実行されますか?
社の勉強会で指摘されたことを自戒の意を込めてポストする。
Defer おさらい
defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。
上記は Tour of go より。HTTPクライアントの実装やファイルの読み書きをするときにリソースのクローズする際などによく使われる。
例えば以下のようなコード。
func ReachableDefer() (err error) { resp, err := http.Get("http://sample.com/") if err != nil { return err // 遅延実行されない } defer func() { if err := resp.Body.Close(); err != nil { log.Fatal("close resources", err) } }() _, err = ioutil.ReadAll(resp.Body) if err != nil { return err } return err }
deferの宣言の手前にreturnの宣言がある場合、そのreturn実行時にはdeferの処理は実行されない。故に、上記コードにおいては
http.Get("http://sample.com/")の読み込みが失敗した場合、deferの遅延実行は走らない。この時、
respはnilとなるのでリソースをクローズしなくても問題にはならない。Defer を宣言する位置に注意
さきほどのコードにおいて
deferの宣言をうっかり下記のようにすると、読み込んだリソースが閉じられることなく処理が終了してしまい、読み込んだリソースがリークしてしまう。func UnreachableDefer() (err error) { resp, err := http.Get("http://sample.com/") if err != nil { return err // 遅延実行されない, resp は空なのでOK. } _, err = ioutil.ReadAll(resp.Body) if err != nil { return err // 遅延実行されない, resp はクローズされずにリークするのでNG. } defer func() { if err := resp.Body.Close(); err != nil { log.Fatal("close resources", err) } }() return err }
ioutil.ReadAll(resp.Body)の処理が失敗した場合は遅延実行は走らない。最後に
いつ如何なるときも関数の終了時に遅延実行してくれるものと勘違いしてました。
学び。
- 投稿日:2020-02-08T19:41:26+09:00
slackのSlashCommandsでハマった
ginを使ってslackのSlashCommandsを実行した時のapiサーバーを作っていた。
slackは指定したapiに対して以下のような情報をpostしてくれる
(当初bodyで飛んできていると思い込んでおり、この形でpostされていることに気づくのに時間がかかった)token=gIkuvaNzQIHg97ATvDxqgjtO &team_id=T0001 &team_domain=example &enterprise_id=E0001 &enterprise_name=Globular%20Construct%20Inc &channel_id=C2147483705 &channel_name=test &user_id=U2147483697 &user_name=Steve &command=/weather &text=94070 &response_url=https://hooks.slack.com/commands/1234/5678 &trigger_id=13345224609.738474920.8088930838d88f008e0参考: https://api.slack.com/interactivity/slash-commands
上記の内容を以下のように受け取ろうとしていた。
type Body struct { Token string `json:"token"` TeamID string `json:"team_id"` TeamDomain string `json:"team_domain"` ChannelID string `json:"channel_id"` ChannelName string `json:"channel_name"` UserID string `json:"user_id"` UserName string `json:"user_name"` Command string `json:"command"` Text string `json:"text"` ResponseURL string `json:"response_url"` TriggerID string `json:"trigger_id"` } func Post(c *gin.Context) { var body Body err := c.BindJSON(&body) // some code }実行すると、BindJSONに失敗して以下のようなエラーがでた。
invalid character 'o' in literal true (expecting 'r')これがslackから飛んで来ているが、見てわかる通り、JSONじゃない。
token=gIkuvaNzQIHg97ATvDxqgjtO &team_id=T0001 &team_domain=example &enterprise_id=E0001 &enterprise_name=Globular%20Construct%20Inc &channel_id=C2147483705 &channel_name=test &user_id=U2147483697 &user_name=Steve &command=/weather &text=94070 &response_url=https://hooks.slack.com/commands/1234/5678 &trigger_id=13345224609.738474920.8088930838d88f008e0ginでQueryStringを構造体にbindするには、ShouldBindやBindが使える。
(ShouldBind、Bindはエラーハンドリングを自分でやるかどうかで使い分ける)以下のように書き換えたらちゃんとslackから飛んで来たパラメータが受け取れた。
type Body struct { Token string `form:"token"` TeamID string `form:"team_id"` TeamDomain string `form:"team_domain"` ChannelID string `form:"channel_id"` ChannelName string `form:"channel_name"` UserID string `form:"user_id"` UserName string `form:"user_name"` Command string `form:"command"` Text string `form:"text"` ResponseURL string `form:"response_url"` TriggerID string `form:"trigger_id"` } func Post(c *gin.Context) { var body Body err := c.Bind(&body) // some code
- 投稿日:2020-02-08T14:56:39+09:00
golang で簡単なポーリング処理をサンプルで書いてみた
はじめに
AWS などのサービスは、何かの操作を実行し、リソースのステータス状況を API で確認することがあると思います。
そのステータスの変化を API で確認したいとき、golang でどう書くかを簡単ですが書いてみました。
tl;dr
無限 for ループを使うやり方が簡単そうでした。以下サンプルです。
package main import ( "fmt" "time" ) var count int func callApi() bool { if count >= 2 { return false } count++ return true } func main() { result := true for result { result = callApi() fmt.Println("Waiting...") time.Sleep(1 * time.Second) } fmt.Println("Finish") }一定回数呼ばれたら true/false が変わる関数
callApiで API のステータス変化を簡易的に表してみました。結果は以下のような形です。
Waiting... Waiting... Waiting... Waiting... Finishおわりに
単体の API の処理であればこれで十分なのかなと思いました。golang には channel という並行処理を行う仕組みもあるようなので、その使い方も調べてみようと思います。
参考
- 投稿日:2020-02-08T13:36:12+09:00
golang でファイルアップロードを受けるREST APIサーバを作る
概要
goが提供する標準ライブラリを利用して,ファイルをアップロードするエンドポイントを作成した.下記の理由で標準ライブラリを使用することとなり,私のググり力が少ないのか,Qiita等で同じような記事を見つけられなかった.なので詰まったところ等をまとめておきたいと思う.
標準ライブラリ使用の理由
- githubにアクセスできない環境下であること泣
- メンテナンス性
- 自分の理解向上
環境はgo:1.13学んだところ
詳細は次の節ソースコードを読んでください.
RESTAPIのURI設計
最初は,
http://localhost:8080/updateというエンドポイントに対してPOSTでアップロードするように指定していた.しかし,URIに対して動詞を設定するのはアンチパターン.なるべくオブジェクトを一意に指定できるようにし,メソッドで対応するのがベターというアドバイスをいただいた.なので,http://localhost:8080/fileというエンドポイントを指定するよう変更した.bufの処理
メモリが溢れないための,一時的保存領域である
bufという変数はbuf := make([]byte, 100)という形で宣言されている.またこれにより自動的に100の領域が確保される.次に,n, err := file.Read(buf)という部分で,ファイルからバイナリデータを読み込み,戻り値として読み込んだ数(n)とエラーの有無を返してくる.このままbufをファイルに保存する処理を書くと,保存したファイルにゴミが残ってしまった.そのため,得たn でスライスのサイズをちょうど良い数に変更することでこの問題をクリアした.ソースコード
fileupload.gopackage main import( "fmt" "net/http" "encoding/json" } func main() { http.HandleFunc("/file", handler) http.ListenAndServe(":8080", nil) } func fileHandler(w http.ResponseWriter, r *http.Request) { // postのみ対応させる if r.Method != "POST" { http.Error(w, "405 Method Not Allowed", http.StatusMethodNotAllowed) return } file, fileHeader, err := r.FormFile("json") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } uploadedFileName := fileHeader.Filename // ファイル名が指定したものじゃないとエラーを吐くように設定 if uploadedFileName != "file.json" { http.Error(w, "Invaild file", http.StatusInternalServerError) return } saveJSONFile, err := os.Create("./" + uploadedFileName) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var len int64 = 0 for { buf := make([]byte, 100) // buf を用意するのはメモリ節約のため. n, err := file.Read(buf) buf = buf[:n] // sliceをnで制限しないと,ループ終了時の処理でゴミが残りアップロード if err == io.EOF { // ファイルを後で読み込むことができないエラーが発生する. buf = buf[:n] } if n == 0 { break } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } saveJSONFile.WriteAt(buf,len) len += int64(n) } saveJSONFile.Close() file.Close() fmt.Fprintf(w, "success") }おわりに
golang を書き始めて4ヶ月くらい経ちましたが,いいですね〜.特にエンドポイント等が書きやすく使い勝手がいいです.昨今はマイクロサービス化して,RESTAPIでサービスを接続する流れになってきていると思いますので,一度勉強しておくと良いと思います.
- 投稿日:2020-02-08T08:49:36+09:00
Fn Projectを使ってGoで書いたfunctionを動かしてみる
概要
このエントリでは、OSSのFaaSサーバである「Fn Project」を使い、Goの関数を動かすパターンを扱います。
下図のようなGoによる割り算の関数をFnのサーバにデプロイして動かします。
想定読者
- Fn ProjectでのGoプログラムまだ自分で動かしていない方
- 「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」のGo版に興味がある方
準備
Fn Projectを動かすまでのところは、別エントリ「OCIのMicro InstanceでCentOSにFn Projectのサーバをインストールしてみる」を参照ください。
このエントリでは、筆者はOCIのMicro Instanceの上で作業しています。
Pythonでfunctionを動かす
作業の基本的な流れは、Introduction to Fn with Goに書いてあるものに従っています。
Fn上のアプリ
別のエントリ「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」で、「fn create app」で、fn上に「calc-fn-app」アプリを作ってあるものにFunctionを追加する形をとります。
登録
/divideという位置にGoで関数を一つ作るため、「fn init」を実行します。
$ fn init --runtime go --trigger http divide Creating function at: ./divide Function boilerplate generated. func.yaml created.下記のような3つのファイルができています。
$ ls func.go func.yaml go.modコードを変更
下記のような、入力値のleftとrightを足して返すような関数に書き換えます。
アプリデプロイします。「-w」で作業ディレクトリを指定して、登録しています。
fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/divide初回実行時には、以下のような流れとなるようです。
$ fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/divide Deploying divide to app: calc-fn-app Bumped to version 0.0.3 Building image fndemouser/divide:0.0.3 FN_REGISTRY: fndemouser Current Context: default Sending build context to Docker daemon 6.656kB Step 1/10 : FROM fnproject/go:dev as build-stage ---> 96c8fb94a8e1 Step 2/10 : WORKDIR /function ---> Using cache ---> d5da6400b169 Step 3/10 : WORKDIR /go/src/func/ ---> Using cache ---> 1f6d9b0553c1 Step 4/10 : ENV GO111MODULE=on ---> Using cache ---> 76b3280f859f Step 5/10 : COPY . . ---> 8b49123ddb74 Step 6/10 : RUN cd /go/src/func/ && go build -o func ---> Running in 4ebf2264a532 go: finding github.com/pkg/errors v0.9.1 go: finding github.com/cockroachdb/apd v1.1.0 go: finding github.com/fnproject/fdk-go v0.0.1 go: downloading github.com/fnproject/fdk-go v0.0.1 go: downloading github.com/cockroachdb/apd v1.1.0 go: downloading github.com/pkg/errors v0.9.1 Removing intermediate container 4ebf2264a532 ---> 4804dafb59b8 Step 7/10 : FROM fnproject/go ---> bc635796c9df Step 8/10 : WORKDIR /function ---> Using cache ---> e16151850db8 Step 9/10 : COPY --from=build-stage /go/src/func/func /function/ ---> 30d845c22c66 Step 10/10 : ENTRYPOINT ["./func"] ---> Running in b223d912dc43 Removing intermediate container b223d912dc43 ---> 508f6decd40d Successfully built 508f6decd40d Successfully tagged fndemouser/divide:0.0.3 Updating function divide using image fndemouser/divide:0.0.3... Successfully created function: divide with fndemouser/divide:0.0.3 Successfully created trigger: divide Trigger Endpoint: http://127.0.0.1:18080/t/calc-fn-app/dividefnのコマンドで確認します。(別エントリでplus,minus,multiplyを作ってあります)
$ fn list fn calc-fn-app NAME IMAGE ID divide fndemouser/divide:0.0.3 01E0GXAP0ENG8G00GZJ0000026 multiply fndemouser/multiply:0.0.5 01E086F2D6NG8G00GZJ000001E plus fndemouser/plus:0.0.2 01E0393FDCNG8G00GZJ000000E subtract fndemouser/subtract:0.0.3 01E05KAKGENG8G00GZJ0000013Dockerのコマンドで確認すると、対応するコンテナのイメージができていることがわかります。
$ docker images | grep divide fndemouser/divide 0.0.3 508f6decd40d About a minute ago 17.1MB実行
登録時の末尾にあったURLにcurlでアクセスしてみます。
$ curl -d '{"left":"8", "right":"4"}' http://127.0.0.1:18080/t/calc-fn-app/divide {"result":"2"}8/4の結果である2が返ってきています。
おわりに
このエントリでは、Fn projectを使ってGoで書いたfunctionを動かしてみることを扱いました。
このエントリで使用したコードは、https://github.com/hrkt/calc-fn-app/releases/tag/0.0.4のタグに格納してあります。
補足:このエントリを書くにあたり
このエントリを書くにあたり、OCIのマイクロインスタンス上に下記の状況を作り、ノートPCからクラウド側にsshでリモートでつないで作業しました。
- sshの先にscreenで複数枚のウインドウ上げておいて作業して
- VS CodeをWEBで動かして「cdr/code-serverとOCIのAlways FreeのMicroインスタンスでVS Codeを動かしてみる」
- Fn projectのサーバもそこで動かして
- コードはGitHubに
- 投稿日:2020-02-08T00:24:59+09:00
[Docker]クソ重いHello, World!イメージを劇的に軽くする
Docker v17くらいからMultiStageBuildという機能が実装されています。
今まで、「Hello,Worldを出力するだけのImageなのに、ベースImageサイズが大きくて、結局出来上がるImageも数百MBになるんだけど、、、」と、なっていたものが、BuildはGoのImage,実行はalpineのImageとできるようになりました。(v17以前でもできなくはなかったけど、めんどくさかった)
これが一体どういうことなのかをみていきたいと思います。今回は、
Hello, World!を出力するコンテナを作成していきます。main.gopackage main import "fmt" func main() { fmt.Println("Hello, World!") }MultiStageBuildを使わない場合
Dockerfileは以下のようになります。
FROM golang:1.13.7-alpine3.11 COPY ./main.go ./ RUN go build -o ./hello ./main.go ENTRYPOINT ["./hello"]それぞれ同じディレクトリに存在する状態で、以下のコマンドを打ってみましょう。
$ docker build -t hello:0.1 .ビルド出来たらRunしてみます。
$ docker run hello:0.1 Hello World!ちゃんとmain.goがbuildされて、実行されました。では、この時のImageサイズをみてみます。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello 0.1 ea6e8238c063 49 seconds ago 361MB golang 1.13.7-alpine3.11 87eefb76f0a8 9 days ago 359MBこのように、golangのcontainer Imageがベースになっているため、サイズが大きくなっています。
Hello, World!を出力するだけのコンテナなのに、これじゃあ重すぎですね。。。MultiStageBuildを使う場合
Dockerfileは以下のようになります。
#Stage 1 FROM golang:1.13.7-alpine3.11 as builder COPY ./main.go ./ RUN go build -o /hello ./main.go #Stage 2 FROM alpine:3.11 COPY --from=builder /hello . ENTRYPOINT ["./hello"]Stage1はgolangのImageを使っていて、ここでmain.goをbuildしています。そしてその実行ファイルをStage2に持ってきて、こっちで実行すると言う形です。
$ docker run hello:0.1 Hello World!こちらもちゃんと実行されました。同様に、Imageのサイズをみてみます。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello 0.1 15f814bf3a87 12 seconds ago 7.6MB <none> <none> 6f908ead16f8 13 seconds ago 361MB golang 1.13.7-alpine3.11 87eefb76f0a8 9 days ago 359MB alpine 3.11 e7d92cdc71fe 2 weeks ago 5.59MBここで
<none>はStage1のImageで、<hello>がStage2のImageになっています。Hello, Worldを出力するImageは<hello>なので、そのサイズなんと7.6MBです!MultiStageBuildを使わない場合と比べて約350MBもImageサイズを削減することを実現できました!!まとめ
ということで、今回はDockerのMultiStageBuildの機能について、触れてみました。サイズの重たいBuild環境を分離することで、Imageサイズの削減を可能にすることを挙げましたが、他にもStageで分けることで、Dockerfileの保守のしやすさが上がる、セキュリティに強くなるなど、良い事が沢山あります!みなさまも使った事がない方は一度チャレンジしてみてください!




