こんにちは、バックエンドエンジニアの Kaz です。
前回の記事では、Go 言語でアプリケーションを作る第一歩として「 Hello World 」を行いました。今回は Go 言語を使って「 Web サーバー」を作っていきます。
【はじめてのGo言語】Go言語でアプリケーションを作ってみよう 〜こんにちは、世界編〜
Go 言語には標準で「net / http」という HTTP サーバー用のパッケージが含まれていて、これを使うことで、わずか数行のコードだけで非常に簡単に Web サーバーを立ち上げられるようになっています。
下準備(ディレクトリの作成)
まず今回の下準備として、ディレクトリだけ作っておきます。
適当に $GOPATH/src/localhost/gohttpserver
というディレクトリを作ります。
$ mkdir -p $GOPATH/src/localhost/gohttpserver
$ cd $GOPATH/src/localhost/gohttpserver
次に、このディレクトリ内に main.go
というファイルを作り、Atom で開きましょう。
$ touch main.go
$ atom main.go
サーバーを作って静的ページを配信する
Atom が開いたら、公式ドキュメントにある「静的ファイルサーバー」のサンプルを参考に、静的なページを配信するサーバーを作ってみます。
使うコードはたったこれだけです。
package main
import (
"log"
"net/http"
)
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/etc/gohttpserver/doc"))))
log.Fatal(http.ListenAndServe(":8080", nil))
}
このコードは一体何を意味するのでしょう? main 関数に解説を加えてみました
func main() {
// 指定されたパス (/static/) に対してハンドラーを登録します
http.Handle("/static/",
// URLパスから接頭辞 (/static/) を取り除くミドルウェア的なハンドラーを生成します
http.StripPrefix("/static/",
// URLパスを元に静的ファイルを探して返すハンドラーを生成します
http.FileServer(
// 静的ファイルを配置したディレクトリ (/etc/gohttpserver/doc) を読み込みます
http.Dir("/etc/gohttpserver/doc"),
),
),
)
// ListenAndServe は何かに失敗した時にエラーを返すので、それを記録します。
// エラーが起きなければ ListenAndServe はブロッキングし続けるので log.Fatal は呼ばれません。
// ここの log はデフォルトで標準出力に書き出されるので、内容はターミナルにそのまま表示されます
log.Fatal(
// 指定されたアドレス (:8080) でHTTPサーバーを起動します
// 2つめの引数で nil を渡すことで、上の http.Handle で登録したハンドラーが使われるようになります
http.ListenAndServe(":8080", nil),
)
}
このコードのなかの "/etc/gohttpserver/doc"
の部分(以下、静的ファイルディレクトリ)は、適宜書き換えてください。
次に、静的ファイルディレクトリに適当な内容の「index.html」を配置します。
<!DOCTYPE html>
<html>
<body>
Hello World!
</body>
</html>
たったこれだけですが、必要な作業はすべて整いました。
それでは実際にこのサーバーを立ち上げてみましょう。ターミナルを開いて次のコマンドを実行します。
go run main.go
これで Web サーバーが立ち上がります。Web サーバーを起動しているあいだは処理がブロックされるため、一見ターミナルが動作を停止したように見えるようになりますが、正常な動作なのでご安心ください(停止するには Ctrl+C
を押します)。
次にブラウザを開いて http://localhost:8080/static/
にアクセスしてみましょう。すると、先ほど保存した index.html
が表示されます。これでできあがりです!
動的なコンテンツを配信する
次に、動的な情報を持ったページを返す機能を追加してみましょう。
内容を次のように変更して保存します。
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func clockHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `
<!DOCTYPE html>
<html>
<body>
It's %d o'clock now.
</body>
</html>
`, time.Now().Hour())
}
func main() {
// ハンドラーを登録
http.HandleFunc("/clock", clockHandler)
// 先ほどの静的ファイルハンドラー
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/etc/gohttpserver/doc"))))
// サーバーを起動
log.Fatal(http.ListenAndServe(":8080", nil))
}
保存したら、先ほどと同じく go run main.go
を叩いてサーバーを起動します(立ち上げたサーバーは Ctrl+C
で終了させてください)
次にブラウザを開いて http://localhost:8080/clock
にアクセスしてみましょう。そこで clockHandler
で書いたように時刻が表示されれば成功です!
これで動的なページを生成するアプリケーションは作れるようになりました。ただしソースコード中に HTML コードを混ぜてしまうのは見た目にも美しくありませんし、規模が大きくなってくるとこの方法だと管理も大変です。
そこで、次は「テンプレート」を使って HTML をソースコードから追い出すことにします。
テンプレートを使う
HTML コードをテンプレート化するには、標準パッケージの html/template
を利用します。
main.go
を下記のように書き換えてください。
package main
import (
"html/template"
"log"
"net/http"
"time"
)
func clockHandler(w http.ResponseWriter, r *http.Request) {
// テンプレートをパース
t := template.Must(template.ParseFiles("/etc/gohttpserver/templates/clock.html.tpl"))
// テンプレートを描画
if err := t.ExecuteTemplate(w, "clock.html.tpl", time.Now()); err != nil {
log.Fatal(err)
}
}
func main() {
// ハンドラーを登録
http.HandleFunc("/clock", clockHandler)
// 先ほどの静的ファイルハンドラー
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/etc/gohttpserver/doc"))))
// サーバーを起動
log.Fatal(http.ListenAndServe(":8080", nil))
}
次に /etc/gohttpserver/templates/clock.html.tpl
に次の内容を保存します。
<!DOCTYPE html>
<html>
<body>
It's {{ .Hour }} o'clock now.
</body>
</html>
これを保存し、もう一度 go run main.go
を叩いてサーバーを起動します。先ほどと同じく http://localhost:8080/clock
にアクセスし同じものが表示されていれば完了です!
Go 言語では、基本的にこのようにして Web アプリケーションを作っていくことになります。このように、標準パッケージのサーバー (net/http
) とテンプレート (html/template
) を用いるだけで、静的・動的ページの配信を簡単に実現できてしまいます。
しかし近年の Web アプリケーション開発では、Ajax を使って「最新の情報を動的にロードする」といった使い方も行われるようになりました。これに対応するため、Javascript で扱いやすい「JSON」形式のデータを返す API を作ってみましょう。
JSONを返すAPIを作る
Go言語で JSON を扱うには、標準パッケージの encoding/json
が利用できます。
main.go
を次のように書き換えてみてください。
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
func apiClockHandler(w http.ResponseWriter, r *http.Request) {
// JSONにする構造体
type ResponseBody struct {
Time time.Time `json:"time"`
}
rb := &ResponseBody{
Time: time.Now(),
}
// ヘッダーをセット
w.Header().Set("Content-type", "application/json")
// JSONにエンコードしてレスポンスに書き込む
if err := json.NewEncoder(w).Encode(rb); err != nil {
log.Fatal(err)
}
}
func main() {
// ハンドラーを登録
http.HandleFunc("/api/clock", apiClockHandler)
// サーバーを起動
log.Fatal(http.ListenAndServe(":8080", nil))
}
サーバーの再起動後にブラウザで http://localhost:8080/api/clock
にアクセスすると、下記のような結果が返ってくると思います。
{"time":"2016-01-02T03:04:05.678901234+09:00"}
あとはこの URL を Javascript で叩くことで、取得したデータを煮るなり焼くなり自由に扱うことができるようになります!
さいごに
Go 言語ではこのようにして、標準パッケージを用いるだけで簡単に Web サーバーを立ち上げられるようになっています。
実際の Web アプリケーション開発では、これに加えてデータベースへの接続やデータの取得・保存処理などを行っていくことになります。その詳細なやり方もこの連載内で説明していければと思います!
PHP などで Web サービスを書かれている方も多いかと思いますが、連載第一回目で紹介したように Go 言語は速さなど利点がいっぱいあるので、ぜひこの機会にお試しください。
PHPとGoって何が違うの?LIGが自社サービス開発にGo言語を採用したお話
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。