Go プログラムで静的リソースをバンドルするにはどうすればいいですか? 質問する

Go プログラムで静的リソースをバンドルするにはどうすればいいですか? 質問する

私は Go で小さな Web アプリケーションを開発中です。これは、開発者のマシン上でアプリケーションや Web サービスのデバッグを支援するツールとして使用されることを目的としています。プログラムへのインターフェイスは、HTML だけでなく、JavaScript (機能用)、画像、CSS (スタイル用) を含む Web ページです。このアプリケーションをオープンソース化することを計画しているので、ユーザーは Makefile を実行でき、すべてのリソースが必要な場所に配置されるようになります。ただし、ファイルや依存関係をできるだけ少なくして、実行可能ファイルを簡単に配布できるようにしたいと考えています。HTML/CSS/JS を実行可能ファイルにバンドルして、ユーザーがダウンロードして 1 つのファイルだけを気にすればよいようにする良い方法はありますか?


現在、私のアプリでは、静的ファイルの提供は次のようになります。

// called via http.ListenAndServe
func switchboard(w http.ResponseWriter, r *http.Request) {

    // snipped dynamic routing...

    // look for static resource
    uri := r.URL.RequestURI()
    if fp, err := os.Open("static" + uri); err == nil {
        defer fp.Close()
        staticHandler(w, r, fp)
        return
    }

    // snipped blackhole route
}
    

これは非常に簡単です。要求されたファイルが静的ディレクトリに存在する場合は、ハンドラーを呼び出します。ハンドラーはファイルを開いて、Content-Type提供前に適切な設定を試みます。私の考えでは、これを実際のファイルシステムに基づく必要がある理由はなく、コンパイルされたリソースがある場合は、要求 URI でインデックスを付けて、そのように提供することができます。

これを行う良い方法がなかったり、私がこれをやろうとすると間違った方向に進んでしまう場合は、お知らせください。エンド ユーザーは、管理するファイルができるだけ少ないことを好むだろうと考えただけです。

ベストアンサー1

Go 1.16 以降、go ツールは実行可能バイナリに静的ファイルを直接埋め込むことをサポートしています。

インポートする必要がありますembedパッケージを作成し、//go:embedディレクティブを使用して、埋め込むファイルとそれらを保存する変数をマークします。

実行可能ファイルにファイルを埋め込む 3 つの方法hello.txt:

import _ "embed"

//go:embed hello.txt
var s string
print(s)

//go:embed hello.txt
var b []byte
print(string(b))

//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))

使用方法embed.FS変数の型を指定すると、複数のファイルを変数に含めることができ、シンプルなファイル システム インターフェイスが提供されます。

// content holds our static web server content.
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

net/httpembed.FS使用値からファイルを提供するサポートがありますhttp.FS()このような:

http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))

テンプレートパッケージは、以下を使用してテンプレートを解析することもできます。text/template.ParseFS()html/template.ParseFS()機能とtext/template.Template.ParseFS()html/template.Template.ParseFS()方法:

template.ParseFS(content, "*.tmpl")

以下の回答には、古いオプション (Go 1.16 より前) がリストされています。


テキストファイルの埋め込み

テキスト ファイルの場合、ソース コード自体に簡単に埋め込むことができます。string次のようにバック クォートを使用してリテラルを宣言するだけです。

const html = `
<html>
<body>Example embedded HTML content.</body>
</html>
`

// Sending it:
w.Write([]byte(html))  // w is an io.Writer

最適化のヒント:

ほとんどの場合、リソースをio.Writer変換の結果を保存することもできます[]byte

var html = []byte(`
<html><body>Example...</body></html>
`)

// Sending it:
w.Write(html)  // w is an io.Writer

注意しなければならないのは、生の文字列リテラルにバッククォート文字 (`) を含めることができないということだけです。生の文字列リテラルにはシーケンスを含めることができません (解釈された文字列リテラルとは異なります)。そのため、埋め込むテキストにバッククォートが含まれている場合は、生の文字列リテラルを分割し、バッククォートを解釈された文字列リテラルとして連結する必要があります。次の例をご覧ください。

var html = `<p>This is a back quote followed by a dot: ` + "`" + `.</p>`

これらの連結はコンパイラによって実行されるため、パフォーマンスには影響しません。

バイナリファイルの埋め込み

バイトスライスとして保存する

バイナリファイル(画像など)の場合、最もコンパクト(ネイティブバイナリに関して)かつ最も効率的なのは、ファイルの内容を[]byteソースコードに記述することです。これは、次のようなサードパーティのツール/ライブラリによって生成できます。ゴービンデータ

[]byteこれにサードパーティのライブラリを使用したくない場合は、バイナリ ファイルを読み取り、ファイルの正確な内容で初期化される型の変数を宣言する Go ソース コードを出力する簡単なコード スニペットを次に示します。

imgdata, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}

fmt.Print("var imgdata = []byte{")
for i, v := range imgdata {
    if i > 0 {
        fmt.Print(", ")
    }
    fmt.Print(v)
}
fmt.Println("}")

ファイルに0から16までのバイトが含まれている場合の出力例(遊び場に行く):

var imgdata = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

base64として保存string

ファイルが「大きすぎない」場合(ほとんどの画像やアイコンはこれに該当します)、他の実行可能なオプションもあります。ファイルの内容をBase64に変換しstring、ソースコードに保存することができます。アプリケーションの起動時(func init())または必要なときに、元の[]byteコンテンツにデコードできます。Goは、encoding/base64パッケージ。

(バイナリ) ファイルを base64 に変換するのはstring次のように簡単です:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data))

結果の base64 文字列を、たとえば としてソース コードに保存しますconst

デコードは 1 つの関数呼び出しだけです。

const imgBase64 = "<insert base64 string here>"

data, err := base64.StdEncoding.DecodeString(imgBase64) // data is of type []byte

引用通り保存string

base64で保存するよりも効率的ですが、ソースコードが長くなる可能性があります。引用バイナリデータの文字列リテラル。任意の文字列の引用符付き形式は、strconv.Quote()関数:

data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(strconv.Quote(string(data))

0から64までの値を含むバイナリデータの場合、出力は次のようになります(遊び場に行く):

"\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

(strconv.Quote()引用符が先頭と末尾に追加されることに注意してください。)

この引用符で囲まれた文字列をソース コード内で直接使用できます。次に例を示します。

const imgdata = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

すぐに使用でき、デコードする必要はありません。引用符の解除はコンパイル時に Go コンパイラによって実行されます。

必要に応じて、次のようにバイトスライスとして保存することもできます。

var imgdata = []byte("\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?")

おすすめ記事