2020/10/30


Go 言語はシングルバイナリをウリにしたプログラミング言語です。バイナリファイルを1つポンと scp で転送すれば動くのでとても便利です。シングルバイナリとなると当然、画像や HTML といったアセットをバイナリに埋め込みたくなります。

Go 言語ではこれまで go-assetsgo-bindatastatik というツールを使う事でファイルのコンテンツをバイナリ化し、変数からアクセスする様にしてきました。

しかしそれらには色々な流儀や OS 間でのまばらな動作など、ユーザにとって納得のいかない物がありました。昨日、Go 言語ではオフィシャルとしてこのファイル埋め込みをサポートする様になりました。Go 1.16 から使える様になります。

cmd/go: add //go:embed support · golang/go@25d28ec · GitHub

+3 −3 src/cmd/go/internal/fsys/fsys.go +1 −1 src/cmd/go/internal/fsys/fsys_test.go +2 −0 src/cmd/go/...

https://github.com/golang/go/commit/25d28ec55aded46e0be9c2298f24287d296a9e47
package main

import (
    _ "embed"
    "net/http"

    "github.com/labstack/echo"
)

//go:embed static/logo.png
var contents []byte

func main() {
    e := echo.New()
    e.GET("/"func(c echo.Context) error {
        return c.Blob(http.StatusOK, "image/png", contents)
    })
    e.Logger.Fatal(e.Start(":8989"))
}

変数に //go:embed [ファイル名] というコメントを付ける事でそのコンテンツを変数に埋め込む事ができます。開発者がやるのは embed をブランクインポートする事と go build だけです。ファイルが読み込めなかったり、記法が不正な場合はコンパイルエラーとなります。

pattern static/logo1.png: no matching files found

またバイト列ではなく文字列でも使えます。

package main

import (
    _ "embed"
    "fmt"
)

//go:embed message.txt
var message string

func main() {
    fmt.Println(message)
}

これまで go generate を使ってバイナリファイルを変数に埋め込んでいたので、それから比べるとずいぶん便利になりました。そしてファイルだけでなく、ファイルシステムとして埋め込む事もできる様になりました。

package main

import (
    "embed"
    "io"
    "log"
    "os"
    "path"
)

//go:embed static
var local embed.FS

func main() {
    fis, err := local.ReadDir("static")
    if err != nil {
        log.Fatal(err)
    }
    for _, fi := range fis {
        in, err := local.Open(path.Join("static", fi.Name()))
        if err != nil {
            log.Fatal(err)
        }
        out, err := os.Create("embed-" + path.Base(fi.Name()))
        if err != nil {
            log.Fatal(err)
        }
        io.Copy(out, in)
        out.Close()
        in.Close()
        log.Println("exported""embed-"+path.Base(fi.Name()))
    }
}

embed.FS は実際にファイルが開ける Open メソッドをサポートしている為、 http.FileSystem が返す様なファイルシステムとは互換性がなく、以下の様にウェブサーバに使う事はできないですが、いずれサードパーティからラップするライブラリが登場すると思います。

package main

import (
    "embed"
    "net/http"

    "github.com/labstack/echo"
)

//go:embed static
var local embed.FS

func main() {
    e := echo.New()
    e.GET("/", echo.WrapHandler(http.FileServer(local)))
    e.Logger.Fatal(e.Start(":8989"))
}

追記 http.FS という関数が既に用意されていました。以下のリポジトリの example4 にサンプルを足しています。

Go 1.16 が待ち遠しいですね。

Go 1.16 が待ち遠しいですね。

※大事な事なので2回言いました。

これらのサンプルは以下のリポジトリに置いてあります。

GitHub - mattn/go-embed-example

We use optional third-party analytics cookies to understand how you use GitHub.com so we can build b...

https://github.com/mattn/go-embed-example
改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 Kindle版 / ¥2,278 (2019年08月01日)
 
発送可能時間:

Posted at by



2020/09/16


Go のアプリケーションを作っていると、シグナルの受信に伴い処理を中断したり再起動する処理を実装する事が多い。これまでは signal.Notify でシグナルをキャッチし、別途 context.WithCancel で作成したコンテキストを自ら cancel する処理を書かなければならなかった。Go の tip に入ったコミットにより、これが幾分改善される様になった。

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "time"
)

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    select {
    case <-time.After(time.Second):
        fmt.Println("done")
    case <-ctx.Done():
        stop()
        fmt.Println("canceled")
    }
}

このコードを実行すると、1秒経過すると done が、途中で CTRL-C をタイプすると canceled が表示される。一見、利用用途が少ない様に見えるが以下の様に goroutine を複数起動し、signal で一括終了する時には便利。

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "sync"
)

func blocking(ctx context.Context, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("worker started")
    <-ctx.Done()
    fmt.Println("worker canceled")
}

func main() {
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    var wg sync.WaitGroup
    wg.Add(3)
    go blocking(ctx, &wg)
    go blocking(ctx, &wg)
    go blocking(ctx, &wg)

    wg.Wait()
}
改訂2版 みんなのGo言語 改訂2版 みんなのGo言語
松木 雅幸, mattn, 藤原 俊一郎, 中島 大一, 上田 拓也, 牧 大輔, 鈴木 健太
技術評論社 Kindle版 / ¥2,278 (2019年08月01日)
 
発送可能時間:

Posted at by



2020/06/19


元ネタ

作ったもの。

mattn/fakemovie · GitHub
https://github.com/mattn/fakemovie

Go のパッケージにしてあるので、Go の HTTP サーバからサーブする画像全てに再生ボタンを付ける様なミドルウェアを作る事もできてとても便利です。一応、オマケとしてコマンドを用意してあります。

$ fakemovie -r 40 input.png
fakemovie

こんな感じに使えるのでご利用下さい。

Posted at by