Go
ハンズオン
TechDo

Goハンズオン 第3回 課題内容

ここでは「Goハンズオン 第三回」で実施する、皆さんに解いていただく課題内容を記載いたします。


課題メニュー


  1. HTMLテンプレートを使って、ページを出力するサーバを作ってみよう

  2. JSONを返却する、簡単なAPIを作ってみよう

  3. Webフレームワーク「echo」を使って実装をしてみよう

  4. 出力したHTMLからAPIを実行して、結果を画面に出力しよう


前提条件



  • go getgo run 等々の基本的なコマンドの知識がある事とします

  • 課題のプログラムは、環境変数 GOPATH 配下に作成します

  • 開発ツールはVSCodeとします(その他、各自個別のものの場合、それぞれ専用でのサポートが難しいため)

  • go moduleを使用するため、Goのバージョンは1.11以上とします


準備


Go 1.11以上をインストールしている場合

go moduleを使います。


go moduleの設定

プロジェクト別に依存パッケージを管理するには、今までdepなどの外部ツールを使う必要がありましたが、Go 1.11からはgo moduleという機能が追加され、標準の機能でも依存関係の管理ができるようになりました。せっかくなので使ってみましょう。

適当な作業ディレクトリの直下で、下記を実行します。

$ go mod init hello

下記のようなファイルが、カレントディレクトリに作成されます。


go.mod

module hello


この状態で go rungo build すると、プログラム内に外部パッケージがあれば、go.modへ依存関係が追記されていき(あわせてgo.sumというファイルも出来上がります)、パッケージを実際に使うこともできます。go.mod、go.sumに依存パッケージのバージョンが記録されるため、これらのファイルがあれば、指定のバージョンをプロジェクトごとに使うことができるという仕組みです。


もしも、Go 1.11未満をインストールしている場合

GOPATH 配下で作業します。


作業ディレクトリの作成

GOPATH 配下の任意の場所に、作業用のディレクトリを作成してください。 GOPATH を明示的に設定していない場合は、ユーザホームディレクトリ配下の go ディレクトリとなります。Macであれば、 $HOME/go 、などです。


課題1: HTMLテンプレートを使って、ページを出力するサーバを作ってみよう

Go標準機能のHTMLテンプレートを使用して、HTMLを返却するサーバを作成します。


やること


  • HTMLテンプレートの作成

  • Webサーバの実装

  • Webサーバの実行、HTML確認


HTMLテンプレートの作成

{{.〜}} はWebサーバが出力する値を表示します。構造体のフィールドを指定することで、対象の値を取得することができます。


hello.html.tpl

<!DOCTYPE html>

<html>
<body>
<h1>{{.Title}} </h1>
<p>{{.Text}}</p>
</body>
</html>


Webサーバの実装

http://localhost:8080/hello でHello, worldページが表示されるようにします。ここはWebサーバを作ってみる、お試しなので、実装を全て記載しています。


  1. ページの描画に使用する値を持つ、構造体を作成

  2. 該当のページを描画するためのAPI定義(ハンドラ)を作成


    1. ハンドラ内で、構造体の初期化、HTMLテンプレートの描画を実行する



  3. サーバを起動し、ハンドラを実行するメインの処理を作成


main.go

package main

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

type Message struct {
Title string
Text string
}

func handleHello(w http.ResponseWriter, r *http.Request) {
// テンプレートをパース
t := template.Must(template.ParseFiles("./hello.html.tpl"))
// テンプレートに渡す値を作成
m := &Message{
Title: "Hello ,world",
Text: "hogehoge",
}
// テンプレートを描画
if err := t.ExecuteTemplate(w, "hello.html.tpl", m); err != nil {
log.Fatal(err)
}
}

func main() {
// パスにAPIを登録
http.HandleFunc("/hello", handleHello)
// 8080ポートを開放して、サーバを起動
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}



Webサーバの実行、HTML確認

プログラムを実行します。

$ go run main.go

ブラウザで http://localhost:8080/hello へアクセスすると下記画像のように表示されます。

hello.png


課題2: JSONを返却する、簡単なAPIを作ってみよう

Go標準機能を引き続き使用して、JSONを返却するサーバを作成します。この項目から、ソースコード全体ではなく、穴埋め形式のようにしていきます。


やること


  • Webサーバの実装追加

  • Webサーバの実行、レスポンス確認


Webサーバの実装追加


ハンドラの登録

下記のコードを追加して、APIのハンドラを登録してください。パスは /hello_api とします。


main.go

// ロジック実行用コード

func handleHelloAPI(w http.ResponseWriter, r *http.Request) {
}

