初めに
GoとAngularでWebアプリケーションを作成する
初めての取り組みで実装しつつなので投稿自体は何回かに分けて行う
またGoでWebアプリケーション開発の場合、ginやgorillaとかのすでにあるフレームワークを利用することを考えるべきだと思うが、勉強もかねて独自に実装する
目的
今回はAngularの初期表示画面をGoのWebサーバ経由で表示するまで
ng newして作成したAngularプロジェクトをng serveではなくGOのWebサーバ経由で表示する
環境構築
Angular CLI: 8.0.1
go version go1.12.5 windows/amd64
Node.jsとかは既にインストール済みの想定
GoはモジュールモードでGOPATH以外の場所にプロジェクトを作成する
git init //適当なディレクトリで
go mod init github.com/ponta-dev/angular-go
必要に応じてgitignoreとかも作っておく
プロジェクトの階層は下記を想定
root
|-server //GOのサーバ側実装(github.com/ponta-dev/angular-go/serverパッケージ)
|-template //Angularのビルド結果のjsやindex.htmlが出力される場所
|-ui //Angularのプロジェクト
go.mod
.gitignore
Angularプロジェクトの作成
ng new ui
cd ui //以下起動確認
ng serve --open
Goのビルド確認
server/mainディレクトリ以下にmain.goとmain_test.goを適当に作る
内容は実行確認なのでfmt.Println("hello world")とかでよい
go run ./server/main/main.go //GOの実行確認
go test -v github.com/ponta-dev/angular-go/server/main //テストの実行確認
ここまでがGoとAngularを同じディレクトリ内で実行が確認できるところまで
ビルド設定
Angularの設定を変更する
変更したい設定は次の2つ
- ビルド成果物の出力先の変更
- ベースURLの設定
1つ目はng buildでビルドした際に成果物の出力先がデフォルトだとAngularプロジェクト内になっていてGoからみると若干気持ち悪いのでプロジェクトルート以下のtemplateディレクトリに出力先を変更する
2つ目はGOのWebサーバに対してhttp://localhost:8080/angular-go をホームURLにするため、ベースURL(/angular-go/)の設定を行う
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "../template", //出力先の変更
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"baseHref": "/angular-go/" //ベースURLの設定
},
ベースURLの設定はng buildだけでなくng serveの方でもしておく
baseHrefの設定は"angular-go"とかだとうまく設定できないので注意("/angular-go/")
Go側の実装
GoのWebサーバはnet/httpパッケージを用いて実装する
特定のURLに対するHTTPリクエストに対して、ハンドラを登録しておき何らかのサーバ処理を行う
今回の範囲だとホームURLに対するリクエストに対して、templateディレクトリに出力したindex.htmlを返せばよさそう
ハンドラはhttp.Handlerインタフェースの実装であり、ServeHTTP(ResponseWriter, *Request)を実装していればなんでもOK
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ハンドラのServeHTTP内で下記のような実装をすると、index.htmlを返却することができる
//index.htmlテンプレートの取得
index := template.Must(template.ParseFiles(HTMLTemplatePath + "/" + IndexHTML)) // "template/index.html"
//index.htmlの書き込み
if err := index.Execute(w, nil); err != nil {
log.Fatal("index.html Write Error", err)
}
template.Mustはhtml/templateパッケージの*template.Templateを返却するメソッドに対するラッパー関数で、index.htmlの取得の際にエラーが起きた場合パニックを起こす(パスが違うとか)
上記のハンドラをホームURLに対して登録することでAngularで作成したindex.htmlを返却するGoのWebサーバができる
リソースアクセス
index.htmlを返却することはできたが、このままだとhttp://localhost:8080/angular-go にアクセスしてもAngularのUIを表示することはできない
これはindex.htmlが参照している~~.jsや~~.icoなどのリソースファイルの取得ができないためで、GoのWebサーバに対してこれらのリソースに対するアクセスを許可してやる必要がある
ただし、リソースファイルのアクセスURLは/angular-go/~~.jsとかなので、このままだと/angular-go/のホームURLに対するハンドラが発火してしまう
かといってリソースに対するアクセスすべてに対してハンドラを登録するのはナンセンスな気がするので、上手いことしてやる必要がある
これに対する解法はいくつかありそうで、筆者がググった限りで下記の記事とかが出てきた
- Getting Started With Angular and Go :
https://medium.com/@anshap1719/getting-started-with-angular-and-go-setting-up-a-boilerplate-project-8c273b81aa6 - AngularのプロジェクトをビルドしてGolangで動かす:
https://mslgt.hatenablog.com/entry/2017/04/04/014906
今回筆者の方では上記の方法とは別の方法でリソースアクセスを許可する
※実装が汚かったり、すべきでない実装の可能性もあるのでその辺はご容赦ください
まず、リソースファイルへのアクセスはすべてホームURLのハンドラで処理する
Goのregexpパッケージの正規表現を用いてリクエストのパスを検査し、~~.js等のリソースファイルと、単にホームURLへのアクセスを切り分ける
ホームURLへのアクセスの場合は上記のハンドラでindex.htmlを返し、リソースファイルへのアクセスであればファイルサーバとしてリソースファイルを返すようにする
regexpによる正規表現の判定は下記のような感じ
//constで下記のような定数を定義し
ResourceRegexp = `.*\.js$|.*\.ico$|.*\.js\.map$`
// *regexp.Regexp構造体の取得
ResourceRegexp := regexp.MustCompile(ResourceRegexp)
if ResourceRegexp.MatchString(r.URL.Path) {
// リソースファイルに対する処理
}else {
// index.htmlの返却
}
Goにおけるファイルサーバは、http.FileServerによる実装がベター
定義したディレクトリ以外へのアクセスを禁止するはずなので、セキュリティの観点から推奨されている
このhttp.FileServerの戻り値はhttp.Handlerインタフェースの実装であるため、ServeHTTPを実行してやればファイルサーバとしてのハンドラを実行できる
// http.StripPrefixでベースURL(/angular-go/を除外する
// http.FileServer(http.Dir(HTMLTemplatePath))でファイルサーバのハンドラを取得
fileServer := http.StripPrefix(HomePrefix, http.FileServer(http.Dir(HTMLTemplatePath)))
// ServeHTTP
// 自前のServeHTTPの引数のリクエストとレスポンスをそのまま受け渡す
if ResourceRegexp.MatchString(r.URL.Path) {
//FileServerのServeHTTPをキック
fileServer.ServeHTTP(w, r)
}
以上のハンドラを実装することで、index.htmlが参照しているリソースファイルに対してもGoのWebサーバが適切に動作するようになる
次回予定
今回はAngularのHello WorldがGoのWebアプリケーションで表示できるところまでを紹介した
次回は下記のどれかを予定している
- PostgreSQLとGoサーバーの通信
- GoサーバをRestのWebAPI化