レスポンスを返す
読了目安 約3分
ステータスコードとヘッダーの書き方を押さえ、JSON を返す API と html/template で HTML を返すページを実装します。
この章の目次
レスポンスの 3 要素、ステータスコード、ヘッダー、ボディを制御して、JSON の API と HTML のページを作ります。
ステータスコードとヘッダーを書く
http.ResponseWriter には、ボディの書き込みのほかに 2 つの操作があります。
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusCreated) // 201
fmt.Fprintln(w, "作成しました")w.Header().Set はヘッダーを設定します。
Content-Type はボディの形式を伝えるヘッダーで、ブラウザは描画方法をこれで決めます。
w.WriteHeader はステータスコードを指定します。
呼ばずにボディを書き始めると、自動で 200 OK になります。
WriteHeader の瞬間にステータス行とヘッダーが送信されて取り消せないため、「ヘッダー、ステータス、ボディ」の順で書きます。
リクエストは正しいのに処理が失敗したときは、500 Internal Server Error です。
http.Error(w, "サーバー内部でエラーが発生しました", http.StatusInternalServerError)JSON を返す API と HTML を返すページ
次を main.go に保存してください。
package main
import (
"encoding/json"
"html/template"
"log"
"net/http"
)
type Post struct {
ID int `json:"id"`
Body string `json:"body"`
}
var tmpl = template.Must(template.New("page").Parse(`<!DOCTYPE html>
<html>
<body>
<h1>{{.Title}}</h1>
<p>メッセージ: {{.Message}}</p>
</body>
</html>
`))
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/posts", func(w http.ResponseWriter, r *http.Request) {
posts := []Post{
{ID: 1, Body: "最初の投稿"},
{ID: 2, Body: "二つ目の投稿"},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(posts)
})
mux.HandleFunc("GET /page", func(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Message string
}{
Title: "ねじき教室",
Message: r.URL.Query().Get("msg"),
}
tmpl.Execute(w, data)
})
log.Fatal(http.ListenAndServe(":8080", mux))
}go run main.go で起動し、まず API を叩きます。
curl -i http://localhost:8080/api/postsHTTP/1.1 200 OK
Content-Type: application/json
...
[{"id":1,"body":"最初の投稿"},{"id":2,"body":"二つ目の投稿"}]HTML と JSON で読んだ JSON を、今度は生成する側です。
struct を JSON に変換する
json.NewEncoder(w).Encode(posts) は、Go の値を JSON に変換して w に書き込みます(前章の Decode の逆)。
キー名を決めるのが、フィールド定義の後ろの struct タグです。
ID int `json:"id"`タグがなければフィールド名("ID")がそのままキーになります。
Go は公開フィールドが大文字始まり、JSON のキーは小文字が通例で、タグがその橋渡しです。
テンプレートで HTML を組み立てる
ブラウザで http://localhost:8080/page?msg=こんにちは を開くと、「メッセージ: こんにちは」を含む HTML が表示されます。
テンプレートは、穴あきの雛形に値を埋め込む仕組みです。
{{.Title}} のような穴を含む雛形を template.Must(...Parse(...)) で用意し、tmpl.Execute(w, data) を呼ぶと、穴に data の値を埋めた結果が w に書き出されます。
穴とデータの対応は名前で決まるので、Execute には Title と Message を持つ無名 struct を渡しています。
template.Mustは、雛形の構文が壊れていたら起動時に panic させる包み紙です。
text/template ではなく html/template を使う理由
兄弟 text/template ではなく html/template を選ぶ理由は、エスケープ(記号を無害な表記への変換)が自動だからです。
msg の値(前章の「信用できない値」)が、そのまま HTML に埋め込まれます。
次を叩いてみてください。
curl 'http://localhost:8080/page?msg=<script>alert(1)</script>'出力では < が < に変換されており、ブラウザはただの文字として表示するだけです。
text/template なら <script> がそのまま出力され、閲覧者のブラウザで他人のスクリプトが動いてしまいます。
これが代表的な攻撃 XSS(クロスサイトスクリプティング)です。
「HTML は html/template で出す」を機械的に守るだけで、大部分を防げます。