20200208のGoに関する記事は10件です。

Go の x/text/transform で独自構造パケットを華麗に処理する

ネットワーク上に流すデータ(パケット)はよく次のような構造になります。

image.png

可変長のデータを格納できるように、先頭に長さを入れておくという方式ですね。

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を暗号化してほしい!」という追加の仕様がきたとします。
そうなると暗号化の処理が追加で必要となります。

image.png

暗号化の関数を 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で実現できました。
図で表すと次のようなデータの流れになります。

image.png

transform.Transformerを使う最大の利点は、transformer.Reader/transformer.Writerがio.Reader/io.Writerを実装していることです。

io.Readerはシンプルながらかなりの応用が効くGoらしいインターフェースとなっています。詳しい利点や応用例については次の記事などを読むとよいでしょう。

とにかく、Transformerを組み合わせることで、間にどれだけ処理を挟んでも、Go標準のio.Reader/io.Writerで読み書きできるため、TCPソケットやUDPソケット(net.Conn)にそのまま接続することも可能です。

すばらしいですね!

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.ValueIsUndefinedIsNullIsNaN メソッドを持つようになりました.

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.NumCPUGOMAXPROCS のデフォルト値のためのゾーン 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 buildgo run、およびその他のビルドコマンドは、標準ライブラリ内のパッケージと、コマンド行で .go ファイルとして指定されたパッケージだけをビルドできます.

以前は、go コマンドはそれぞれのパッケージパスをモジュールの最新バージョンに解決しますが、モジュールのパスやバージョンは記録しませんでした. その結果、遅くて、再現できないビルドとなりました.

go get は以前と同じように動作しますし、バージョンを明示的した go mod downloadgo list -m も同様です.

+incompatible バージョン

モジュールの最新バージョンに go.mod ファイルが含まれている場合、go get は、そのバージョンが明示的に要求されているか、すでに必要とされていない限り、そのモジュールの互換性のないメジャーバージョンにはアップグレードしません. go list もバージョン管理から直接取得する際にそのようなモジュールの互換性のないメジャーバージョンを省略しますが、プロキシから報告された場合にはそれらを含めるかもしれません.

go.mod ファイルメンテナンス

