Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

ここでは「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をブラウザから直接実行するなどで、ご確認ください。

mediado
私たちメディアドゥは、電子書籍を読者に届けるために「テクノロジー」で「出版社」と「電子書店」を繋ぎ、その先にいる作家と読者を繋げる「電子書籍取次」事業を展開しております。業界最多のコンテンツラインナップとともに最新のテクノロジーを駆使した各種ソリューションを出版社や電子書店に提供し、グローバル且つマルチコンテンツ配信プラットフォームを目指しています。
https://mediado.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away