JSP & Servlet 入門


## はじめに

* Javaの基本的な構文について理解している前提です。
* Eclipse (Pleiades) を使用している前提です。

開発環境のセットアップについては [Eclipseの超基本的な使い方 - Qiita](http://qiita.com/Kazunori-Kimura/items/aad2366531502a1da618) を参照してください。


まずは、ServletとJSPの動作について確認します。（当記事）
その後、TODOアプリの開発を通してJSP&ServletによるWebアプリ開発の基本について解説しようと思います。(次回予定)


## プロジェクトの作成

* `パッケージ・エクスプローラー` を右クリックし `新規` -> `プロジェクト...` を選択
* `Web` -> `動的Webプロジェクト` を選択して `次へ>` をクリック
* プロジェクト名、ターゲット・ランタイムを入力して `完了` をクリック
  - プロジェクト名: `TodoServlet`
  - ターゲット・ランタイム: `Tomcat8 (Java8)`

| 1 | 2 | 3 |
|---|---|---|
|![プロジェクトの作成(1)](https://qiita-image-store.s3.amazonaws.com/0/39362/4c3dcac2-6cc5-7816-2fb8-ea33e1966f66.png "スクリーンショット 2017-06-22 14.27.25.png")|![プロジェクトの作成(2)](https://qiita-image-store.s3.amazonaws.com/0/39362/21e21e68-2024-3261-e8ff-40e9e22af668.png "スクリーンショット 2017-06-22 14.28.51.png")|![プロジェクトの作成(3)](https://qiita-image-store.s3.amazonaws.com/0/39362/d603e670-6893-c913-f937-9c334625fa10.png "スクリーンショット 2017-06-22 14.30.02.png")|



## HelloServletの作成

まずは動作確認を兼ねて、非常にシンプルな Servlet を作成します。

### Servletクラスの作成

* `パッケージ・エクスプローラー` で `src` を右クリックし `新規` -> `その他...` を選択
* `Web` -> `サーブレット` を選択して `次へ>` をクリック
* Javaパッケージ、クラス名を入力して `完了` をクリック
  - Javaパッケージ: `todo.controller`
  - クラス名: `HelloServlet`

### Servletの実行

* `パッケージ・エクスプローラー` で `TodoServlet` を右クリックし `実行` -> `サーバーで実行` をクリック
* `Tomcat v8.0 サーバー` を選択して `完了` をクリック
  - コンソールに `Tomcat` の起動ログが出力され、しばらくするとEclipseの内蔵ブラウザが表示されます。
  - `HTTPステータス 404 - /TodoServlet/` というページが表示されます。
* URLを `http://localhost:8080/TodoServlet/HelloServlet` として Enterキーを押してください。
  - `Served at: /TodoServlet` というページが表示されれば正常にServletが実行できています。

| 1 | 2 | 3 |
|---|---|---|
|![スクリーンショット 2017-06-22 14.51.48.png](https://qiita-image-store.s3.amazonaws.com/0/39362/3d2ac44d-7d84-2bee-675d-76701186bb5b.png "スクリーンショット 2017-06-22 14.51.48.png")|![スクリーンショット 2017-06-22 14.52.29.png](https://qiita-image-store.s3.amazonaws.com/0/39362/3ae30047-e8f2-e450-1201-259a8c67b98f.png "スクリーンショット 2017-06-22 14.52.29.png")|![スクリーンショット 2017-06-22 14.53.09.png](https://qiita-image-store.s3.amazonaws.com/0/39362/de5127b7-c3c0-5876-5e48-041a92f39e0f.png "スクリーンショット 2017-06-22 14.53.09.png")|



## index.jspの作成

つづいて、JSPの動作確認を行います。

### JSPファイルの作成

* `パッケージ・エクスプローラー` で `WebContent` を右クリックし `新規` -> `その他...` を選択
* `Web` -> `JSPファイル` を選択して `次へ>` をクリック
* ファイル名を入力して `完了` をクリック
  - ファイル名: index.jsp
* 以下のように編集します。
  - `<title>` を変更
  - `<body>` に 日時を出力するようコードを追加

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	<%= new java.util.Date() %>
</body>
</html>
```

Eclipseの内蔵ブラウザかご自身が普段使用しているブラウザで `http://localhost:8080/TodoServlet/` にアクセスします。

![スクリーンショット 2017-06-22 15.13.18.png](https://qiita-image-store.s3.amazonaws.com/0/39362/503e6325-deaa-c0f3-a0ca-311031f8df08.png "スクリーンショット 2017-06-22 15.13.18.png")


ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v1.0.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v1.0.0)

---

ServletとJSPの作成方法および動作確認を行いました。  
Eclipseがクラスやファイルの雛形を作成してくれるので、肉付けだけすればWebアプリケーションが作成できそうな感じですね。

---

## ServletとJSPの連携

JSPはTomcatにてServletに変換されて実行されています。
したがって、Servletで出来ることは基本的にJSPでも出来るはずです。

しかしながら、HTMLにコードを埋め込むJSPに詳細なロジックまで詰め込んでしまうと、途端に見通しの悪いソースコードになってしまいます。

* 詳細なロジックは Servlet にまとめ、その処理結果を JSP に引き渡す
* JSP は受け取った内容をどのように表示するのか、に専念する

という役割分担を行うことで見通しが良くなり、開発をスムーズに進められます。

簡単な例を用いて、ServletとJSPの連携方法について解説します。


### ServletからJSPにforwardする

まずは、単純に Servlet でリクエストを受け取り、JSPの内容を返すように実装します。

#### index.jspを移動させる

直接JSPにアクセスされないように、 `WEB-INF` の下に `index.jsp` を移動させます。
今後、JSPファイルが複数になった場合に管理が容易になるように `WEB-INF/view` フォルダを作成し、その下にJSPファイルを配置することにします。

* `パッケージ・エクスプローラー` で `WebContent/WEB-INF` を右クリックし `新規` -> `フォルダ` を選択
* フォルダー名を入力して `完了` をクリック
  - フォルダー名: `view`
* `パッケージ・エクスプローラー` で `WebContent/index.jsp` を右クリックし `リファクタリング` -> `移動` を選択
* `WebContent/WEB-INF/view` を選択して `OK` をクリック

#### HelloServletの修正

クライアント (ブラウザ) から `HelloServlet` にリクエストがあると、 `doGet` メソッドが実行されます。

`RequestDispatcher#forward`メソッドを使用すると、処理を他のServletやJSPに転送することができます。

今回は `/HelloServlet` にリクエストが来ると、先程の `WEB-INF/view/index.jsp` に転送するようにしています。


```java:HelloServlet.java

/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// TODO Auto-generated method stub

	String view = "/WEB-INF/view/index.jsp";
	RequestDispatcher dispatcher = request.getRequestDispatcher(view);

	dispatcher.forward(request, response);
}

```

#### サーバービューの表示とTomcatの再起動

* `ウィンドウ` -> `ビューの表示` -> `その他` を選択
* `サーバー` を選択して `OK` をクリック
* `サーバー` タブが表示されるので `(▶)` をクリック

Tomcatが再起動され、Servletの変更が反映されます。

ブラウザで `http://localhost:8080/TodoServlet/HelloServlet` にアクセスし、`index.jsp` の内容が表示されることを確認します。


ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v2.0.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v2.0.0)



### ServletからJSPに値を渡す

ServletからJSPに値を渡すには `HttpServletRequest#setAttribute` メソッドを使用します。

第1引数にキー、第2引数に値をセットします。

```jsp:HelloServlet.java
/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// JSPに適当な文字列を渡す
	request.setAttribute("foo", "bar");

	// JSPにforward
	String view = "/WEB-INF/view/index.jsp";
	RequestDispatcher dispatcher = request.getRequestDispatcher(view);

	dispatcher.forward(request, response);
}
```

Servletで `setAttribute` された値は JSPの `request.getAttribute` メソッドで取得できます。

`request` オブジェクトは JSP で特に宣言をしなくても使用可能になっています。


```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	<%= request.getAttribute("foo") %>
</body>
</html>
```

ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v2.1.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v2.1.0)


#### 参考: EL式

*Expression Language* の略。
`${...}` と書くことで式が評価・出力されます。

詳細な解説はこちらをご覧ください > [【初心者向け】EL式 - Qiita](http://qiita.com/chihiro/items/50131e8488e1b0cd1cdd)

EL式では、`request.getAttribute` の記載を省略して値を取得できます。

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	${foo}
</body>
</html>
```


### JSPからServletに値を渡す

JSPにテキストボックスを配置し、入力された値をServletで受け取ってみましょう。


JSPでユーザーに何かしらの入力を受け付けるためには `form` タグと `input` タグを利用します。

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	<%= request.getAttribute("foo") %>

	<form method="post" action="./HelloServlet">
		何か入力して: <input type="text" name="hoge">
		<button type="submit">送信</button>
	</form>
</body>
</html>
```

`form` タグの `method`属性で `submit`ボタンが押された際に、どのHTTPメソッドを使用するかを指定します。
`get` か `post` を使用することになります。

`action`属性は送信先のURLを指定します。
現在ブラウザに表示されているURLからの相対パスで指定するのが良いでしょう。
絶対パスで指定した場合、本番環境と開発環境でURLが異なる場合などの対応が困難になります。

`get`メソッドはブラウザからリクエストがあった際に使用していますので、formからのデータ受け取りは `post`メソッドを使用します。

`input`タグを使用してテキストボックスを表示します。

`button`タグの`type`属性を`submit`とすると、ボタンクリック時に`form`の`action`属性に指定されたURLに対して`method`属性で指定されたHTTPメソッドで`form`タグ内の各要素の値を送信します。


```java:HelloServlet.java
/**
 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 */
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// formから値を取得
	String value = request.getParameter("hoge");
	System.out.println(value);

	doGet(request, response);
}
```

ユーザーがボタンをクリックすると、`doPost`メソッドに処理が移ります。

`form` に入力された値は `HttpServletRequest#getParameter`メソッドで取得できます。
引数には `input`タグの`name`属性を指定します。

今回は `name="hoge"`の値を取得し、コンソールに出力した後に `doGet` メソッドを実行しています。


ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v2.2.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v2.2.0)


### JSP -> Servlet -> JSP

JSPからServletへの連携とServletからJSPへの連携を組み合わせて、Webアプリケーションっぽい動きを実装してみます。

1. GETでアクセスがあると "こんにちは、Guest さん！" というメッセージと、名前入力フォームを表示する
2. 名前入力して送信ボタンをクリックすると Servlet に入力内容をPOSTする
3. "Guest" をPOSTされた名前に置き換え、フォームを非表示とする

#### 名前入力フォームの準備

まずは「こんにちは」のメッセージと名前入力フォームを用意します。

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	こんにちは、<%= request.getAttribute("userName") %> さん！

	<form method="post" action="./Hello">
		名前を入力してください: <input type="text" name="name">
		<button type="submit">送信</button>
	</form>
</body>
</html>
```

#### 初回アクセス時の変数初期化

初回にGETでアクセスしてきた際は "Guest" と表示したいので、`userName` に "Guest" をセットしておきます。

```java:HelloServlet.java
/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	request.setAttribute("userName", "Guest");

	String view = "/WEB-INF/view/index.jsp";
	RequestDispatcher dispatcher = request.getRequestDispatcher(view);
	dispatcher.forward(request, response);
}
```

#### 入力データの受け取り

フォームに入力された名前を受け取り、`userName` を更新します。

日本語、中国語、韓国語などの言語 (Chinese, Japanese, Korean の頭文字から CJK、あるいはベトナム語を加えて CJKV と呼ばれます) を扱う場合、文字コードを適切に指定しないと文字化けが発生しますので注意してください。

```java:HelloServlet.java
/**
 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 */
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// 文字コードの指定
	request.setCharacterEncoding("utf-8");
	// formから値を取得
	String name = request.getParameter("name");

	request.setAttribute("userName", name);

	doGet(request, response);
}
```

#### 入力データの表示

*初回アクセス時の変数初期化* にて `userName` を問答無用で初期化してしまっています。
このままでは、せっかく `doPost` でセットした値が "Guest" に上書きされてしまうので、修正しましょう。

`userName` が `null` か空っぽの場合のみ、"Guest" をセットするようにします。

```java:HelloServlet.java
/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	String name = (String) request.getAttribute("userName");
	
	if (name == null || "".equals(name)) {
		request.setAttribute("userName", "Guest");
	}
	
	String view = "/WEB-INF/view/index.jsp";
	RequestDispatcher dispatcher = request.getRequestDispatcher(view);
	dispatcher.forward(request, response);
}
```

#### 入力フォームの非表示

一度名前を入力したら、入力フォームは不要なので隠してしまいます。
`userName` が "Guest" の場合のみ、入力フォームが表示されるようにします。

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	<% String userName = (String) request.getAttribute("userName"); %>
	こんにちは、<%= userName %> さん！

	<% if ("Guest".equals(userName)) { %>
	<form method="post" action="./HelloServlet">
		名前を入力してください: <input type="text" name="name">
		<button type="submit">送信</button>
	</form>
	<% } %>
</body>
</html>
```