go mod tidy 以外の go コマンドは、メインモジュールの他の(推移的な)依存関係によってすでに暗示されている間接的な依存関係のバージョンを指定する require ディレクティブを削除しなくなりました. go mod tidy 以外の 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/armdarwin/armjs/wasmplan9/* を除く全てのプラットフォームでサポートされています.

プリエンプションの実装の結果として、Linux や macOS システムを含む Unix システムでは、Go 1.14 でビルドされたプログラムは以前のリリースでビルドされたプログラムよりも多くのシグナルを受け取ります. このことは、syscallgolang.org/x/sys/unix のようなパッケージを使うプログラムで遅いシステムコールが EINTR で失敗するのをより目撃するようになることを意味します. それらのプログラムは、ループでシステムコールに再びトライする可能性が高いと思いますが、何らかの方法でエラーを処理する必要があります. この件についての情報が知りたければ、Linux システムのための man 7 signal や他のシステムのための同様のドキュメントを見てください.

ページアロケータは GOMAXPROCS が高い値のときに、より効率的で大幅にロック競合が少なくなりました. 並列かつ高い頻度での大規模な割当では、より低いレイテンシと高いスループットが顕著になります.

time.Aftertime.Ticknet.Conn.SetDeadline 等で使われる内部タイマーは、ロック競合とコンテキストスイッチが少なくなり、より効率的になりました. これはユーザの目に見える変更のないパフォーマンスの改善です.

コンパイラ

このリリースは、Go コードが unsafe.Pointer 安全ルールに従っているかを動的にチェックするための測定を追加する、コンパイル時のオプションとして -d=checkptr が追加されました. このオプションは -race または -msan フラグでデフォルトで有効になり(Windows を除く)、-gcflags=all=-d=checkptr=0 で無効にすることができます. 特に -d=checkptr は以下をチェックします:

  1. unsafe.Pointer*T に変換するときは、結果のポインタが T に適切に整合する必要があります.
  2. ポインタ演算の結果が 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 は、入力ファイルから DWARF Data への任意の新しい DWARF セクションの追加をサポートします.

新しいメソッド (*Reader).ByteOrder は、現在のコンパイル単位のバイトオーダーを返します. これは、場所の記述など、ネイティブの順序でエンコードされた属性を解釈するために使用することができます.

新しいメソッド (*LineReader).Files は、ラインリーダーからファイル名テーブルを返します. これは、AttrDeclFile などのDWARF 属性の値を解釈するために使用することができます.

encoding/asn1

Unmarshal は、新しい TagBMPString 定数で表される、ASN.1 文字列型の BMPString をサポートするようになりました.

encoding/json

Decoder 型は、現在のデコーダ位置の入力ストリームのバイトオフセットを返す新しいメソッド InputOffset をサポートします.

Compactは、文書化されていない、U+2028U+2029 文字のエスケープをしなくなりました. 正しいエスケープについては、HTMLEscape を参照してください.

go/build

Context 型はビルドの作業ディレクトリを設定するために使用できる新しいフィールド Dir を持っています. デフォルトは実行中のプロセスのカレントディレクトリです. モジュールモードではメインモジュールの位置を特定するのに使用します.

io/ioutil

TempDir は、予測可能なプレフィックスとサフィックスを名前に持つディレクトリを作成できるようになりました. TempFile と同様に、パターンに '*' が含まれている場合は、最後の '*' がランダムな文字列に置き換えられます.

log

新しい Lmsgprefix フラグを使用すると、オプションの出力プレフィックスを行の先頭ではなく、ログメッセージの直前に出力するようにロギング関数に指示することができます.

go/doc

新しい関数 NewFromFiles は、*ast.Fileのリストからパッケージドキュメントを計算し、適切なパッケージ要素に例を関連付けます. 新しい情報は PackageTypeFunc 型の新しい Examples フィールドと、Example 型の新しい Suffix フィールドにあります.

math

新しい FMA 関数は、x*y 計算の中間丸めを行わずに、x*y+z を浮動小数点で計算します. いくつかのアーキテクチャでは、専用のハードウェア命令を使用してこの計算を実装し、更なるパフォーマンスを得ています.

math/big

GCD 関数は入力の ab に0または負の数を受け付けるようになりました.

math/bits

新しい関数 RemRem32Rem64 は商がオーバーフローした場合でも剰余の計算をサポートします.

mime

.js ファイルと .mjs ファイルのデフォルトタイプは application/javascript ではなく text/javascript になりました. これは、application/javascript を廃止されたものとして扱うIETF ドラフトに従っています.

mime/multipart

Reader の新しいメソッド NextRawPartquoted-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_EVENTCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT イベントは syscall.SIGTERM シグナルを生成するようになりました.

plugin

plugin パッケージは freebsd/amd64 をサポートしました.

reflect

StructOf は、StructField 要素に PkgPath フィールドを設定することによって、エクスポートされていないフィールドを持つ構造体の型の生成をサポートしました.

runtime

再帰的な panicrecover によって runtime.Goexit が中止することはなくなりました.

macOS では、SIGPIPE は Go ランタイムが初期化される前にインストールされたシグナルハンドラに転送されなくなりました. これが必要なのは、macOS が SIGPIPE を閉じたパイプに書き込むのではなく、メインスレッドに渡すからです.

runtime/pprof

生成されたプロファイルには、インラインマークに使用される擬似 PC は含まれなくなりました. インライン関数のシンボル情報は、pprof ツールが想定するフォーマットでエンコードされます. これは、最近のリリースで導入されたリグレッションの修正です.

strconv

NumError 型に、変換が失敗した理由を取得するのに使える Unwrap メソッドが追加されました. これは、根本のエラーが strconv.ErrRange なのか、strconv.ErrSyntax なのかを確認することに、errors.IsNumError の値を使うことをサポートします.

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 にアップグレードされました.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.ValueIsUndefinedIsNullIsNaN メソッドを持つようになりました.

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.NumCPUGOMAXPROCS のデフォルト値のためのゾーン 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 buildgo run、およびその他のビルドコマンドは、標準ライブラリ内のパッケージと、コマンド行で .go ファイルとして指定されたパッケージだけをビルドできます.

以前は、go コマンドはそれぞれのパッケージパスをモジュールの最新バージョンに解決しますが、モジュールのパスやバージョンは記録しませんでした. その結果、遅くて、再現できないビルドとなりました.

go get は以前と同じように動作しますし、バージョンを明示した go mod downloadgo 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/armdarwin/armjs/wasmplan9/* を除く全てのプラットフォームでサポートされています.

プリエンプションの実装の結果として、Linux や macOS システムを含む Unix システムでは、Go 1.14 でビルドされたプログラムは以前のリリースでビルドされたプログラムよりも多くのシグナルを受け取ります. このことは、syscallgolang.org/x/sys/unix のようなパッケージを使うプログラムで遅いシステムコールが EINTR で失敗するのをより目撃するようになることを意味します. それらのプログラムは、ループでシステムコールに再びトライする可能性が高いと思いますが、何らかの方法でエラーを処理する必要があります. この件についての情報が知りたければ、Linux システムのための man 7 signal や他のシステムのための同様のドキュメントを見てください.

ページアロケータは GOMAXPROCS が高い値のときに、より効率的で大幅にロック競合が少なくなりました. 並列かつ高い頻度での大規模な割当では、より低いレイテンシと高いスループットが顕著になります.

time.Aftertime.Ticknet.Conn.SetDeadline 等で使われる内部タイマーは、ロック競合とコンテキストスイッチが少なくなり、より効率的になりました. これはユーザの目に見える変更のないパフォーマンスの改善です.

コンパイラ

このリリースは、Go コードが unsafe.Pointer 安全ルールに従っているかを動的にチェックするための測定を追加する、コンパイル時のオプションとして -d=checkptr が追加されました. このオプションは -race または -msan フラグでデフォルトで有効になり(Windows を除く)、-gcflags=all=-d=checkptr=0 で無効にすることができます. 特に -d=checkptr は以下をチェックします:

  1. unsafe.Pointer*T に変換するときは、結果のポインタが T に適切に整合する必要があります.
  2. ポインタ演算の結果が 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.CertificatesConfig.GetCertificate の両方を nil にできるようになりました. コールバックが証明書もエラーも返さない場合、unrecognized_name が送信されます.

新しい CertificateRequestInfo.Version フィールドは、クライアント証明書コールバックに TLS バージョンを提供します.

以前は TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305TLS_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 は、入力ファイルから DWARF Data への任意の新しい DWARF セクションの追加をサポートします.

新しいメソッド (*Reader).ByteOrder は、現在のコンパイル単位のバイトオーダーを返します. これは、場所の記述など、ネイティブの順序でエンコードされた属性を解釈するために使用することができます.

新しいメソッド (*LineReader).Files は、ラインリーダーからファイル名テーブルを返します. これは、AttrDeclFile などのDWARF 属性の値を解釈するために使用することができます.

encoding/asn1

Unmarshal は、新しい TagBMPString 定数で表される、ASN.1 文字列型の BMPString をサポートするようになりました.

encoding/json

Decoder 型は、現在のデコーダ位置の入力ストリームのバイトオフセットを返す新しいメソッド InputOffset をサポートします.

Compactは、文書化されていない、U+2028U+2029 文字のエスケープをしなくなりました. 正しいエスケープについては、HTMLEscape を参照してください.

go/build

Context 型はビルドの作業ディレクトリを設定するために使用できる新しいフィールド Dir を持っています. デフォルトは実行中のプロセスのカレントディレクトリです. モジュールモードではメインモジュールの位置を特定するのに使用します.

go/doc

新しい関数 NewFromFiles は、*ast.Fileのリストからパッケージドキュメントを計算し、適切なパッケージ要素に例を関連付けます. 新しい情報は PackageTypeFunc 型の新しい Examples フィールドと、Example 型の新しい Suffix フィールドにあります.

hash/maphash

この新しいパッケージはバイトシーケンスのハッシュ関数を提供します. これらのハッシュ関数は、任意の文字列またはバイトシーケンスを整数の均一な分布にマップする必要があるハッシュテーブルまたはその他のデータ構造を実装するために使われることを意図しています.

このハッシュ関数は衝突耐性がありますが、暗号学的に安全ではありません.

io/ioutil

TempDir は、予測可能なプレフィックスとサフィックスを名前に持つディレクトリを作成できるようになりました. TempFile と同様に、パターンに '*' が含まれている場合は、最後の '*' がランダムな文字列に置き換えられます.

log

新しい Lmsgprefix フラグを使用すると、オプションの出力プレフィックスを行の先頭ではなく、ログメッセージの直前に出力するようにロギング関数に指示することができます.

math

新しい FMA 関数は、x*y 計算の中間丸めを行わずに、x*y+z を浮動小数点で計算します. いくつかのアーキテクチャでは、専用のハードウェア命令を使用してこの計算を実装し、更なるパフォーマンスを得ています.

math/big

GCD 関数は入力の ab に0または負の数を受け付けるようになりました.

math/bits

新しい関数 RemRem32Rem64 は商がオーバーフローした場合でも剰余の計算をサポートします.

mime

.js ファイルと .mjs ファイルのデフォルトタイプは application/javascript ではなく text/javascript になりました. これは、application/javascript を廃止されたものとして扱うIETF ドラフトに従っています.

mime/multipart

Reader の新しいメソッド NextRawPartquoted-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_EVENTCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT イベントは syscall.SIGTERM シグナルを生成するようになりました.

plugin

plugin パッケージは freebsd/amd64 をサポートしました.

reflect

StructOf は、StructField 要素に PkgPath フィールドを設定することによって、エクスポートされていないフィールドを持つ構造体の型の生成をサポートしました.

runtime

再帰的な panicrecover によって runtime.Goexit が中止することはなくなりました.

macOS では、SIGPIPE は Go ランタイムが初期化される前にインストールされたシグナルハンドラに転送されなくなりました. これが必要なのは、macOS が SIGPIPE を閉じたパイプに書き込むのではなく、メインスレッドに渡すからです.

runtime/pprof

生成されたプロファイルには、インラインマークに使用される擬似 PC は含まれなくなりました. インライン関数のシンボル情報は、pprof ツールが想定するフォーマットでエンコードされます. これは、最近のリリースで導入されたリグレッションの修正です.

strconv

NumError 型に、変換が失敗した理由を取得するのに使える Unwrap メソッドが追加されました. これは、根本のエラーが strconv.ErrRange なのか、strconv.ErrSyntax なのかを確認することに、errors.IsNumError の値を使うことをサポートします.

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 にアップグレードされました.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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でも同様にできます。
ぜひお試しください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

待って、その 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 の遅延実行は走らない。

この時、respnil となるのでリソースをクローズしなくても問題にはならない。

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) の処理が失敗した場合は遅延実行は走らない。

最後に

いつ如何なるときも関数の終了時に遅延実行してくれるものと勘違いしてました。
学び。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.8088930838d88f008e0

ginで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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 という並行処理を行う仕組みもあるようなので、その使い方も調べてみようと思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package 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でサービスを接続する流れになってきていると思いますので,一度勉強しておくと良いと思います.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Fn Projectを使ってGoで書いたfunctionを動かしてみる

概要

このエントリでは、OSSのFaaSサーバである「Fn Project」を使い、Goの関数を動かすパターンを扱います。

下図のようなGoによる割り算の関数をFnのサーバにデプロイして動かします。

fn_go.png

想定読者

準備

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/divide

fnのコマンドで確認します。(別エントリで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       01E05KAKGENG8G00GZJ0000013

Dockerのコマンドで確認すると、対応するコンテナのイメージができていることがわかります。

$ 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でリモートでつないで作業しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Docker]クソ重いHello, World!イメージを劇的に軽くする

Docker v17くらいからMultiStageBuildという機能が実装されています。
今まで、「Hello,Worldを出力するだけのImageなのに、ベースImageサイズが大きくて、結局出来上がるImageも数百MBになるんだけど、、、」と、なっていたものが、BuildはGoのImage,実行はalpineのImageとできるようになりました。(v17以前でもできなくはなかったけど、めんどくさかった)
これが一体どういうことなのかをみていきたいと思います。

今回は、Hello, World!を出力するコンテナを作成していきます。

main.go
package 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の保守のしやすさが上がる、セキュリティに強くなるなど、良い事が沢山あります!みなさまも使った事がない方は一度チャレンジしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む