とーますメモ

Ruby on Rails / Goなどの学習メモ

【Go】Echo × レイヤー管理のWebアプリをgo-bindataでまとめて、サーバ上で常駐化させてみようとしたが・・・

ちなみにかなり時間を割いて調べたけど、
自分が欲しい情報が得られず、トライアンドエラーを繰り返すも
全くうまくいかず、やっとこそなんとか常駐化まで出来た内容をメモ書き。

【前提】
Echoフレームワークを使用し、常駐化にはSupervisorを使用する。
Supervisorの使い方はここでも紹介している。
thoames.hatenadiary.jp

自分がGoを使ってみようと思ったきっかけの一つに、
デプロイがシングルバイナリひとつで簡単にできるという理由があるが
これは静的ファイルを含むWebアプリでは当てはまらないということに
デプロイを試行錯誤している段階で気づいた。

これはあくまでも.goファイルのみで作成されたアプリが対象になるっぽい。

また、自分が調べた感じでは、
静的ファイル(css, javascript, 画像等)を含めたGoのWebアプリを
サーバ上で常駐化させているサンプル例があまりないため、
どうしてもGoでWebアプリを作成する際は
APIサーバとして使用する前提(静的ファイルを含まない)で作成し、
静的ファイルは別に管理する方式の方が良い気がする。
(他に良い方法があるようでしたら、コメントくださいませ。)

では以下にどうやって常駐化まで対応したかを書く。

静的ファイルを含めたシングルバイナリを作成する方法として
メジャーっぽいのは「go-bindata」を使用する方法になると思う。
このgo-bindataとechoに関連する情報を調べると大体が以下のQiitaページが引っかかると思う。

qiita.com

Echo+Go関連の情報が少ない中、非常にありがたい情報ではあるが、
サンプルがシンプル過ぎて、レイヤー管理を行うための
「html/templete」パッケージを使用する方法にはマッチしない。

ここでいうレイヤー管理とは、Railsみたいな、layout viewが大枠にあって、
その中でコンテンツや部分テンプレートを
自由に呼び出す方法のこと。
小規模ならいいかもだけど、中規模以上だと、こういう仕組みの上で作らないと
管理が大変になる。

ディレクトリ構成は大事な箇所だけ抜き出すとこんな感じ。

src
┣ main.go
┣ /assets
┃ ┣ /js
┃ ┣ /css
┃ ┗ /images

┗ /views
  ┣ layout.html
  ┣ header.html
  ┗ index.html

「html/template」パッケージを使用し、こういったレイヤー構成の
アプリを作成する場合、色んなサイト様で掲載されている方法としては以下のように書くことである。

template.Must(template.ParseFiles("views/layout.html", "views/header.html", "views/index.html"))

layout.html

{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title></title>
  <link href="/static/css/base.css" rel="stylesheet">
  <script src="/static/js/base.js"></script>
</head>
<body>
{{template "header" .}}

{{template "content" .}}
</body>
</html>
{{end}}

header.html

{{define "header"}}
<nav>
  ...
</nav>

</div>
{{end}}

index.html

{{define "content"}}
<div>
  <h1>Hello World!</h1>
</div>
{{end}}

上記のhtmlファイル群をまずはシングルバイナリに含ませるため
go-bindataを用いて以下のようにする。

$ go-bindata -o bindata.go ./views/...

※ サブパッケージに含ませる場合には、-pkgを使用する。
例)routerパッケージに含ませる場合。

$ go-bindata -pkg router -o router/bindata.go ./views/...

その後以下のヘルパー関数を作成し、ParseFiles関数の代わりに使用する。

func ParseAssets(filenames ...string) (*template.Template, error) {
  var ns = template.New("complex")

  for _, filename := range filenames {

    src, err := Asset(filename)
    if err != nil {
      return nil, err
    }

    s := string(src)
    name := filepath.Base(filename)

    _, err = ns.New(name).Parse(s)
    if err != nil {
      return nil, err
    }
  }

  return ns, nil
}
template.Must(ParseAssets("views/layout.html", "views/header.html", "views/index.html"))

これで、viewsとして使用するHTML郡はシングルバイナリとして含まれる。
しかし、次が問題でこれらのHTML郡の中で使用するcssやjsなどの静的ファイルを
以下のQiitaのやり方に沿ってみても、上手く読み込まない。
※正しくは、読み込んでるっぽいんだけど、ファイルの先頭に{message: "Not Found"}が入るため
読み込めない。なぜこうなるかは不明ではまったので、この方法は諦めた。
echo 初心者でも簡単!! echo で扱うアセットファイル群を簡単にバイナリにまとめて使ってみる - Qiita

なので、もう他にどうしようもなかったので
絶対パスで読み込むことにした。苦笑

以下の設定を行うことで、HTML内で「/static」と始まるパスは
assetsディレクトリ内を参照するようになる。

e := echo.New()

current_working_dir := "サーバ上の絶対パス"
e.Static("/static", filepath.Join(current_working_dir, "assets"))

これでビルドし、バイナリとassetsディレクトリをサーバにデプロイし
バイナリのパスをSupervisorに設定し、起動させれば常駐化してくれる。

以上。疲れた。。。


[参考]
アセット的なアレを実行バイナリ内に入れる話。
src/html/template/template.go - The Go Programming Language
echo 初心者でも簡単!! echo で扱うアセットファイル群を簡単にバイナリにまとめて使ってみる - Qiita