// 省略

// ハンドラ登録用コード
http.HandleFunc("/hello_api", handleHelloAPI)



JSONを返却するコードの実装

handleHelloAPIメソッドの中身を実装します。下記のスケルトンに沿って実装してください。


main.go

func handleHelloAPI(w http.ResponseWriter, r *http.Request) {

// 構造体Messageを使って、渡す値を作成(値は何でも良い)

// json.Marshal()メソッドに作成した構造体を渡し、JSONボディを取得
// (encoding/jsonのimportが必要です)

// err検知、ログ出し
if err != nil {
log.Fatal(err)
}

// w.Write()に作成したJSONを渡して、レスポンスする
}



Webサーバの実行、レスポンス確認

プログラムを実行します。

$ go run main.go

ブラウザで http://localhost:8080/hello_api へアクセスすると下記のように表示されます。

{"Title":"Hello ,world","Text":"hogehoge"}


課題3: Webフレームワーク「echo」を使って実装をしてみよう

課題1、課題2で実装したものを、Webフレームワークである、echoを使って実装し直します。ライブラリの所在は、github.com/labstack/echoです。

echoは軽量なフレームワークで、シンプルさとハイパフォーマンスが特徴です。競合するフレームワークではGinなどがあります。今回のハンズオンでは、シンプルさを重視してechoを選択しました。


やること


  • echoの取得

  • echoを使ってサーバを起動する

  • echoでHTMLテンプレートの出力を実装する、動作確認する

  • echoでJSONを返却するAPIを実装する、動作確認する


echoの取得


もしも、go moduleを使っていない場合

go get してください。

$ go get github.com/labstack/echo


go moduleを使っている場合

echoをimportに追記し、 go run するとmoduleにより追加されます。


echoを使ってサーバを起動する

下記のコードにより、サーバを起動できます。


main.go

package main

import (
"github.com/labstack/echo"
)

func main() {
e := echo.New()
// e.Start()するまでに、ここで作成したechoに対して、機能づけしていく
e.Logger.Fatal(e.Start(":8080"))
}


実行すると、下記のようにechoによるサーバが起動します。

$ go run main.go

____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.0.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:8080


echoでHTMLテンプレートの出力を実装する、動作確認する

公式のサンプルに沿ってやってみましょう。

公式の参考資料:https://echo.labstack.com/guide/templates

下記がechoでHTMLテンプレートを使うために必要な実装です。参考にしつつ、処理確認まで進めましょう。


HTMLテンプレートを作成する

echoではHTMLテンプレートを下記のように定義する必要があります。課題1で作成したテンプレートファイルを流用しつつ、echo用に変更して下さい。


hello.html.tpl

{{define "hello"}}

〜ここにHTMLテンプレートの内容を記述〜
{{end}}

{{define "hello"}} でテンプレートファイルに名前をつけます。後の工程でこの名前を使用します。{{end}} でテンプレートの終了を定義します。これらの設定が無いと、プログラムエラーとなります。


echoにHTMLテンプレートエンジンを組み込む

下記のコードにより、echoでHTMLテンプレートを使用します。まずは、レンダリング処理の定義をします。この定義を、echoに渡します。


main.go

type Template struct {

templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}


e := echo.New() で作成したechoに対して、下記のように上記の定義を設定します。


main.go

// 前処理

t := &Template{
templates: template.Must(template.ParseGlob("./*.tpl")),
}
e.Renderer = t
// 後処理


HTMLテンプレートを表示するハンドラを登録する

ロジックは課題1の内容と同じものを作成してみましょう。


main.go

// パスの登録

// 前処理
e.GET("/hello", Hello)
// 後処理

// ロジックの実装
type Message struct {
Title string
Text string
}

func Hello(c echo.Context) error {
m := &Message{
Title: "Hello ,world",
Text: "hogehoge",
}
return c.Render(http.StatusOK, "hello", m)
}


c.Render() に渡しているhelloという文字列が、HTMLテンプレート作成時につけた、名前となります。


動作確認

http://localhost:8080/hello にアクセスすると、課題1の内容と同じ出力がされます。


echoでJSONを返却するAPIを実装する、動作確認する

課題2で作成したAPIと同じものを、echoを使って実装します。レスポンス形式をJSONにする必要があるため、その部分を実装しつつ、動作確認まで進めましょう。

公式の参考資料(レスポンス作成):https://echo.labstack.com/guide/response


JSONレスポンスの作成

c.JSON() メソッドを通すことで、構造体をJSONにしてレスポンスすることができます。


main.go

