今川館

都内勤務の地味OLです

Goのimageモジュールを使って絵を描く

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

imageモジュールを使った描画の記事いろいろ

今月のGoの勉強の締めくくりに、画像ライブラリを使って絵を描いてみたくなった。
とはいえ、わたしは今までPythonのPILを使って画像のペーストやリサイズ程度のことしか経験がない。
一から絵を描く場合、どうしたら良いかまったく知見が無いので、他の人の記事を参考にやり方を調べた。

Go 言語でソースコードから画像生成する - てっく煮ブログ

「プログラムでシダを描画する」をGoで描画する - Qiita

これらの記事がどういうロジックで正確な画像を作っているのか恥ずかしながらよく分からなかったのだが、とりあえずimage.Rectで四角いパレットを作って、その中の座標にひたすらcolor.RGBAの色成分を埋めていけば絵が描けそうなことは分かった。

imageのRGBAとcolorのRGBA

Goのライブラリにはimage.RGBAとimage/color.RGBAの同じ名前の2種類の構造体が存在する。
color.RGBAの方が赤・緑・青の三原色を表す色成分を、image.RGBAの方がRGBAで表すインメモリの画像オブジェクトに該当するらしい。

imageのRGBAはimage.NewRGBA()で作れる。

円を描く

円を描くためには

if ある座標が円の中に入っていれば {
	円の色を指定
} else {
	背景色を指定
}

こうすれば良いことまで理解した。

問題は、「円の中に座標が入っている」ことを確かめる方法なのだが、以下記事にやり方が書いてあったので拝借。

任意の点(x, y)が円内に含まれているかどうか

冒頭の「てっく煮ブログ」さんのサンプルコードも参考に円を表す構造体のメソッドを定義した。(以下)

type Circle struct {
	X, Y, R float64
}

func (c *Circle) Inside(x, y int) bool {
	// sqrt((x-a)^2 + (y-b)^2) は二点(x, y), (a, b)間の距離
	// これが(x, y)を中心とする円の半径r以内の長さであれば円の内側

	xx, yy := c.X-float64(x), c.Y-float64(y)
	return math.Sqrt(xx*xx+yy*yy) <= c.R
}

サンプルコード

f:id:imagawa_yakata:20161221184327p:plain

以下の通り、DrawingHandlerを定義して、/newyearというPathに紐付けたので、localhost:8080/newyearを問い合わせると上記の画像が返却される。

[lib/drawing/main.go]

package drawing

import (
	"image"
	"image/color"
	"image/png"
	"math"
	"net/http"
)

type Circle struct {
	X, Y, R float64
}

func (c *Circle) Inside(x, y int) bool {
	// sqrt((x-a)^2 + (y-b)^2) は二点(x, y), (a, b)間の距離
	// これが(x, y)を中心とする円の半径r以内の長さであれば円の内側

	xx, yy := c.X-float64(x), c.Y-float64(y)
	return math.Sqrt(xx*xx+yy*yy) <= c.R
}

func NewImage() *image.RGBA {
	var w, h int = 280, 240
	m := image.NewRGBA(image.Rect(0, 0, w, h))

	large := Circle{140, 240, 80}
	middle := Circle{140, 180, 50}
	small := Circle{140, 120, 20}

	white := color.RGBA{
		255,
		255,
		255,
		255,
	}
	another := color.RGBA{
		170,
		179,
		0,
		255,
	}
	orange := color.RGBA{
		255,
		102,
		0,
		255,
	}

	for x := 0; x < w; x++ {
		for y := 0; y < h; y++ {
			var c color.RGBA
			if small.Inside(x, y) {
				// ちっちゃい円の中にある座標はオレンジ色(橙の絵)
				c = orange
			} else if large.Inside(x, y) || middle.Inside(x, y) {
				// 大きい円、または中くらいの円の中にある座標は白(お餅の絵)
				c = white
			} else {
				// それ以外は背景とする
				c = another
			}
			m.Set(x, y, c)
		}
	}
	return m
}

func DrawingHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "image/png")
	img := NewImage()
	png.Encode(w, img)
}

[main.go]

// import "github.com/oyakata/kanjo/lib/drawing"

func init() {
	// 一部抜粋
	http.HandleFunc("/newyear", drawing.DrawingHandler)

}

鏡餅の絵に見えると思うんだけど、どうかなぁ・・

良いお年を。

はじめてのwebアプリ⑤ ユニットテストを追加

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

ユニットテストを追加する頃合いに

文字列とファイルに対応した文字数カウント機能を作ったところで、これらのユニットテストを作る頃合いになってきた。
また、リクエストを受け取ってレスポンスに書き込むハンドラ関数のテストも欲しいところだ。

そこで、ユニットテストを追加する工程をここに記していきたい。

まず、今までmain.goに全ての処理を記述していたプログラムを、以下の構成に変更した。

kanjo
├── lib
│   └── wc
│       ├── main.go
│       └── main_test.go
├── main.go
└── main_test.go

lib/wcの配下に文字数カウントモジュールを移動し、lib/wc/main_test.goで文字数カウントのテストを、main_test.goでハンドラ関数のテストを書いていく。

文字数カウントのプログラム

アプリケーションコード

まずlib/wcに移動したプログラムは以下、

[lib/wc/main.go]

func WordCountInString(text string) (count, byte_count, invalid int) {
	b := []byte(text)

	for len(b) > 0 {
		r, size := utf8.DecodeRune(b)
		if r == utf8.RuneError {
			invalid += size
		} else {
			count++
			byte_count += size
		}
		b = b[size:]
	}
	return
}

// ファイルを読み取って文字数、バイト数、不正バイト数を数えて返す。
func WordCountInFile(rd io.Reader) (count, byte_count, invalid int) {
	in := bufio.NewReader(rd)

	for {
		r, size, err := in.ReadRune()
		if err == io.EOF {
			break
		}
		if r == utf8.RuneError {
			invalid += size
		} else {
			byte_count += size
			// 正常な文字だけカウントする。
			count++
		}
	}
	return
}

関数をふたつ定義し、それぞれ文字列とファイルを引数に取る。
(カウントの処理を更に共通化したらどうか?と思う人もいるだろうが、今回は見送り)

テストコード

テストコードは以下のスライドの記述を参考にTable Driven Testを心がけて書いてみた。

テストしやすいGoコードのデザイン

[lib/wc/main_test.go]

package wc

// テストを実行するとき
// $ go test github.com/oyakata/kanjo/lib/wc/

import (
	"strings"
	"fmt"
	"testing"
)

type T struct {
	Input                     string
	Count, ByteCount, Invalid int
}

var valid, broken, exp string

func init() {
	valid = string([]byte{
		240, 169, 184, 189, // 𩸽
		227, 129, 174, // の
		227, 129, 178, // ひ
		227, 130, 137, // ら
		227, 129, 141, // き
		240, 159, 153, 134, // face with ok gesture
		240, 159, 153, 134, // face with ok gesture
		240, 159, 153, 134, // face with ok gesture
	})

	broken = string([]byte{
		169, 184, 189, // [NG] 先頭欠損
		227, 129, 174, // の
		129, 178, // [NG] 先頭欠損
		227, 137, // [NG] 2桁目欠損
		227, 129, // [NG] 3桁目欠損
		240, 153, 134, // [NG] 2桁目欠損
		240, 159, 134, // [NG] 3桁目欠損
		240, 159, 153, // [NG] 末尾欠損
	})

	part := "𩸽のひらきを居酒屋で注文して、1時間経つがまだ来ない。𠈻な客が店員を引き止めてなじるからだ。"
	// 46 * 3 + 7 + 2 = 147文字
	// 1行: (4byte * 2文字) + (3byte * 43文字) + (1byte * 1文字) = 138byte
	// 全体: 140 * 3 + 7 + 6 = 427byte
	exp = fmt.Sprintf("%v\n\n%v\n\n\n%v\n\n以上", part, part, part)
}

