はじめに
前回の記事の続きです。
前回の記事はこちら
フォームに入力された内容の検証
前回は、ついにフォームからのリクエストをサーバ側で受け取って出力するというところまでやりました。
受け取ったリクエストをサーバ側で検証して行う実装をします。
Webアプリケーションを書く時は主に2つの場所でデータ検証を行います。
ひとつはページ上でのJavaScriptによる検証で(現在この方面では多くのプラグインがあります。
例えばValidationJSプラグインなどがそうです)、もうひとつはサーバ側での検証です。
この節ではどのようにサーバでの検証を行うか解説します。
ということですね、セキュリティ上、クライアント側のみの検証ですと問題がありますのでサーバ側での検証を行います。
必須フィールド
あるフィールド(項目)に必須チェックをつけたい場合の実装ですね。len
関数を使って文字列の長さが 0 の場合にエラーにするようです。
早速実装してみましょう。
前回のプログラムに関数を追加します。
func validate(form url.Values) (errors []string) {
const requiredErr = "%sは必須です。"
if len(form["username"][0]) == 0 {
errors = append(errors, fmt.Sprintf(requiredErr, "ユーザ名"))
}
return
}
また、username
を Println
している else
の一番最後に 下記を入れてみます。
// バリデーションしてみる
errors := validate(r.Form)
fmt.Println(errors)
さて、これで実行した後にユーザ名を空にして送信すると必須エラーが出るはずです。
見てましょう。
r.Formは異なる型のフォーム要素の空白に対して異なる処理を行います。
空のテキストフィールド、テキストエリアおよびファイルアップロードに対して、その要素の値を空にします。
また選択されていないコンボボックスやセレクトボックスr.Formの中にはそもそもその項目を作りません。
上の例の中の方法でデータを取得した時プログラムはエラーを発生させます。
そのため、r.Form.Get()を使って値を取る必要があります。
なぜなら、もしフィールドが存在しなかった場合、この方法で取得すると空の値を得るからです。
ですが、r.Form.Get()は単体の値しか得ることができません。
もしmapの値であれば必ず上の方法で得る必要があります。
JavaScript
とかで動的にフォームを変えた場合とかに、フォームの値があるかないかを検証する場合は、r.Form.Get()
を使う必要がありそうですね。
数
いわゆる 数値チェック
ですね。strconv.Atoi
で判定するようです。
これも実装してみましょう。
login.gtpl
に下記を追加します。
年齢:<input type="text" name="age">
そして魔改造したvalidate
関数です。
func validate(form url.Values) (errors []string) {
const requiredErr = "%sは必須です。"
const numErr = "%sは数字で入力してください。"
const rangeErr = "%sは0~99の値で入力してください。"
if len(form["username"][0]) == 0 {
errors = append(errors, fmt.Sprintf(requiredErr, "ユーザ名"))
}
if form.Get("age") != "" {
getint, err := strconv.Atoi(form.Get("age"))
if err != nil {
errors = append(errors, fmt.Sprintf(numErr, "年齢"))
} else {
if getint < 0 || getint > 99 {
errors = append(errors, fmt.Sprintf(rangeErr, "年齢"))
}
}
}
return
}
これで実行してみましょう。
0 → 100 → aaa で入力してみました。
下記のようになりました。うまく効いているようですね。
$ go run mathapp/main.go
method: GET
method: POST
username: [user]
password: [pass]
age: [0]
GET query string /login
[]
method: POST
username: [user]
password: []
age: [101]
GET query string /login
[年齢は0~99の値で入力してください。]
method: POST
username: [user]
password: []
age: [aaaa]
GET query string /login
[年齢は数字で入力してください。]
また、 正規表現を使った数字のチェックもあります。
たぶんこちらのほうがスマートですね。
ただ、範囲チェックする場合は後で数字を使いますので、strconv
のほうが良さそうですね。
中国語
英語
こちらは正規表現を使って実装していますね。
実装自体は先程と同じ手順ですね、 validate
メソッドに追加してフィールドをチェックします。
さらっといきましょう。
メールアドレス
下記のような正規表現でチェックできるらしいです。PHP
フレームワークの Laravel
とかだと DNS
チェックまでするやつがあったような気がします。
フレームワークレベルになると結構ガチガチのチェックで、要望でもっとゆるくしてほしいとか言われることもありますね。
そういうときは、下記のように正規表現をゴニョゴニョしています。
if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
fmt.Println("no")
}else{
fmt.Println("yes")
}
携帯電話番号
こちらも正規表現ですね。
これはハイフン無しの場合ですね(0000000000)みたいに続けて打つタイプです。
これも実務だとハイフンありなしで変わってくるので、正規表現をゴニョるしかないですね。
海外の番号とか日本の番号とかで形式も変わってくるので、作るシステムが海外でも使用するようであればちゃんと調べないといけませんね。
if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
return false
}
プルダウンメニュー
フォームの中の
こちらもWebシステムではチェックする必要がありますね。
<select name="fruit">
<option value="apple">apple</option>
<option value="pear">pear</option>
<option value="banane">banane</option>
</select>
の場合、 下記のように
slice:=[]string{"apple","pear","banane"}
for _, v := range slice {
if v == r.Form.Get("fruit") {
return true
}
}
return false
で判定しています。 要は、範囲外のものは弾くような実装ですね。
ちなみに、Select
の要素とかは、DB
や定数
とかで管理することが多いと思います。html
と .go
のファイルで別々に定義するのではなく、同じところを参照したいですね。
ラジオボタン
多分こちらも同じですね。
コードはほぼ同じですので、引用はやめておきます。
チェックボックス
こちらも同様です。
チェックボックス以外の選択肢を弾いています。
日付と時間
日付と時間、
例えば、開始日~終了日のような入力欄があった場合に、開始日を終了日より後に設定させない。
の場合に使いますね。
ユーザが入力した日時が有効か確認したいとします。例えばユーザがスケジュールで8月45日にパーティを開く
予定を入力したり、未来の時間を誕生日にしてみたりといった場合です。
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Printf("Go launched at %s\n", t.Local())
なるほど。
これは、Date
オブジェクトを作っているだけですね。
バリデーションを行う場合、取得した文字列を日付にする必要がありますので、time.Parse
とかで日付オブジェクトに変えた後に、 用意されている方法で比較するのが良いと思います。
身分証明書番号
中国ではこういうものがあるんですね~! 15, 18桁の番号を検証していますね。
おわりに
ここではバリデーションの実装をやりました。
今後Webフレームワークを使う際は、この部分は吸収されて用意されている方法で書くことになると思います。PHP
とかだと CakePHP
だったり Laravel
はフォームに対して Form
のクラスを作ったりしているので。
なので、おそらく手で書くことはあまりないと思いますが、どういう方法でチェックしているのかを知ることができました。(もしかしたら、フレームワークでぜんぜん違う方法かもしれませんが。)
結構楽しくなってきましたね。