func HelloAPI(c echo.Context) error {

m := &Message{
Title: "Hello ,world",
Text: "hogehoge",
}
return c.JSON(http.StatusOK, m)
}

また、上記資料の「Send JSON」項目で、構造体のフィールドに対して、JSON変換時の項目名をしていする方法が書かれています。ここでは、Message構造体の、Titleをtitle_nameに、Textをtext_messageに変更してみましょう。


main.go

type Message struct {

Title string // ここにJSON変換時の項目名を指定
Text string // ここにJSON変換時の項目名を指定
}


APIを実行するハンドラを登録する

作成した関数を設定します。


main.go

e.GET("/hello_api", HelloAPI)



動作確認

http://localhost:8080/hello_api へアクセスすると、下記のようにJSONが返却されます。

{"title_name":"Hello ,world","text_message":"hogehoge"}


課題4: 出力したHTMLからAPIを実行して、結果を画面に出力しよう

出力されたHTMLから、APIを実行し、結果をHTML上に表示してみましょう。HTMLテンプレートに下記を追記します。ボタンをクリックすると、APIが実行され、結果が#resultのdivタグへ結果が追記されます。取得するJSONのフィールド名は、上記で構造体に設定したものとなっています。


hello.html.tpl

前略〜

<button onClick="kickApi()">
実行
</button>

<div id="result">
</div>

中略〜

<script>
function kickApi() {
const url = '/hello_api';
fetch(url).then(function(response) {
return response.json();
}).then(function(json) {
var result = document.querySelector('#result');
var titleName = json.title_name;
var textMessage = json.text_message;
result.innerHTML = titleName + ", " + textMessage;
});
}
</script>

後略〜


実行すると下記のように表示されます。

kickApi.png

以上により、GoによるHTMLの出力と、APIの実行までができました。此処から先の課題では、これらのやり方を元に、各々でロジックを組み立てていただきます。例としては、四則演算を用意しています。


チャレンジ課題: ソースの変更を自動ビルドして、開発を楽にしよう

ここまでの作業で、Goのソースを書いて、サーバを起動する際、都度プロセスの終了と、 go run を相殺していたかと思います。そこで、ソースの変更を検知して、自動反映してくれるパッケージがあるので、使ってみましょう。こういった、ホットデプロイを提供するパッケージはいくつかありますが、今回は使い方がシンプルなFreshを使います。


Freshの取得

$ go get github.com/pilu/fresh


Freshの実行

go run の代わりに、Freshを実行するような形です。 GOPATH はデフォルトの、ユーザホームディレクトリ配下を想定しています。GOPATH を変えている場合はパスを適宜変更してください。

# カレントディレクトリを実行したいパッケージの配下へ移動

$ cd <作業ディレクトリ>
# freshを実行
$ $HOME/go/bin/fresh

実行すると、下記のような形で出力されます。

18:51:24 runner      | InitFolders

18:51:24 runner | mkdir ./tmp
18:51:24 watcher | Watching .
18:51:24 main | Waiting (loop 1)...
18:51:24 main | receiving first event /
18:51:24 main | sleeping for 600 milliseconds
18:51:25 main | flushing events
18:51:25 main | Started! (5 Goroutines)
18:51:25 main | remove tmp/runner-build-errors.log: no such file or directory
18:51:25 build | Building...
18:51:27 runner | Running...
18:51:27 main | --------------------
18:51:27 main | Waiting (loop 2)...
18:51:27 app |
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.0.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
18:51:27 app | ⇨ http server started on [::]:8080

Freshを使って起動している状態でソースコードを変更し、保存すると、そのたびに自動でビルドされ、上記のようなログが都度追記されます。

go moduleとの共存ですが、問題なく可能です。Freshでは内部で go build コマンドを実行しており、freshコマンドを通して実行していても、buildの際にgo moduleが使用されます。


課題5: 四則演算のAPIを作ってみよう

下記の仕様でAPIを作成し、HTML上のフォームから実行してみましょう。


足し算API

パス:/plus

リクエストパラメータ:first(整数、足し算の対象), second(整数、足し算の対象)

出力形式:JSON

出力内容:リクエストパラメータのfirst, secondの値を足したものが、フィールド名resultで返却される


インプット例

http://localhost:8080/plus?first=1&second=2


アウトプット例

{"result":3}


HTMLからの実行

フォームでfirstとsecondの2つの値を受け取り、上記のAPIを実行する。出力結果をHTMLに追記する。フォームの値を変えることで、任意の数字で足し算を実行することができる。

HTMLからの実行は、Javascript等の実装も含みますので、詰まる場合はAPIをブラウザから直接実行するなどで、ご確認ください。