func TestWordCountInString(t *testing.T) {
	cases := []T{
		{valid, 8, 28, 0},
		{broken, 1, 3, 18},
		{"", 0, 0, 0},
		{exp, 147, 427, 0},
	}

	for _, tc := range cases {
		x, y, z := WordCountInString(tc.Input)
		if x != tc.Count || y != tc.ByteCount || z != tc.Invalid {
			t.Errorf("WordCountInString=%v% v% v, want=%v% v% v",
				x, y, z,
				tc.Count, tc.ByteCount, tc.Invalid,
			)
		}
	}
}

func TestWordCountInFile(t *testing.T) {
	cases := []T{
		{valid, 8, 28, 0},
		{broken, 1, 3, 18},
		{"", 0, 0, 0},
		{exp, 147, 427, 0},
	}

	for _, tc := range cases {
		x, y, z := WordCountInFile(strings.NewReader(tc.Input))
		if x != tc.Count || y != tc.ByteCount || z != tc.Invalid {
			t.Errorf("WordCountInString=%v% v% v, want=%v% v% v",
				x, y, z,
				tc.Count, tc.ByteCount, tc.Invalid,
			)
		}
	}
}

引数が文字列とファイルで違うけれども、基本的に確かめたいテストデータの内容は同じなのでinit()に共通化してしまったのだが個人的にはどうもしっくりこない。共通化したつもりがかえって読みづらくなった感がある。が、経験不足ということでとりあえずこれで良しとした。(テストもOKを確認)

ハンドラ関数のプログラム

アプリケーションコード

とりあえず、JSONで結果を返すハンドラ関数のテストだけ書いてみた。

まず、ハンドラ関数のコードは以下。

[main.go]

package main

// import "encoding/json"
// import "github.com/oyakata/kanjo/lib/wc"
// import "net/http"

// json.Marshalは構造体の公開フィールドしか出力してくれないので注意。
// 小文字でJSONのキーを出力したければタグを指定する。
type WordCount struct {
	Text      string `json:"text"`
	Count     int    `json:"count"`
	ByteCount int    `json:"byte_count"`
}

func JSONWordCountHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	text := r.FormValue("text")
	count, bc, _ := wc.WordCountInString(text)

	result, err := json.Marshal(WordCount{text, count, bc})
	if err != nil {
		log.Panic(err)
	}
	w.Write(result)
}

なお、テストコードの方でUnmarshalしたいのでWordCountの構造体をトップレベルに移動した。

テストコード

問題は、ハンドラ関数をテストするときに引数に渡すhttp.ResponseWriterとhttp.Requestの作り方なのだが…
まずコードを示すと以下の通り。

[main_test.go]

package main

