vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

AngularのプロジェクトをビルドしてGolangで動かす

はじめに

前回に引き続きGolangのお話。

以前作成したAngularのプロジェクトをビルドして、 Golangを使って表示してみます。

Angularのプロジェクトをビルドする

実は最近のJS系の勉強会などに参加していて気になっていたことがあります。

「本番ではどうやって表示するの?」

例えばバックエンドをSpring Bootで構築したとして、
フレームワークを使わずに作った場合はSpring Bootのプロジェクトに組み込んで、
Thymeleafなどを使って表示するかと思います。

しかし、例えばAngularの場合だとAngular-cliを使って、
Angularのプロジェクト単体で表示します(確認の段階では)。

では、それぞれを完全に別に扱うの…?

などなど考えていたのですが、実際にはAngularのプロジェクトをビルドすることで、
上の例であればSpring Bootのプロジェクトに組み込むことができる、ということでした。

ビルド自体は(とりあえずビルドするだけなら)簡単で、Angularのプロジェクト直下で下記を実行するだけです。

ng build

ビルドに成功すればプロジェクト直下に「dist」というフォルダが作成され、
必要なファイルがその中に出力されます。

あとはバックエンド側のプロジェクトにまとめて放り込んで、
「index.html」を表示してあげればOKです。

今回はGolangのファイル(webtest.go)と同じディレクトリに「templates」というディレクトリを作り、
その中に全てファイルを保存しました。

HTMLファイルを読み込む

何故か我が家にある(買った)Go言語によるWebアプリケーション開発で扱われているコードを参考にHTMLを表示してみます。

https://github.com/matryer/goblueprints/blob/master/chapter1/chat/main.go

webtest.go

package main

import (
    "html/template"
    "log"
    "net/http"
    "path/filepath"
    "sync"
)

type templateHandler struct {
    once         sync.Once
    filename     string
    viewTemplate *template.Template
}

func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t.once.Do(func() {
        t.viewTemplate = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
    })
    t.viewTemplate.Execute(w, nil)
}
func main() {
    http.Handle("/", &templateHandler{filename: "index.html"})

    // Portを指定してWebサーバーを開始.
    if err := http.ListenAndServe(":8088", nil); err != nil {
        // エラーが発生すればログ出力.
        log.Fatal("ListenAndServe:", err)
    }
}

http.Handle, ServeHTTP

ファイル読み込みのキーになる部分がhttp.Handle、ServeHTTPです。

http.Handle

第一引数に対象とするURL(上記ではルートディレクトリ)を、
第二引数にhttp.Handlerという関数ServeHTTPを持つインターフェースを取ります。

で、対象URLにアクセスした時、Spring bootでいう「@RequestMapping」のように、
第二引数から関数ServeHTTPが実行され、「index.html」が表示される、という流れになっています。

ServeHTTP

ここで気になるところとして、上記では第二引数としてhttp.Handlerではなく、構造体を渡しています。

これがエラーにならないのは、
Golangでは構造体があるインターフェース(ここではhttp.Handler)が持つ関数を全て持っている場合、
そのインターフェースと同じものとして扱うことができるためです。

なお構造体に関数を持たせているのがServeHTTPの「(t *templateHandler)」の部分です。

便利ですが、なかなか慣れないとナンノコッチャ分からんですね。。。(;・∀・)

その他の詳しい解説は本を参照。

…なのですが、この状態ではLoading…の文字しか表示されません。
index.htmlから呼び出しているJavascriptにアクセスできないためです。

また同じ理由でFaviconも表示できません。

静的ファイルを読み込む

これらのファイルにアクセスできるようにするためにはhttp.Handleを使います。

webtest.go

// Javascriptのファイルを読み込む.
http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("templates"))))
// 画像ファイルを読み込む.
http.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("templates"))))

そして、ng buildで出力したindex.htmlを少し変更します。

MainPage.html

< !doctype html>
< html>
< head>
  < meta charset="utf-8">
  < title>NgCustomer< /title>
  < base href="/">
  < meta name="viewport" content="width=device-width, initial-scale=1">
  < link rel="icon" type="image/x-icon" href="img/favicon.ico">
< /head>
< body>
  < app-root>Loading...< /app-root>
  < script type="text/javascript" src="js/inline.bundle.js">< /script>
  < script type="text/javascript" src="js/polyfills.bundle.js">< /script>
  < script type="text/javascript" src="js/styles.bundle.js">< /script>
  < script type="text/javascript" src="js/vendor.bundle.js">< /script>
  < script type="text/javascript" src="js/main.bundle.js">< /script>
< /body>
< /html>

変更の理由は、http.Handleの第一引数で指定するURLが、index.htmlと重複するのを避けるためです。

第二引数「http.StripPrefix(“/js/”, http.FileServer(http.Dir(“templates”)))」は、
(例えば)アクセスされるURL「/js/inline.bundle.js」から「/js/」の部分を取り除き、
templatesディレクトリ内にあるinline.bundle.jsを取得します。

とりあえず、これでAngularのファイルを表示できるようになりました。
まだ怪しい要素もありますが、抜けなどがあれば追加・修正します。

他からアクセスできるようにする

確認の際は「go run webtest.go」を実行していますが、
例の如く?他のマシンからこのページにアクセスすることはできません。

どうするかというと、これも例によってオプションを追加します。

go run webtest.go -serve 0.0.0.0

参照

Angular

Golang