STEP1: フォームタグを配置する
コメント投稿機能を実装するにあたり、コメントをサーバに送信するために必要なタグを index.ftl
ファイル内に配置する必要があります。最低限、次の要素が必要です。
- テキスト(コメント)を入力するための入力エリア
- コメントをサーバに送信するための送信ボタン
- 上記の要素が一つのまとまったフォームであることを示すためのフォームタグ
なおHTTPリクエストには様々な種類がありますが、フォームに入力された内容をサーバに送信する際はPOSTメソッドが使用されます。HTTPリクエストについてもう少し詳しく知りたい場合は、MDNのサイトを参考にするのがオススメです。(シンプルに情報がまとまっています)
・HTTP リクエストメソッド – HTTP | MDN
https://developer.mozilla.org/ja/docs/Web/HTTP/Methods
index.ftl
に配置するフォーム関連のマークアップ例は次のようになります。
<form action="/" method="post">
<input type="text" name="comment">
<button>Submit</button>
</form>
button要素のtype属性のデフォルト値は submit
であり、これはサーバにフォームデータを送信するための値です。そしてこのボタンはサーバにデータを送信する目的で配置しているので、特に明示的にtype属性を指定する必要はありません。(もちろん、type="submit"
としても問題はありません)
これでフォームタグの配置は完了です。(コメントをページに反映するためのコードは後で追加します)
次のステップで、バックエンド側の処理を記述していきましょう!
STEP2: フォームデータを加工して処理する
このステップではPOSTメソッドで送られたデータをサーバで処理・加工し、それをテンプレートエンジン(index.ftl
)に返すところまで行います。
まず、コメント(文字列)を格納するための変数を宣言しておく必要がありますね。本番環境では、動的なデータを定義したり加工処理したりするコードを記述する用のディレクトリやファイルを作成し、その他の機能・ファイルとは分けて管理することが多いのですが、ここでは構造をシンプルに保つため全て Rooting.kt
ファイル内に記述していくことにします。
次のように、送信されたコメントを管理するための MutableList<String>
を宣言します。なお、初期値として "The first comment"
という値を一つ指定しています。
val comments = mutableListOf(
"The first comment",
)
そして次に、POSTメソッドに対するサーバサイドの処理を記述します。まず、送信されたフォームパラメータ(コメント)を受け取る必要がありますね。次のように記述することで、値を取得することができます。
post("/") {
val formParams = call.receiveParameters()
val comment = formParams["comment"] ?: ""
}
値を取得したら MutableList
にそれを追加し、ページに反映させるために元のページにリダイレクトする必要があります。それらのコードを追加すると、次のようになります。
post("/") {
val formParams = call.receiveParameters()
val comment = formParams["comment"] ?: ""
comments.add(comment)
call.respondRedirect("/")
}
ただ、現時点では index.ftl
にリダイレクトしたところでコメントは反映されません。GETメソッドでコメントを渡す処理を追加してないですし、index.ftl
内でもコメントを反映するためのタグを追加していないからです。
次のステップではそれらを追加していきましょう。
STEP3: コメントをページに反映させる
前のステップに引き続き、Routing.kt
ファイルを編集していきましょう。現在、GETメソッドでは単に index.ftl
が読み込まれるように設定されているだけであり、FreeMarkerContent
の第二パラメータである model
には null
が指定されています。
get("/") {
call.respond(FreeMarkerContent(
"index.ftl",
null
))
}
この model
パラメータに値(コメントリスト)を指定することができます。コード例は次のようになります。
get("/") {
call.respond(FreeMarkerContent(
"index.ftl",
mapOf("comments" to comments)
))
}
次に、index.ftl
テンプレートファイルを修正していきます。FreeMarkerには動的なデータを取り扱うための様々な機能があります。その一つが <#list> ~ </#list>
を使ったデータリストの展開です。
コメント送信フォームの下に、次のようにコメントを表示するためのリストタグを記述します。
<ul>
<#list comments as comment>
<li>${comment}</li>
</#list>
</ul>
ここではMap
のキーとして指定された comments
の値を comment
として展開し、li
要素でマークアップすることでコメントがリスト形式で表示されるようにしています。
こういったデータの展開や条件分岐等、動的にデータを取り扱う上で欠かせないFreeMakerの基本的な機能は公式サイトで確認することができます。
・list, else, items, sep, break, continue – Apache FreeMarker Manual
https://freemarker.apache.org/docs/ref_directive_list.html
これで一応、シンプルなコメント投稿機能の完成です。実際にコメントを投稿してみると、送信したコメントがちゃんと画面に反映されることが確認できます。※スタイル(CSS)調整は別途加えています
データベースを導入していないので、プロジェクトを立ち上げるたびにデータ(送信されたコメント)は失われてしまいますが、記事の冒頭で述べたように、データベースを導入するとコードが複雑になりすぎてしまうためこのシリーズでは割愛させていただきます。
また、現時点ではコメント投稿機能に致命的なセキュリティ上の問題があります。最後にその問題を解決していきましょう。
STEP4: セキュリティの問題を解決する
現在、コメント入力欄では無制限に文字入力を受けつけており、さらに入力された文字を加工することなく画面に反映させている状態です。このままでは、クロスサイトスクリプティング攻撃(XSS)をモロに受けてしまいます。
下の画像のようにコメント入力欄にアラートを表示するJavaScriptコードを入力して送信してみると、JavaScriptコードが実行されてしまいます。
これを防止する方法は色々ありますが、FreeMarkerには自動的にサニタイズしてくれる機能が備わっているので、今回はそれを利用することにしましょう。
Templating.kt
ファイルを開き、Application.configureTemplating()
の install(FreeMarker)
ブロック内に次のコードを追加します。
outputFormat = HTMLOutputFormat.INSTANCE
この設定により、< や > などタグとして認識されてしまう特殊な記号がエスケープ処理されます。この状態で先ほどと同様にコメント入力欄にscriptタグを挿入すると、JavaScriptプログラムとして実行はされず、単なる文字列としてコメントに残るだけで無害化されます。
これだけで全ての攻撃を防げるというわけではありませんが、サニタイズするだけでもセキュリティを高められるということがお分かり頂けたかと思います。
おわりに
以上で『KtorでWebサイト制作シリーズ』の完結となります。ここまでお付き合いいただき、本当にありがとうございました!
さらにKtorを使ってバックエンドのスキルを高めたいという方は、ぜひデータベースを導入して動的なデータをサーバのデータベースで管理する方法を学んでみることをオススメします。
また認証機能を追加して、限られたユーザーにのみコメントの投稿を許可するような仕組みを導入するのも良い勉強になるかと思います。
最終的に出来上がったサンプルのコードの全体は GitHub で公開していますので、そちらも合わせて参考資料としてお使いいただければ幸いです。
・GitHub Ktorサンプルプロジェクト
https://github.com/roydo/Ktro-Website-Sample