import (
	"fmt"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestJSONWOrdCountHandler(t *testing.T) {
	v := "今川義元1560年桶狭間"
	location := fmt.Sprintf("http://example.com/count?format=json&text=%v", v)

	w := httptest.NewRecorder()
	// httptestにはなぜかNewRequestはないと言われる(go version 1.6.2)
	// undefined: httptest.NewRequest
	r, _ := http.NewRequest("GET", location, nil)

	JSONWordCountHandler(w, r)

	if w.Code != http.StatusOK {
		t.Errorf("JSONWordCountHandler=%v, got=%v", http.StatusOK, w.Code)
	}
	result := &WordCount{}
	json.Unmarshal([]byte(w.Body.String()), result)
	expected := WordCount{v, 12, 28}
	if *result != expected {
		t.Errorf("JSONWordCountHandler=%v, got=%v", expected, result)
	}
}
net/http/httptestという便利モジュールが存在する

ハンドラ関数の第1引数、http.ResponseWriterのオブジェクトをどうやって作ったら良いか最初わからず調べるのに結構苦労した。
何と、net/http/httptestというテスト向けのモジュールがちゃんと用意されていた。
httptest.NewRecorder()がResponseWriterインターフェースを実装したオブジェクトを返してくれるのでこれを使う。

リクエストオブジェクトを作るときはnet/http.NewRequst

Goのドキュメントを読むと、httptestにNewRequestが定義されていると書いてあるのだが、自分の手元で試すとundefinedと言われるのでnet/httpのNewRequestを使って対処した。

httptest - The Go Programming Language

よって、ハンドラ関数の引数が無事に作成できた。

w := httptest.NewRecorder()
// httptestにはなぜかNewRequestはないと言われる(go version 1.6.2)
// undefined: httptest.NewRequest
r, _ := http.NewRequest("GET", location, nil)

JSONWordCountHandler(w, r)

その気になればユニットテストを書く仕組みはGoにはちゃんと用意されているものだと感心した。

はじめてのwebアプリ④ アップロードファイルの扱い方

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

ファイルを読み込んで文字数を数える機能を追加する

文字数カウントするwebアプリも入力テキストを処理するだけなら出来上がったので、今度はファイルをアップロードしてカウントする機能を追加することにした。

(画面イメージ)
f:id:imagawa_yakata:20161220110636p:plain

ファイルアップロードの結果として

  • 文字数(不正な文字は除外した数)
  • バイト数
  • 不正なバイト数

これらを画面に表示する。

ここまでは比較的悩むことなく簡単に進めてこられたが、アップロードファイルを扱う必要が生じて、途端に進みが遅くなった。
色々ハマって一応完成したので、学んだことを以下記す。

ファイルアップロードに対応

リクエストメソッドはPOST, enctype="multipart/form-data"を指定

取り敢えず画面からファイルをアップロードしなければならないので、

<form action="/count/file" method="POST" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit">
</form>

このように、リクエストメソッドはPOST、enctypeは"multipart/form-data"にしておく。

http.Request.FormFileからアップロードされたファイルを取得

次に、サーバー側の処理でアップロードされたファイルを取得、利用する方法だが、リクエストがFormFileというメソッドを持っているのでこれを使うことにした。

FormFileはキーを指定するとファイルを返してくれるが、その際、データをhttp.defaultMaxMemoryまで自動的にメモリに読み込んで、残りを一時ファイルに保存する。

FormFileの該当箇所
https://golang.org/src/net/http/request.go?s=34318:34403#L1118

func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
	if r.MultipartForm == multipartByReader {
		return nil, nil, errors.New("http: multipart handled by MultipartReader")
	}
	if r.MultipartForm == nil {
		err := r.ParseMultipartForm(defaultMaxMemory)
		if err != nil {
			return nil, nil, err
		}
	}
	(後略)
ファイルの後始末

FormFileの処理がわかったのは良いとして、一時ファイルが作られたときは後で削除する必要があるが、それはどう対処するのだろうか。
これには、RequestにMutipartForm.RemoveAll()というお誂え向きのメソッドがあった。
ソースを見ると、一時ファイルがあれば消す、と賢く処理してくれるようだ。

RemoveAllの該当箇所
https://golang.org/src/mime/multipart/formdata.go?s=2602:2634#L102

func (f *Form) RemoveAll() error {
	var err error
	for _, fhs := range f.File {
		for _, fh := range fhs {
			if fh.tmpfile != "" {
				e := os.Remove(fh.tmpfile)
				if e != nil && err == nil {
					err = e
				}
			}
		}
	}
	return err
}


そこで、deferに無名関数を指定し、

  1. file.Close() → アップロードファイルを閉じる
  2. r.MultipartForm.RemoveAll() → 一時ファイルが作成されていれば、削除

を呼ぶことにした(以下)。

file, _, err := r.FormFile("file")
if err != nil {
	http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
}

defer func() {
	file.Close()
	r.MultipartForm.RemoveAll()
}()

ファイルから正常なruneの個数を数えたい

今までは文字列の中の文字数やバイト数を数えれば良いので簡単だったのだが、今度はファイルを見なければならない。
せっかくファイルを処理するのだから、全データを[]byteの変数に取り出してカウントするといった無駄なことはしたくないものだ。

最初、unicode/utf8を使って何とかしようと試みたがうまくいかず、色々調べた結果、実はbufioのReaderがReadRuneというメソッドを持っているのでこれを使えば良いことが判った。

ReadRuneの該当箇所
https://golang.org/src/bufio/bufio.go?s=6027:6084#L250

func (b *Reader) ReadRune() (r rune, size int, err error) {
	for b.r+utf8.UTFMax > b.w && !utf8.FullRune(b.buf[b.r:b.w]) && b.err == nil && b.w-b.r < len(b.buf) {
		b.fill() // b.w-b.r < len(buf) => buffer is not full
	}
(後略)

このように、正常なUTF-8の最大バイト数UTFMax(=4)まで読み取って正常なUnicodeコードポイント(rune)であるか調べて文字を返却してくれる。

なお、utf8にはruneを扱う上で必要な定数がちゃんと定義されているので覚えておくと良いかもしれない。

https://golang.org/pkg/unicode/utf8/#pkg-constants

const (
        RuneError = '\uFFFD'     // the "error" Rune or "Unicode replacement character"
        RuneSelf  = 0x80         // characters below Runeself are represented as themselves in a single byte.
        MaxRune   = '\U0010FFFF' // Maximum valid Unicode code point.
        UTFMax    = 4            // maximum number of bytes of a UTF-8 encoded Unicode character.
)

サンプルコード

最終的に、プログラムは以下のようになった。

type Context map[string]interface{}

func FileWordCountHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "POSTでアクセスしてください", http.StatusMethodNotAllowed)
		return
	}

	file, _, err := r.FormFile("file")
	if err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
	}

	defer func() {
		file.Close()
		r.MultipartForm.RemoveAll()
	}()

	in := bufio.NewReader(file)
	count := 0
	bc := 0
	invalid := 0

	for {
		r, size, err := in.ReadRune()
		if err == io.EOF {
			break
		}
		if r == utf8.RuneError {
			invalid += size
		} else {
			bc += size
			// 正常な文字だけカウントする。
			count++
		}
	}

	wc, _ := htmlTemplate.New("file_wc").Parse(`
	<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>

			文字数は: {{.count}}<br>
			バイト数は: {{.bc}}<br>
			不正なバイト数は: {{.invalid}}<br>

			でした。<br><br>

			文字を入力してください。
			<form action="/count" method="GET">
			<input type="text" name="text" size="32">
			<input type="submit">
			</form>

			ファイルを調べたい場合はこちら。
			<form action="/count/file" method="POST" enctype="multipart/form-data">
			<input type="file" name="file">
			<input type="submit">
			</form>

		</body>
	</html>`)

	data := Context{
		"count":   count,
		"bc":      bc,
		"invalid": invalid,
	}
	if err := wc.Execute(w, data); err != nil {
		log.Panic(err)
	}
}