#### 動作確認

| 1 | 2 |
|---|---|
|![スクリーンショット 2017-06-27 10.19.16.png](https://qiita-image-store.s3.amazonaws.com/0/39362/ace896c5-292d-e7dc-985b-f11f3110fd70.png "スクリーンショット 2017-06-27 10.19.16.png")|![スクリーンショット 2017-06-27 10.19.31.png](https://qiita-image-store.s3.amazonaws.com/0/39362/aa8e89f3-f212-3179-a8ea-dddc2d1a65ad.png "スクリーンショット 2017-06-27 10.19.31.png")|


ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v2.3.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v2.3.0)



## クロスサイト・スクリプティング (XSS) の脆弱性

実は、先程作成したWebアプリケーションには クロスサイト・スクリプティング (XSS) の脆弱性があります。
XSSとは *他人のWebサイトに悪意のあるスクリプトを埋め込む* 事です。

実際に名前フォームに `<script>alert("アホ");</script>` と入力してください。

Google Chrome の場合、以下のように `ERR_BLOCKED_BY_XSS_AUDITOR` というエラー画面が表示されます。

![スクリーンショット 2017-06-27 10.29.59.png](https://qiita-image-store.s3.amazonaws.com/0/39362/c3449046-d751-68b5-5014-635a72d7719a.png "スクリーンショット 2017-06-27 10.29.59.png")

ブラウザによってはスクリプトの動作がブロックされず、`アホ` というダイアログが表示されるでしょう。


### XSSの回避方法

XSSの原因は、ユーザーが入力したデータをそのままHTMLに出力していることにあります。
XSSの対策として有効な方法は、`<` や `>` といった文字をタグとして認識させないようにします。
このような処理のことを *エスケープ* と言います。

PHPやJavaScriptにはエスケープ処理を行う関数が標準で提供されていますが、Java (JSP) にはありません。
自作することも可能ですが、バグが混入するなども考えられるので、実績あるライブラリを導入するのが良いと思われます。

また、入力チェックを行い `<` や `>` といった文字を入力させない、という方法を検討する方もいらっしゃるかもしれませんが、XSS対策の良い方法とは言えません。
今回のようなシンプルなフォームであれば入力チェックも容易ですが、複雑なWebアプリケーションで漏れなく確実に入力チェックを実施するのは実装もテストも大変です。入力チェックを迂回する方法が絶対に無いようにするのも難しいでしょう。
やはり、XSS対策としては出力時にエスケープするのが簡単・確実です。


### taglibの導入

JSPには `タグライブラリ` という仕組みがあります。
JSPの処理をカプセル化することでJSPの見通しを良くし、機能の再利用性を高めることができます。

標準的な機能を集めた *JSTL (JSP Standard Tag Library)* が提供されていますので、それを導入します。

| 手順 | 画像 |
|-----|-----|
|[Apache Tomcat® - Apache Taglibs Downloads](http://tomcat.apache.org/download-taglibs.cgi)から4つのjarファイルをダウンロードします。|![スクリーンショット 2017-06-27 10.49.17.png](https://qiita-image-store.s3.amazonaws.com/0/39362/e9840f25-9619-4a03-09d6-28ebe0e2b99f.png "スクリーンショット 2017-06-27 10.49.17.png")|
|Eclipseの `/WebContent/WEB-INF/lib` にダウンロードした 4つの jar をドラッグ&ドロップします。確認メッセージが表示されるので、`ファイルをコピー`を選択して`OK`をクリックします。|![スクリーンショット 2017-06-27 10.52.45.png](https://qiita-image-store.s3.amazonaws.com/0/39362/0f2cabc3-767e-dbdd-f5b1-c62ed4fd5d77.png "スクリーンショット 2017-06-27 10.52.45.png")|
|`lib`フォルダにファイルが存在することを確認します。|![スクリーンショット 2017-06-27 10.53.02.png](https://qiita-image-store.s3.amazonaws.com/0/39362/a6a7c6c5-e8ee-7c98-ac60-eeb831f4410e.png "スクリーンショット 2017-06-27 10.53.02.png")|


※ Tomcatが起動中であれば、 *サーバー* タブで再起動が必要です。

### index.jsp の修正

`<%@ taglib %>` ディレクティブにて JSTL を読み込んでいます。
出力部分を `<%= %>` から `<c:out>` タグに変更しています。`<c:out>`タグはデフォルトでエスケープ処理を行います。

参考: [ＪＳＴＬリファレンス：<c:out>](http://struts.wasureppoi.com/jstl/02_out.html)

```jsp:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello, World!</title>
</head>
<body>
	<% String userName = (String) request.getAttribute("userName"); %>
	こんにちは、<c:out value="${userName}" /> さん！

	<% if ("Guest".equals(userName)) { %>
	<form method="post" action="./HelloServlet">
		名前を入力してください: <input type="text" name="name">
		<button type="submit">送信</button>
	</form>
	<% } %>
</body>
</html>
```

### 動作確認

| 1 | 2 |
|---|---|
|![スクリーンショット 2017-06-27 11.02.14.png](https://qiita-image-store.s3.amazonaws.com/0/39362/3d255704-fe40-1297-5440-b5b50da72cd5.png "スクリーンショット 2017-06-27 11.02.14.png")|![スクリーンショット 2017-06-27 11.02.23.png](https://qiita-image-store.s3.amazonaws.com/0/39362/2f3c2411-80ca-e299-d580-259a364cc25c.png "スクリーンショット 2017-06-27 11.02.23.png")|


ここまでのソースコード: [Kazunori-Kimura/todo-servlet at v2.4.0](https://github.com/Kazunori-Kimura/todo-servlet/tree/v2.4.0)


#### 参考

* [タグライブラリについて](http://ash.jp/java/taglib.htm)
* [Standard Taglib トピックス - JSTL プログラミング](http://www.jajakarta.org/taglibs/topics/docs/security_topics.html)

------

ServletとJSPの動作について確認し、非常にシンプルなWebアプリケーションで Servlet <-> JSP の連携方法を取り上げました。
また、最後に紹介したXSS対策は非常に重要なので、Webアプリケーションを作成する際は常に注意をしてください。