Build Web Application With Golang を試してやってみる 12

はじめに

前回の記事の続きです。
前回の記事はこちら

フォーム

前回まではWebの基礎についてやっていきました。
今回からはもっと実践的なフォームの章をやります。
フォームはお問合せフォームとかのものですね、入力項目があって入力してサーバに情報を送信するものです。

フォームは表の要素を含むエリアです。
フォームの要素はユーザがフォームの中で(例えば、テキストフィールド、コンボボックス、チェックボックス、セレクトボックス等です。)情報を入力する要素です。
フォームはフォームタグ(\)で定義します。

<form>
</form>

これで囲んでる部分ですね。

フォームの入力を処理する

早速サンプルがあるので試してみましょう。

準備

login.gtpl というファイルを作成する必要があるそうです。
まずはこちらを作成します。
html というディレクトリを作成して、その中に作成してみました。 全体構造はこのような感じです。
vscode-01

準備ができたので、今度は main.go を書き換えてみます。
下にあるコードを書いてみます。

できたら早速実行してみます。 web-server-01 さて、早速実行して今回の目的となるパス /login にアクセスしてみましたがエラーになってしまいました!

上のスクリーンショットのエラーを見てみると、templateParseFiles の部分でうまく読み込めていなさそうです。
html/login.gtpl と指定してあげるべきですね!

t, _ := template.ParseFiles("login.gtpl")
t, _ := template.ParseFiles("html/login.gtpl") になおして再度実行してみます。

web-server-02
お!ログイン画面が出てきました。
ひとまずこれでOKですね。
コードを見ていきます。

まずは main 関数です。

func main() {
    http.HandleFunc("/", sayhelloName)       //アクセスのルーティングを設定します
    http.HandleFunc("/login", login)         //アクセスのルーティングを設定します
    err := http.ListenAndServe(":9090", nil) //監視するポートを設定します
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

http.HandleFunc("/login", login) が追加されていますね。
前章では、 path関数 の対応(ルーター)を設定するものと解説されていました。
これは、 /login にアクセスした際に、login 関数が呼ばれるようです。

login 関数を見てみましょう

func login(w http.ResponseWriter, r *http.Request) {
    fmt.Println("method:", r.Method) //リクエストを取得するメソッド
    if r.Method == "GET" {
        t, _ := template.ParseFiles("login.gtpl")
        t.Execute(w, nil)
    } else {
        //ログインデータがリクエストされ、ログインのロジック判断が実行されます。
        fmt.Println("username:", r.Form["username"])
        fmt.Println("password:", r.Form["password"])
    }
}

順におっていきましょう。
まずは関数の定義です、 login(w http.ResponseWriter, r *http.Request) ですね。
これは、HandlerFunc の引数にとるのがこういう形の関数だったというのをやりました、お作法ですね。
そして一行目です。
fmt.Println("method:", r.Method) でどうやら、リクエストのメソッドを出力しているようです。
私の環境でも下記のように、method: GET と表示されています。

$ go run mathapp/main.go 
method: GET

その後、GET であれば、 login.gtpl というファイルを読み込んでいますね。
template.ParseFiles はおそらく html を解析してくれるものでしょう。
その後、 t.Execute で実行しています。(たぶんここで レスポンスにhtmlを書き込む処理をしているのでしょう。)

login関数ではr.Methodに従ってログイン画面を表示するのかログインロジックを処理するのかが判断されます。
GETメソッドによるリクエストの場合はログイン画面を表示し、その他のメソッドによるリクエストではログインロジックを処理します。
例えばデータベースを検索したり、ログイン情報を検証したりといった事です。

あっていますね!

今度はフォーム送信をしてみます。
ユーザ名: username
パスワード: password
としてログインボタンを押してみます。
web-server-03

ログインしても真っ白な画面になりますね・・・!
なぜでしょうか。
引き続き読んでみます。

我々がユーザ名とパスワードを入力してもサーバは何も出力しません。
なぜでしょうか?デフォルトではHandlerの中ではformの内容を自動的に解析しないからです。
必ず明示的にr.ParseForm()をコールした後でなければ、このフォームのデータに対して操作を行うことはできません。
コードを少し修正して、fmt.Println("username:", r.Form["username"]) の前に
r.ParseForm()という一行を追加してください。
再コンパイルしてもう一度入力、送信してみると、
今度はサーバがあなたの入力したユーザ名とパスワードを出力するはずです。

なるほど、そうなんですね。
r.ParseForm というのが大事らしいです。
試してみます。

web-server-04
コンソールに、username , password と文字が入っていますね。
どうやらうまく行ったようです。

r.Formでは全てのリクエストのデータが含まれています。
例えばURLの中のquery-string、POSTのデータ、PUTのデータなどです。
URLのquery-stringフィールドとPOSTが衝突する場合はsliceに保存されます。
これには複数の値が保存されています。
Goのオフィシャルドキュメントでは次のバージョンでPOST、GETといったデータは分離されると述べています。

r.Form にリクエストされたフォームの値が全部入っているんですね。 PHP でいうところの $_REQUEST ですかね。

ではlogin.gtplのformのaction値であるhttp://127.0.0.1:9090/loginをhttp://127.0.0.1:9090/login?username=astaxieに変更してもういちど試してみましょう。
サーバが出力するusernameはsliceになっていませんか。サーバの出力は以下のようになります:

やってみます。
web-server-05

どうやらうまくいきません。
おそらくこの記事の段階ではSlice 保存されていたようですが、

Goのオフィシャルドキュメントでは次のバージョンでPOST、GETといったデータは分離されると述べています。 によって、変わったようですね。

下記を見てましたところ、

golang.jp: https://pkg.go.dev/net/http#Request.ParseForm

(*Request) ParseForm関数 func (r *Request) ParseForm() (err os.Error)

ParseFormは、POSTのときはリクエストのボディをフォームとして解析し、GETのときは受け取ったクエリを解析します。この関数は冪等であり、何回呼び出しても問題ありません。

のようです、おそらくPOSTのフォームの解析が入っているようですね。

最後に、 request.Form についての解説があるので読みます。

request.Formはurl.Values型です。この中にはkey=valueのような対応するデータが保存されています。
ここではformデータに対していくつかの操作をご紹介します:

v := url.Values{}
v.Set("name", "Ava")
v.Add("friend", "Jess")
v.Add("friend", "Sarah")
v.Add("friend", "Zoe")
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
fmt.Println(v.Get("name"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])

Tips: RequestそのものもFormValue()関数でユーザが送信したデータを取得できます。
例えばr.Form[“username”]はr.FormValue(“username”)とも書けます。
r.FormValueをコールした時は自動的にr.ParseFormがコールされますので、事前にコールする必要はありません。
r.FormValueは同名のデータの中から一つ目のものだけを返します。
もしデータが存在しない場合は空文字列を返します。

これも重要ですね。
これで、フォームから値を受け取ってなにかの媒体に保存などのプログラムもかけそうです。

おわりに

次回はバリデーションですね。(フォームに入力された内容の検証)
寒い日が続くので手がかじかんでます。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。