はじめてのwebアプリ③ JSONで結果を出力する

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

JSONで出力する機能を追加する

はじめてのwebアプリのバグを直したところで、今度はJSONで結果を返す機能も追加してみる。

/countにformat=jsonで問い合わせが来たら、結果をJSONで返却する。

ハンドラ関数を分岐

まずハンドラ関数を分岐する。

func WordCountHandler(w http.ResponseWriter, r *http.Request) {
	if r.FormValue("format") == "json" {
		JSONWordCountHandler(w, r)
	} else {
		HTMLWordCountHandler(w, r)
	}
}

func JSONWordCountHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	// ...以下略
}

func HTMLWordCountHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	// ...以下略
}

json.Marshalは渡された構造体の公開フィールドしか出力しないので注意

Goのオブジェクトをjsonのテキストに出力する場合はencoding/jsonモジュールのMarshalを使う。

但し、利用には注意点がある。

小文字で始まるキーでJSONを出力したければタグを指定する

Marshalには構造体を渡すのだが、ここで渡す構造体の中で公開フィールドしか出力してくれない。
つまり、先頭が大文字で始まる名前のフィールドだけが出力対象となる。

最初、わたしは構造体を以下の通り定義したため、結果が"{}"、空のmapで出力されてしまった。

[ダメな例]

