今川館

都内勤務の地味OLです

はじめてのwebアプリ① net/httpのHandleFunc

この記事のソースコードこちら

文字数カウンターを作る

Goも基本的な構文をだいたい覚えたので簡単なwebアプリを作ってみる。

net/httpパッケージの使い方を覚える目的で、テキストを入力して文字数を数えて返す画面を作ることにする。

ソースコードここに上げておく。

Routeの追加: http.HandleFunc

httpにはRouteの追加方法としてHandleとHandleFuncが用意されている。

今回はHandleFuncを使ってトップページとワードカウントのページのRouteを追加してみる。

コードのイメージとしては、以下。

func init() {
	// URLパターンに正規表現は渡せない。
	http.HandleFunc("/count", WordCountHandler)

	// 順番に注意。"/"を先頭に指定すると他のPathがマッチしない。
	http.HandleFunc("/", TopPageHandler)
}

Handle, HandleFuncの第1引数に正規表現は渡せない

どうもnet/httpパッケージのHandleやHandleFuncは単なる文字列の前方一致で処理する模様。
ドキュメントでは引数の名前が"pattern"と書いてあるので正規表現を受け付けてくれるのかと思ったが、違うようだ。
(ただ、最も基本的なAPIは余計なことをしないに限るので、これで良いと思う)

正規表現で処理したい場合はHandleの引数に"/"など広くマッチするPathを指定し、http.Requestから取得可能なURL.Pathを調べて目的の処理に取り次ぐ。

以下、例

func init() {
	http.HandleFunc("/", Index)
}

func Index(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/count" {
		WordCountHandler(w, r)
	} else {
		TopPageHandler(w, r)
	}
}

この辺のコントローラー層を便利にやってくれるフレームワークは色々あるようだが、取り敢えず使わず基本に忠実に行くことにする。

レスポンスボディの書き込み

わたしはHandleFuncの引数に渡されるResponseWriterのWriteを使った(以下)、

func WordCountHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")

	// 1. テンプレートエンジンを使って処理するよう変更
	// 2. JSONで返却する機能を追加
	// 3. ファイルを読み取って文字数を数える機能を追加
	// 4. 文字数とバイト数を数えるよう変更
	// 5. ユニットテストを追加
	// 6. 最初のプログラムだと文字数ではなくバイト数を返してしまうので直す
	// 7. logをファイルに出力するよう変更

	text := r.FormValue("text")
	count := len(text)

	css := ""
	if count == 0 {
		css = "err"
	}

	content := fmt.Sprintf(`
	<html>
		<head>
			<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
			<title>文字数カウント結果</title>
			<style type="text/css">
				.err { border: solid 1px red; }
			</style>
		</head>

		<body>
			<h1>文字数カウント結果</h1>

			入力文字: %v<br>
			文字数は: %v<br>

			でした。<br><br>

			文字を入力してください。
			<form action="/count" method="GET">
			<input type="text" name="text" size="32" class="%v">
			<input type="submit">
			</form>
		</body>
	</html>`, text, count, css)

	w.Write([]byte(content))
}

しかし、Goのドキュメントの例を見ると、fmt.Fprintfを使っていたので、簡単な文字列出力だったらこっちの方が確かに手軽だなぁなどと納得。

https://golang.org/pkg/net/http/#pkg-overview

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})