[翻訳]Go言語のWebアプリケーションの書き方(原文: Writing Web Applications)

## この記事について

この記事はGoでWebアプリを作るための公式チュートリアル「[Writing Web Applications](https://go.dev/doc/articles/wiki/)」の翻訳記事になります(2022/07/26最終更新)。

## 目次
- イントロダクション
- 始めよう
- データ構造
- `net/http`パッケージの紹介
- wikiのページを配信するために`net/http`を使用する
- ページの編集
- `html/template`パッケージ
- 存在しないページの扱い
- ページの保存
- エラーハンドリング
- テンプレートキャッシング
- バリデーション
- 関数リテラルとクロージャの紹介
- 試してみる
- 他の課題

## イントロダクション
このチュートリアルで扱っていること:
- loadとsaveメソッドを使ったデータ構造の作成
- webアプリケーションを構築するためのnet/httpパッケージの使用
- HTMLテンプレートを処理するためのhtml/templateパッケージの使用
- ユーザの入力を検証するためのregexpパッケージの使用
- クロージャの使用

想定する前提知識: 
- プログラミングの経験
- 基本的なweb技術(HTTP, HTML)を理解していること
- コマンドラインの知識(UNIX/DOS)

## 始めよう
現在、Go言語を実行するためにはFreeBSD、Linux、macOS、Windowsのいずれかのマシンが必要です。
コマンドプロンプトを表現するためには`$`記号を使用します。

Goのインストール([インストールの手順を参照](https://go.dev/doc/install))

あなたの`$GOPATH`の内側に新しいディレクトリを作成して、そのディレクトリに移動してください

```terminal
$ mkdir gowiki
$ cd gowiki
```

`wiki.go`という名前のファイルを作成して、好きなエディタでそのファイルを開いて、以下のように入力してください。

```go
package main

import (
    "fmt"
    "os"
)
```

Go言語の標準ライブラリから`fmt`と`os`というパッケージを読み込んでいます。
後々、追加機能を実装していくので、この`import`宣言の中にパッケージを追加していきます。

## データ構造
データ構造を定義する所から始めましょう。wikiは相互に接続された1連のページで構成されており、それぞれのページにはタイトルとボディ(本文)があります。
ここでタイトルとボディを表す2つの値を持った構造体を`Page`として定義します。

```go
type Page struct {
    Title string
    Body  []byte
}
```

この`[]byte`型は「`byte`のslice」という意味です(詳しくは[Slices: usage and internals](https://go.dev/blog/slices-intro)を参照)。
`Body`要素は`string`よりむしろ`[]byte`の方が望ましいです。なぜなら、後で使用する`i/o`ライブラリの型だからです。

この構造体`Page`はページのデータがどのようにメモリに保存されるかを記述しています。
一方、永続的なストレージについてはどうでしょうか？
`Page`に`save`メソッドを作成することで解決できます。

```go
func (p *Page) save() error {
    filename := p.Title + ".txt"
    return os.WriteFile(filename, p.Body, 0600)
}
```

このメソッドのシグネチャは次の通りです。
「これは、レシーバpを`Page`へのポインタとしてとる`save`という名前のメソッドです。これは引数を取らず、`error`型の値を返します。 」
このメソッドは`Page`のボディをテキストファイルへ保存します。単純化するために、`Title`をファイル名として使用します。

この`save`メソッドは`WriteFile`(byteのsliceをファイルへ書き込む標準ライブラリの関数)の型を返すため、`error`の値を返します。
また、`save`メソッドは、ファイルへの書き込み中に発生した問題をアプリケーションが制御するために、`error`の値を返します。
もし全て上手くいった場合は、`Page.save()`は`nil`(ポインタやインタフェースなどのための0値)を返します。

8進数の整数`0600`は`WriteFile`への3つ目の引数として渡される値であり、現在のユーザのみに対する読み書きの権限と共にファイルが作成されることを示しています。

ページの保存に加えて、ページも読み込みもしたくなってくるでしょう

```go
func loadPage(title string) *Page {
    filename := title + ".txt"
    body, _ := os.ReadFile(filename)
    return &Page{Title: title, Body: body}
}
```

`LoadPage`関数は、引数`title`からファイル名を構築し、新しい変数`body`へファイルの中身を読み込み、適切なタイトルとボディの値で構成された`Page`リテラルへのポインタを返します。

関数は複数の値を返すことができます。標準ライブラリの関数`os.ReadFile`は`[]byte`と`error`を返します。`LoadPage`では、`error`はまだ制御されていません。アンダースコア(_)によって表される「空白識別子」はerrorの返り値をスローするのに使われます。
(要するに何もない所に値を割り当てるということです。)

しかし、`ReadFile`がエラーに遭遇した場合は何が起こるのでしょうか？例えば、ファイルが存在しないとしましょう。
そのようなエラーは無視するべきではありません。`*Page`と`error`を返すように修正しましょう。

```go
func loadPage(title string) (*Page, error) {
    filename := title + ".txt"
    body, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return &Page{Title: title, Body: body}, nil
}
```

今、この関数の呼び出し元は2個目のパラメタを確認することができます。
もし、それが`nil`であれば`Page`は読み込まれ、そうでない場合は呼び出し元によって制御できる`error`になるでしょう。

この時点で、単純なデータ構造とファイルを保存・読み込みする機能があります。
`main`関数を書いて、書かれているコードをテストしてみましょう。

```go
func main() {
    p1 := &Page{Title: "TestPage", Body: []byte("This is a sample Page.")}
    p1.save()
    p2, _ := loadPage("TestPage")
    fmt.Println(string(p2.Body))
}
```

このコードをコンパイルして実行した後、`TestPage.txt`という名前のファイルが作成され、それは`p1`の中身を含んでいるでしょう。
また、そのファイルは構造体p2によみこまれ、その`Body`要素は画面に表示されるでしょう。

以下のようにプログラムをコンパイルして実行することができます。

```terminal
$ go build wiki.go
$ ./wiki
This is a sample Page.
```

(もしWindowsを使用している場合は、プログラムを実行するために"./"を抜いて"wiki"と入力しなければなりません)

[ここまで書いたコードを見るためにはここをクリック](https://go.dev/doc/articles/wiki/part1.go)

## net/httpパッケージの紹介
以下のコードは完全に動作するシンプルなwebサーバの例です

```go
//go:build ignore

package main

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

メイン関数は http.HandleFunc の呼び出しから始まり、
http パッケージに Web ルート ("/") へのすべてのリクエストを`handler`で処理するよう指示しています。

次に`http.ListenAndServe`を呼び出し、任意のインターフェイス(":8080")のポート8080をリッスンするよう指定します
(2番目のパラメータは気にしないでください)。
(この関数はプログラムが終了するまでブロックされます。

`ListenAndServe`はいつもエラーを返します。なぜなら予期しないエラーが起こった時にだけエラーを返すためです。
そのエラーをログへ出力するために、関数呼び出しを`Log.Fatal`で包んでいます。

`handler`関数は`http.HandlerFunc`型のものです。また、`http.ResponseWriter`と`http.Request`を引数として受け取ります。

`http.ResponseWriter`の値は、HTTPサーバーのレスポンスを組み立てるもので、
これに書き込むことでHTTPクライアントにデータを送信することができます。

http.Requestは、クライアントのHTTPリクエストを表すデータ構造です。 
r.URL.Pathは、リクエストURLのパスコンポーネントです。
末尾の[1:]は、"1文字目から末尾までのPathのサブスライスを作成する"ことを意味します。
これにより、パス名から先頭の"/"が取り除かれます。

もしこのプログラムを実行して、このURLにアクセスしたとすれば
```terminal
http://localhost:8080/monkeys
```
このプログラムは以下の文言を含むページを表示するでしょう

```terminal
Hi there, I love monkeys!
```

## wikiのページを配信するためにnet/httpを使用する

`net/http`パッケージを使用するためには、これをインポートしなければなりません

```go
import (
    "fmt"
    "os"
    "log"
    "net/http"
)
```

さあハンドラを作りましょう。`viewHandler`はユーザがwikiのページを閲覧できるようにするものです。
また、"/view/"というURLの拡張子を操作します。

```go
func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    p, _ := loadPage(title)
    fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body)
}
```

ここでも、loadPageからのエラーの返り値を無視するために_を使用していることに注意してください。
これは、ここでは単純化するために行っていますが、一般的にはバッドプラクティスと考えられています。
これについては後で説明します。

まず、この関数は、リクエストURLのパスコンポーネントであるr.URL.Pathからページタイトルを抽出します。
Pathは[len("/view/"):]で再スライスされ、リクエストパスの先頭の"/view/"コンポーネントが削除されます。
これは、パスが必ず "/view/" で始まるためで、これはページのタイトルの一部ではありません。

この関数は、ページのデータを読み込み、シンプルなHTMLの文字列でページをフォーマットして、http.ResponseWriterのwに書き出します。

このハンドラを使用するために、メイン関数を書き換えて、viewHandlerを使用してhttpを初期化し、
パス/view/以下のすべてのリクエストを処理するようにします。

```go
func main() {
    http.HandleFunc("/view/", viewHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

[ここまでのコードはこちら](https://go.dev/doc/articles/wiki/part2.go)

いくつかのページデータ(test.txt)を作成し、コードをコンパイルして、wikiページを配信してみましょう。

test.txtをエディタで開き、"Hello world "という文字列を（引用符なしで）保存してください。

```terminal
$ echo "Hello world" >> test.txt
$ go build wiki.go
$ ./wiki
```

(Windowsをお使いの場合は、プログラムを実行するために "./" を除いた "wiki" と入力する必要があります)。

このウェブサーバーを起動し、http://localhost:8080/view/test にアクセスすると、
"Hello world" という言葉を含む "test" というタイトルのページが表示されるはずです。

## ページの編集

ページを編集する機能なしにはwikiはwikiではありません。
さあ2つの新しいハンドラを作りましょう。
1つは'edit page'フォームを表示するeditHandlerと名前のもので、
もう1つはフォームを通して入力されたデータを保存するsaveHandlerという名前のものを作成しましょう。

最初に、以下のようにmain()に追加します。

```go
func main() {
    http.HandleFunc("/view/", viewHandler)
    http.HandleFunc("/edit/", editHandler)
    http.HandleFunc("/save/", saveHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

editHandler関数はページを読み込み(もしくは、存在しない場合は、空の構造体Pageを作ります)、HTMLのフォームを表示します。

```go
func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    fmt.Fprintf(w, "<h1>Editing %s</h1>"+
        "<form action=\"/save/%s\" method=\"POST\">"+
        "<textarea name=\"body\">%s</textarea><br>"+
        "<input type=\"submit\" value=\"Save\">"+
        "</form>",
        p.Title, p.Title, p.Body)
}
```

この関数はうまく動作しますが、ハードコーディングされたHTMLは醜いものです。もちろん、もっと良い方法がありますよ。

## html/templateパッケージ

`html/template`パッケージはGoの標準ライブラリの1部です。
html/templateを使用すると、HTMLを別のファイルに保存できるため、
基礎となるGoコードを変更せずに編集ページのレイアウトを変更することができます。

はじめに、`html/template`をimportのリストに入れてください。
また`fmt`はもう使用しないので、削除してください。

```go
import (
    "html/template"
    "os"
    "net/http"
)
```

HTMLフォームを含んだテンプレートファイルを作ってみましょう。`edit.html`という名前で新しいファイルを開いて、以下の行を追加してください。

```html
<h1>Editing {{.Title}}</h1>

<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>
```

ハードコーディングされたHTMLの代わりに、`editHandler`を編集してテンプレートを使用できるようにしてください。

```go
func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    t, _ := template.ParseFiles("edit.html")
    t.Execute(w, p)
}
```

`template.ParseFiles`関数は`edit.html`の中身を読み込んで、`*template.Template`を返します。

メソッド`t.Execute`はテンプレートを実行して、`http.ResponseWriter`へ生成されたHTMLを書き込みます。
`.Title`と`.Body`のドット付き識別子は、`p.Title`と`p.Body`を参照しています。

テンプレートディレクティブは二重中括弧で囲まれています。
`printf "%s" .Body`の文は、`.Body`をバイトのストリームではなく文字列として出力する関数呼び出しで、
`fmt.Printf.Template`の呼び出しと同じです。
`html/template`パッケージは、テンプレートアクションによって安全で正しい外観のHTMLのみが生成されることを保証するのに役立ちます。
例えば、ユーザーデータがフォームのHTMLを破壊しないように、大なり記号(>)を自動的にエスケープして、`&gt;`に置き換えます。

ここではテンプレートを使っているので、`view.html`という`viewHandler`用のテンプレートを作成しましょう。

```html
<h1>{{.Title}}</h1>

<p>[<a href="/edit/{{.Title}}">edit</a>]</p>

<div>{{printf "%s" .Body}}</div>
```

`viewHandler`も同時に修正しましょう

```go
func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    p, _ := loadPage(title)
    t, _ := template.ParseFiles("view.html")
    t.Execute(w, p)
}
```

両方のハンドラでほとんど同じテンプレート・コードを使用していることに注意してください。
テンプレート化するコードを独自の関数に移動させることで、この重複を解消してみましょう。

```go
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    t, _ := template.ParseFiles(tmpl + ".html")
    t.Execute(w, p)
}
```

そしてこの関数を使うためにハンドラを修正します

```go
func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    p, _ := loadPage(title)
    renderTemplate(w, "view", p)
}
```

```go
func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}
```

mainの未実装の`saveHandler`をコメントアウトすれば、再びプログラムをビルドしてテストすることができます。
これまでに書いたコードは、[ここをクリック](https://go.dev/doc/articles/wiki/part3.go)してご覧ください。

## 存在しないページの扱い

もしあなたが`/view/存在しないページのパス`にアクセスしたらどうなるでしょうか？
HTMLを含むページが表示されるでしょう。
これは`loadPage`からのエラーの戻り値を無視し、データなしでテンプレートを埋めようとし続けるからです。
代わりに、要求されたページが存在しない場合、コンテンツが作成されるようにクライアントを編集ページにリダイレクトする必要があります。

```go
func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}
```

`http.Redirect`関数は、HTTPレスポンスに`http.StatusFound (302)`のHTTP ステータスコードとLocation ヘッダーを追加します。

## ページの保存
関数`saveHandler`は、編集ページに配置されたフォームの送信を処理します。
`main()`関数の関連行のコメントアウトを削除した後、ハンドラを実装してみましょう。

```go
func saveHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/save/"):]
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    p.save()
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
```
ページのタイトル(URL で提供される)とフォームの唯一のフィールドである`Body`は、新しい`Page`に保存されます。
その後、`save()`メソッドが呼び出されてデータがファイルに書き込まれ、クライアントは`/view/`ページにリダイレクトされます。

`FormValue`が返す値は`string`型です。
構造体`Page`に合わせて、この値を`[]byte`に変換する必要があります。
ここでは`[]byte(body)`を使って変換しています。

## エラーハンドリング

このプログラムには、エラーを無視している箇所がいくつかあります。
これはエラーが発生したときにプログラムが意図しない動作をすることになるため、悪い習慣です。
より良い解決策は、エラーを処理して、ユーザーにエラーメッセージを返すことです。
そうすれば、何か問題が発生したときに、サーバーは意図した通りに正確に機能し、ユーザーにエラーが発生したことが伝わります。

まず、`renderTemplate`の中でエラーを処理してみましょう。

```go
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    t, err := template.ParseFiles(tmpl + ".html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    err = t.Execute(w, p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}
```
http.Error関数は、指定されたHTTPレスポンスコード`Internal Server Error`とエラーメッセージを送信します。
すでに、これを別の関数に置くという決断が功を奏しています。

さて、`saveHandler`を修正してみましょう。

```go
func saveHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/save/"):]
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
```

`p.save()`の間で起こったエラーはユーザへ知らされます。

## テンプレートキャッシング
このコードには効率が良くない点があります。
ページがレンダリングされるたびに`renderTemplate `が`ParseFiles`を呼び出しています。
よりよい方法は、プログラムの初期化時に一度だけ`ParseFiles `を呼び出し、
すべてのテンプレートを単一の `*Template`にパースすることです。
それから`ExecuteTemplate`メソッドを使って、特定のテンプレートをレンダリングすることができます。

まず、`templates `というグローバル変数を作成し、それを ParseFiles で初期化します。

```go
var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
```

関数`template.Must`は、`nil`でないエラー値が渡されるとパニックを起こし、
そうでない場合は`*Template`をそのまま返す便利なラッパーです。
テンプレートを読み込むことができない場合、プログラムを終了することが唯一の賢明な方法です。

`ParseFiles`関数はテンプレートファイルを識別する文字列引数を取り、
それらのファイルをベースのファイル名の後に名付けられたテンプレートにパースします。
プログラムにさらにテンプレートを追加する場合は、その名前を`ParseFiles`呼び出しの引数に追加します。

次に、適切なテンプレートの名前で`templates.ExecuteTemplate`メソッドを呼び出すように`renderTemplate`関数を変更します。

```go
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    err := templates.ExecuteTemplate(w, tmpl+".html", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}
```

なお、テンプレート名はテンプレートファイル名なので、`tmpl`の引数に".html "を追加する必要があります。

## バリデーション
お気づきのように、このプログラムには重大なセキュリティ上の欠陥があります。
ユーザーがサーバー上で読み書きをするために、任意のパスを供給することができるのです。
これを軽減するために、タイトルを正規表現で検証する関数を書けばよいでしょう。

まず、`import`のリストに "regexp "を追加してください。
そして、グローバル変数を作成して、検証式を格納してください。

```go
var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
```

関数`regexp.MustCompile`は正規表現を解析、コンパイルし、`regexp.Regexp.MustCompile`を返します。
`MustCompile`は`Compile`と異なり、コンパイルに失敗するとパニックを起こしますが、
`Compile`は第2パラメータとしてエラーを返します。

では、`validPath`式を使って、パスを検証し、ページタイトルを抽出する関数を書いてみましょう。

```go
func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
    m := validPath.FindStringSubmatch(r.URL.Path)
    if m == nil {
        http.NotFound(w, r)
        return "", errors.New("invalid Page Title")
    }
    return m[2], nil // The title is the second subexpression.
}
```

タイトルが有効な場合は、`nil`のエラー値とともに返されます。
タイトルが無効な場合は、HTTP接続に"404 Not Found"エラーを書き込み、ハンドラにエラーを返します。
新しいエラーを作成するには、`errors`パッケージをインポートする必要があります。

各ハンドラーに`getTitle`の呼び出しを記述してみましょう。

```go
func viewHandler(w http.ResponseWriter, r *http.Request) {
    title, err := getTitle(w, r)
    if err != nil {
        return
    }
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}
```

```go
func editHandler(w http.ResponseWriter, r *http.Request) {
    title, err := getTitle(w, r)
    if err != nil {
        return
    }
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}
```

```go
func saveHandler(w http.ResponseWriter, r *http.Request) {
    title, err := getTitle(w, r)
    if err != nil {
        return
    }
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err = p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
```

## 関数リテラルとクロージャの紹介
各ハンドラでエラー条件をキャッチすると、多くのコードが繰り返されることになります。
各ハンドラを、この検証やエラーチェックを行う関数で包む事ができたらどうでしょう？
Goの関数リテラルは、機能を抽象化する強力な手段であり、ここで役に立ちます。

まず、タイトル文字列を受け取るように各ハンドラの関数定義を書き直します。

```go
func viewHandler(w http.ResponseWriter, r *http.Request, title string)
func editHandler(w http.ResponseWriter, r *http.Request, title string)
func saveHandler(w http.ResponseWriter, r *http.Request, title string)
```

ここで、上記の型の関数を受け取り、(関数`http.HandleFunc`に渡すのに適している)
`http.HandlerFunc`型の関数を返すラッパー関数を定義してみましょう。

```go
func makeHandler(fn func (http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // ここでリクエストからページのタイトルを抽出します
        // そして与えられているハンドラ`fn`を呼び出します
    }
}
```

返された関数は、その外側で定義された値を囲むので、クロージャと呼ばれます。
この場合、変数`fn`（`makeHandler`の引数）はクロージャで囲まれています。
変数`fn`は、`save`、`edit`、または`view`ハンドラのいずれかになります。

さて、`getTitle`からコードを取り出し少々修正して、ここで使うことができます。

```go
func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        m := validPath.FindStringSubmatch(r.URL.Path)
        if m == nil {
            http.NotFound(w, r)
            return
        }
        fn(w, r, m[2])
    }
}
```

`makeHandler`が返すクロージャは`http.ResponseWriter`と`http.Request`を受け取る関数です (言い換えれば`http.HandlerFunc`です)。
クロージャは、リクエストパスからタイトルを抽出し、正規表現`validPath`でそれを検証します。
タイトルが無効な場合、関数`http.NotFound`を使用して、`ResponseWriter`にエラーが書き込まれます。
タイトルが有効な場合は、`ResponseWriter`、`Request`、`title`を引数として同封のハンドラ関数`fn`が呼び出されます。

これで、ハンドラ関数が`http`パッケージに登録される前に、`main`で`makeHandler`を使ってラップすることができるようになりました。

```go
func main() {
    http.HandleFunc("/view/", makeHandler(viewHandler))
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))

    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

最後に、ハンドラ関数から`getTitle`の呼び出しを削除してシンプルにしました。

```go
func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}
```

```go
func editHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}
```

```go
func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
```

## 試してみる

[最終的なコード一覧は、ここをクリックしてください。](https://go.dev/doc/articles/wiki/final.go)

コードを再度コンパイルして、コードを実行してください。

```terminal
$ go build wiki.go
$ ./wiki
```

[http://localhost:8080/view/ANewPage](http://localhost:8080/view/ANewPage)にアクセスすると、ページ編集フォームが表示されるはずです。
テキストを入力し、「保存」をクリックすると、新しく作成されたページに移動することができます。

## 他の課題

ご自身で取り組まれると良いシンプルな課題を用意しました

- テンプレートを`tmpl/`、ページのデータを`data/`に保存する。 
- ルートを`/view/FrontPage`へリダイレクトするハンドラを追加する。
- ページテンプレートを有効なHTMLにし、CSSのルールを追加することで、より洗練されたものにする。
- ページ間のリンクを実装するために、`[PageName]`のインスタンスを`<a href="/view/PageName">PageName</a>`へ変更する。(ヒント: `regexp.ReplaceAllFunc`を使ってみましょう。)