type WordCount struct {
	text  string
	count int
}

result, err := json.Marshal(WordCount{text, count})
if err != nil {
	log.Panic(err)
}
w.Write(result)

[ブラウザで問い合わせると…]

f:id:imagawa_yakata:20161219140820p:plain

↑この通り、空のmapが出力されて、NG。

[フィールドの名前を大文字で始めると]

type WordCount struct {
	Text  string
	Count int
}

result, err := json.Marshal(WordCount{text, count})

[ブラウザで問い合わせると…]

f:id:imagawa_yakata:20161219141030p:plain

↑今度はちゃんとデータが出力された、、が、キーが大文字で始まるのはちょっと・・

Goには「タグ」という仕組みがあって、構造体のフィールド宣言の後ろに文字列を書いておくとものによってはよきにはからってくれる。

json.Marshalはタグを見てくれるので、小文字のキーで出力するよう指定する。

[タグ付きでMarshal]

type WordCount struct {
	Text  string `json:"text"`
	Count int    `json:"count"`
}

result, err := json.Marshal(WordCount{text, count})

[ブラウザで問い合わせると…]

f:id:imagawa_yakata:20161219141334p:plain

↑今度は小文字でキーが出力され、期待通りの結果となった。

文字数とバイト数のふたつを返すよう変更

ついでに文字数の他にバイト数も返却するよう変更する。
さっきのMarshalに渡す構造体をちょっといじるだけ。

text := r.FormValue("text")
count := utf8.RuneCountInString(text)
bc := len(text)

// json.Marshalは構造体の公開フィールドしか出力してくれないので注意。
// 小文字でJSONのキーを出力したければタグを指定する。
type WordCount struct {
	Text      string `json:"text"`
	Count     int    `json:"count"`
	ByteCount int    `json:"byte_count"`
}

result, err := json.Marshal(WordCount{text, count, bc})
if err != nil {
	log.Panic(err)
}
w.Write(result)

出力結果にバイト数が追加されるようになった。

f:id:imagawa_yakata:20161219142357p:plain

はじめてのwebアプリ② unicode/utf8で文字数を数える

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

バグを直す

文字数カウントのwebアプリは、以下のバグがあるので直す。

  1. 文字数ではなくバイト数を算出している
  2. HTMLのエスケープをしていない

このため、

<font color="red">𩸽</font>

上記文字列を入力すると、スクリーンショットの通り、fontタグが適用されて文字が赤字表記になり、なおかつ本当は26文字と数えて欲しいにもかかわらず29文字と数えてしまう。

f:id:imagawa_yakata:20161219115328p:plain

文字数ではなくバイト数を算出している

これは前に学んだunicode/utf8のRuneCountInStringで数を調べるよう改める。

[before]

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

[after]

// import "unicode/utf8"

text := r.FormValue("text")
count := utf8.RuneCountInString(text)

HTMLのエスケープをかける

次に、HTMLのエスケープをかけるため、html/templateのHTMLEscapeStringを使うことにした。

[before]

content := fmt.Sprintf(`...HTML本文`, text, count, css)

[after]

// import htmlTemplate "html/template"

content := fmt.Sprintf(`...HTML本文`, htmlTemplate.HTMLEscapeString(text), count, css)

text/templateモジュールでHTMLを出力するよう変更

html/templateをよく見たら、HTMLEscapeStringを使わなくても、テンプレート機能を利用すれば出力の式({{.名前}})で自動エスケープしてくれるようだ。

なので、以下の通り改めた。

// import htmlTemplate "html/template"
// import "log"

