はじめてのGo―シンプルな言語仕様,型システム,並行処理

第4章 標準パッケージ―JSON,ファイル,HTTP,HTMLを扱う

この記事を読むのに必要な時間:およそ 8.5 分

html/templateパッケージ

Goは,text/templateパッケージとhtml/templateパッケージの2つのテンプレートエンジンが付属しており,どちらも同じインタフェースで使用できます。

2つの違いは,html/templateパッケージの場合はHTMLのエスケープ処理が自動で行われる点です。XSSCross-Site Scriptingクロスサイトスクリプティング)などの脆弱性を埋め込みにくくすることができます。したがって,Webの用途ではhtml/templateパッケージを用います。

テンプレートの作成

テンプレートはJinja2というテンプレートエンジンと似た記法で記述します。データを埋め込むには{{ }}で値をくくり,その中に埋め込みたいプロパティを指定します。ここでは,Personの構造体の値を埋め込むテンプレートを,index.htmlに作成します。

<!DOCTYPE html>
<title>person</title>
<h1>{{ .ID }} : {{ .Name }}</h1>

テンプレートのコンパイルはParseFiles()という関数を使います。

t, err := template.ParseFiles("index.html")

ParseFiles()は戻り値としてエラーを一緒に返しますが,Must()を一緒に用いるとエラー時に戻り値ではなくパニックを発生します。一度コンパイルが通ることを確認したテンプレートであれば,毎回エラー処理をする必要性は低いため,Must()を合わせて利用することがよくあります。

var t = template.Must(template.ParseFiles("index.html"))

テンプレートへの値の埋め込み

コンパイルしたテンプレートに実際に値を埋め込むには,Execute()を用います。ここでは第二引数に渡したpersonがテンプレートに適用され,たとえば{{ .ID }}の部分にはperson.IDの値が適用されます。第一引数はio.Writerを渡すと,値を埋め込んだ結果をそこに書き込みます。ここでは結果をレスポンスとして送信するためResponseWriterを直接指定します。

// テンプレートのコンパイル
var t = template.Must(template.ParseFiles("index.html"))

func PersonHandler(w http.ResponseWriter,
    r *http.Request) {
    defer r.Body.Close() // 処理の最後にBodyを閉じる

    if r.Method == "POST" {
        // リクエストボディをJSONに変換
        var person Person
        decoder := json.NewDecoder(r.Body)
        err := decoder.Decode(&person)
        if err != nil { // エラー処理
            log.Fatal(err)
        }

        // ファイル名を{id}.txtとする
        filename := fmt.Sprintf("%d.txt", person.ID)
        file, err := os.Create(filename) // ファイルを生成
        if err != nil {
            log.Fatal(err)
        }
        defer file.Close()

        // ファイルにNameを書き込む
        _, err = file.WriteString(person.Name)
        if err != nil {
            log.Fatal(err)
        }

        // レスポンスとしてステータスコード201を送信
        w.WriteHeader(http.StatusCreated)
} else if r.Method == "GET" {
        // パラメータを取得
        id, err := strconv.Atoi(r.URL.Query().Get("id"))
        if err != nil {
            log.Fatal(err)
        }

        filename := fmt.Sprintf("%d.txt", id)
        b, err := ioutil.ReadFile(filename)
        if err != nil {
            log.Fatal(err)
        }

        // personを生成
        person := Person{
            ID: id,
            Name: string(b),
        }

        // レスポンスにエンコーディングしたHTMLを書き込む
        t.Execute(w, person)
    }
}

サーバを起動したら,ブラウザからhttp://localhost:3000/persons?id=1にアクセスすると,結果が表示されます図2・注2⁠。

図2 html-server

図2 html-server

注2)
先にPOST を実行するか,手動で1.txt を生成している必要があります。

まとめ

本章では,よく使う標準パッケージを用いてHTTPサーバを作成する方法を解説しました。特にioパッケージのインタフェースの扱いは,ほかのパッケージを用いる際にも必要となる場合があるため,覚えておくとよいでしょう。

著者プロフィール