0
1

More than 1 year has passed since last update.

Go httpサーバー&リソースをワンバイナリにする

Posted at

Go言語で 標準package のみを使用して、
httpサーバーを作成するのをやってみたいと思います。

ただ httpサーバー を立てるだけだと
すぐできてしまいそうなので、今回は go:embed を使ってみたいと思います。

go:embed とは

httpサーバーを立てて何かページを返すようなものを作ろうと思っています。
あとリソースは、外部アクセスさせずにローカルで完結するようにします。

そのためには、リソースをローカルで持たなければいけません。

例えば、画像・CSS・JavaScript などのファイルとか。。。

実行体(exe)と同じところにファイルを置いて、
exeから読み込んで返してもらってもよいのですが
ファイルを入れ忘れるとかがあると面倒なので・・・ワンバイナリにしたい!

そんな時に使えるのが go:embed です。
リソースも実行体に含めてワンバイナリにしてくれます。

Go 1.16 からリリースされたので、それ以上で使えます!

使い方もそんなに難しくないので、さっそくやってみます。

httpサーバーを立てる

まずは、httpサーバーを作ります。

server.go
package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", indexPage)

    err := http.ListenAndServe(":3000", nil)
    if err != nil {
        log.Fatal(err)
    }
}

func indexPage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World.")
}

あとは go build して 出来上がったexe をコマンドプロンプトから実行する。
※最初はgo modも作成してください

実行したあとに、ブラウザで http://localhost:3000 にアクセスしてみましょう。

image.png

文字が表示されました!

ちなみに実行を止めるときは、
コマンドプロンプト上で Ctrl + C を押すと実行を停止できます。

ページを作成する

ちょっと もの寂しいので、ページを作って返してみます。

Bootstrap4 でなんとなく作ります。

Compiled CSS and JSDownload からファイルを一式(zip)落としてきます。

あと jQuery も必要なので、落としてきます。

Downloading jQueryDownload the compressed, production jQuery 3.6.0 あたりからファイル(js)を落としてきます。

落としたファイル類は、解凍して ローカルの./static に入れます。

あと./templates に ページのテンプレート を作って入れます。

index.tmpl
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{ .Title }}</title>
  <link rel="stylesheet" href="/static/css/bootstrap.min.css">
  <script src="/static/js/jquery-3.6.0.min.js"></script>
  <script src="/static/js/bootstrap.bundle.min.js"></script>
</head>
<body>

<div class="container mt-2">
  <div class="card">
    <div class="card-header">
      Profile
    </div>
    <div class="card-body">
      <h5 class="card-title">hiro</h5>
      <p class="card-text">
        見習いエンジニア Go / Perl など勉強中
      </p>
    </div>
  </div>
  </div>
</body>
</html>

先ほど作成したGoのソースコードをベースに少し変更します。

テンプレートを読み取って、ページを返すようにします。
あとリソースもローカルから返すように変更します。

server.go
package main

import (
    "log"
    "net/http"
    "text/template"
)

func main() {
    http.HandleFunc("/", indexPage)

    // ローカルからファイルを返す
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

    err := http.ListenAndServe(":3000", nil)
    if err != nil {
        log.Fatal(err)
    }
}

func indexPage(w http.ResponseWriter, r *http.Request) {
    // テンプレートを読み取ってページを作成する
    t, err := template.ParseFiles("templates/index.tmpl")
    if err != nil {
        log.Fatalln(err)
    }

    err = t.Execute(w, struct {
        Title string
    }{
        Title: "プロフィール",
    })
    if err != nil {
        log.Println(err)
    }
}

最終的にはこんな構成になるはず。

ファイル構成
./
│  go.mod
│  server.go
│  
├─static
│  ├─css
│  │      bootstrap-grid.css
│  │      bootstrap-grid.css.map
│  │      bootstrap-grid.min.css
│  │      bootstrap-grid.min.css.map
│  │      bootstrap-reboot.css
│  │      bootstrap-reboot.css.map
│  │      bootstrap-reboot.min.css
│  │      bootstrap-reboot.min.css.map
│  │      bootstrap.css
│  │      bootstrap.css.map
│  │      bootstrap.min.css
│  │      bootstrap.min.css.map
│  │      
│  └─js
│          bootstrap.bundle.js
│          bootstrap.bundle.js.map
│          bootstrap.bundle.min.js
│          bootstrap.bundle.min.js.map
│          bootstrap.js
│          bootstrap.js.map
│          bootstrap.min.js
│          bootstrap.min.js.map
│          jquery-3.6.0.min.js
│          
└─templates
        index.tmpl

go build して アクセスしてみましょう。

image.png

ページが表示できました!

この状態でも、
exeファイル と staticフォルダ と templatesフォルダ を持っていけば動くのですが
go:embedでワンバイナリにしてみましょう。

go:embed でファイルを埋め込む

go:embed で指定してあげて、少し変更するだけ 簡単に使用できます!

server.go
package main

import (
    "embed"
    "log"
    "net/http"
    "text/template"
)

//go:embed static
var static embed.FS

//go:embed templates
var templates embed.FS

func main() {
    http.HandleFunc("/", indexPage)

    // http.FS に変更するだけ
    http.Handle("/static/", http.FileServer(http.FS(static)))

    err := http.ListenAndServe(":3000", nil)
    if err != nil {
        log.Fatal(err)
    }
}

func indexPage(w http.ResponseWriter, r *http.Request) {
    // ParseFS に変更するだけ
    t, err := template.ParseFS(templates, "templates/index.tmpl")
    if err != nil {
        log.Fatalln(err)
    }

    err = t.Execute(w, struct {
        Title string
    }{
        Title: "プロフィール",
    })
    if err != nil {
        log.Println(err)
    }
}

//go:embed <ディレクトリ or ファイル>で指定してあげる。
//の後ろは半角スペースあけない!
そしてその直下に変数を定義する。

これだけでFileSystemな変数ができちゃいます。

あとは go buildすれば、ワンバイナリになるので
リソースとなるファイル類がなくても、実行体だけを持って行って実行するだけ。

テンプレート側はソースコード内で指定する必要がありますが、
static側はファイルを置いて、ページから読み込むだけという感じで使えますね!

まとめ

今回初めて go:embed を使用してみましたが、
簡単に使えて便利です!

今回紹介したもの以外にも
いろいろな機会に使用できると思うので、みなさん使ってみてくださいね!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1