wc, _ := htmlTemplate.New("wc").Parse(`
<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>

		入力文字: {{.text}}<br>
		文字数は: {{.count}}<br>

		でした。<br><br>

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

type Context map[string]interface{}
data := Context{"text": text, "count": count, "css": css}
if err := wc.Execute(w, data); err != nil {
	log.Panic(err)
}

これでHTMLのタグはエスケープされ、文字数も正しく26を数えてくれるようになった。

f:id:imagawa_yakata:20161219115336p:plain

はじめての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))
})

Pythonでサロゲートペア -- ほっけの逆襲

Pythonサロゲートペアを処理するとどうなるか

http://qiita.com/YusukeHirao/items/2f0fb8d5bbb981101be0

既に他の記事で上記記事に登場する「𩸽」(ほっけ)というサロゲートペアの文字をGoで扱う話しを書いたが、Pythonで処理するとどうなるのか試してみた。

Python2は寛容

Python2は3に比べて寛容である。printに渡しても取り敢えずエラーにならず表示される。

>>> print u'\uD867\uDE3D'
𩸽
>>> x = u'\uD867\uDE3D'
>>> y = u'𩸽'

但し、表示される文字は同じだが、文字列比較では同値ではないと評価される

>>> x == y
False

更に不思議なことに、こちらはordが結果を返すのに

>>> ord(y)
171581

こちらはエラーになってしまう
>>> ord(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 2 found


http://docs.python.jp/2/library/functions.html#ord

ord(c)(原文)

長さ 1 の与えられた文字列に対し、その文字列が unicode オブジェクトならば Unicode コードポイントを表す整数を、 8 ビット文字列ならばそのバイトの値を返します。たとえば、 ord('a') は整数 97 を返し、 ord(u'\u2020') は 8224 を返します。この値は 8 ビット文字列に対する chr() の逆であり、 unicode オブジェクトに対する unichr() の逆です。引数が unicodePython が UCS2 Unicode 対応版ならば、その文字のコードポイントは両端を含めて [0..65535] の範囲に入っていなければなりません。この範囲から外れると文字列の長さが 2 になり、 TypeError が送出されることになります。

サロゲートペアはlenで長さを調べると、「2」が返却される。
unicode型をlenに渡しているので「文字数」を取得するはずが、「2」を返す点が問題。
 str型をlenに渡すとバイト数を返すのでここでの問題対象外。

>>> len(x)
2
>>> len(y)
1

参考:

>>> len(u'あ')  # unicode型: 文字数
1
>>> len('あ')  # str型: バイト数
3

Unicode番号から文字を生成する」

当然、chrに渡すと範囲外なのでエラー。

>>> chr(0x29E3D)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: chr() arg not in range(256

unichrは正しく値を返してくれる。

>>> unichr(0x29E3D)
u'\U00029e3d'
>>> print unichr(0x29E3D)
𩸽

しかも、他の文字を試すとUbuntu16のVMでは化けて表示されるが、Macだとエラーになった。

★Ubuntu16.04のVM

f:id:imagawa_yakata:20161216105738p:plain

★El CapitalのMac

f:id:imagawa_yakata:20161216105742p:plain

El CapitanのMacでダメなのは、Pythonのビルドの条件のせいらしい。(以下)

Pythonでサロゲートペアの範囲の数値実体参照を文字に戻す - 西尾泰和のはてなダイアリー

Python3だと扱いが難しくなる

Python3ではそもそもu'\uD867\uDE3D'をprintに渡すとエラーになる。

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'utf-8' codec can't encode character '\ud867' in position 0: surrogates not allowed

Python3は、最初からサロゲートペアを許さないポリシーのようだ。
そのため、'surrogatepass', 'suroogateescape'といったエラーオプションを指定して何とか解決する。

具体的には以下stackoverflowの回答の通り、UTF-16サロゲートペアを許可した状態で一度byteに変換し、strに戻すと大丈夫らしい。

How to work with surrogate pairs in Python? - Stack Overflow

>>> "\ud867\ude3d".encode('utf-16', 'surrogatepass').decode('utf-16')
'𩸽'
|<

ちなみに、Unicodeコードポイントでリテラル表記してあげれば大丈夫である。

>|text|
>>> print('\U00029e3d')
𩸽