FormDataオブジェクトとCommons FileUploadでformデータをServletに送信・処理

#1. 目的と仕様
1. Java Webアプリケーション(Servlet, JSP)において，JSP内のformに入力された情報(e.g. ユーザネーム, パスワード, 画像)をサーバ(Servlet)にHTTPリクエストで送信する
2. form送信前に入力内容を確認し，不備があれば送信しない．そのため動的な処理が必要
3. Content-Type : multipart/form-data の場合Servletでリクエストパラメータが取得できないため，フィールドとファイルを同時に取得する方法が必要

<img width="433" alt="スクリーンショット 2018-12-29 17.48.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/319282/b3f88517-1e61-c8a3-da6e-98d6a70757fc.png">



#2. 方法
- JavaScript
    - FormDataオブジェクトでformデータを動的に処理
- Servlet
    - Apache Commons FileUploadを用いて受け取ったformデータをフィールドとファイルに分けて処理

#3. 環境と準備
- Apache Tomcat 9.0
- commons-fileuploard-1.3.3.jar
    - http://commons.apache.org/proper/commons-fileupload/download_fileupload.cgi の commons-fileupload-1.3.3-bin.zip をダウンロードし，解凍して commons-fileuploard-1.3.3.jar を WebContent/WEB-INF/lib に置く
    - 過去に脆弱性が報告されているため最新の1.3.3を使用
- commons-io-2.6.jar
    - http://commons.apache.org/proper/commons-io/download_io.cgi の commons-io-2.6-bin.zip をダウンロードし，解凍して commons-io-2.6.jar を WebContent/WEB-INF/libに置く

#4. 実装
```sample.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE HTML>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Sample</title>
</head>
<body>
	<form id="form">
	Username : <input type="text" id="username" name="username"><br>
	Password : <input type="password" id="password" name="password"><br>
	Image :  <input type="file" id="image" name="image" accept="image/*"><br>
	<input type="submit" value="send">
	</form>
</body>
<script type="text/javascript" src="sample.js"></script>
</html>
```
```sample.js
// formオブジェクトの作成
var form = document.getElementById("form");
// submitイベントが起きたときの処理
form.addEventListener("submit", function(event) {
	// formのデフォルト機能を消し，JavaScriptで管理
	event.preventDefault();
	check();
});

function check() {
	// formのvalueを取ってくる
	var username = document.getElementById("username").value;
	var password = document.getElementById("password").value;
	var image = document.getElementById("image").value;

	// 入力されている場合送信
	if (username != "" && password != "" && image != "") {
		sendData();
	} else {
		alert("Please input all item.");
	}
}

function sendData() {
	// FormDataオブジェクトの作成
	var formData = new FormData(form);
	// XMLHttpRequestでHTTPリクエストを送る
	var req = new XMLHttpRequest();

	// 送受信が完了した場合の処理
	req.addEventListener("load", function(event) {
		if (req.responseText == "done") {
			// 処理が成功した場合の処理
			alert("Succeeded.");
		} else {
			alert("Failed to send.");
		}
	});
	// 送受信が失敗した場合の処理
	req.addEventListener("error", function(event) {
		alert("Error");
	});

	// HTTPリクエストの設定
	req.open("POST", "/sample/SampleServlet"); // /application名/Servlet名
	// リクエストを送信
	req.send(formData);
}
```
```SampleServlet.java
package controller;

import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

@WebServlet("/SampleServlet")
public class SampleServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public SampleServlet() {
        super();
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("utf-8");

		// レスポンステキスト
		String responseText = "done";

		// DiskFileItemFactoryからServletFileUploadを作成
		DiskFileItemFactory factory = new DiskFileItemFactory();
		ServletFileUpload sfu = new ServletFileUpload(factory);

		try {
			// リクエストのformデータをリストに格納
			List list = sfu.parseRequest(request);
			for (Object o : list) {
				// formデータの1つ1つをFileItemとして扱う
				FileItem item = (FileItem) o;
				// FileItemがフィールドかファイルかで分岐
				if (item.isFormField()) {
					// フィールドの場合，フィールドネームとvalueを取得
					// (form内の<input name="フィールドネーム" value="入力内容")
					String fieldName = item.getFieldName();
					String value = item.getString();
					if (fieldName.equals("username")) {
						// ユーザネームの処理
						System.out.println("username : " + value);
					} else if (fieldName.equals("password")){
						// パスワードの処理
						System.out.println("password : " + value);
					}
				} else {
					File file = new File(item.getName());
					try {
						String dirPath = "イメージファイルの保存先";
						String filePath = dirPath + File.separator + file.getName();
						// イメージファイルを保存
						item.write(new File(filePath));
					} catch (IOException e) {
						e.printStackTrace();
						responseText = "error";
					} catch (Exception e) {
						e.printStackTrace();
						responseText = "error";
					}
				}
			}
		} catch (FileUploadException e) {
			e.printStackTrace();
			responseText = "error";
		}
		// レスポンスの送信
		response.getWriter().print(responseText);
	}
}

```

#6. 結果
入力されていない場合
<img width="711" alt="スクリーンショット 2018-12-29 17.49.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/319282/ebcd0b36-7ad2-9662-f6a2-6ae5fd0270f1.png">


全項目が入力されている場合
<img width="712" alt="スクリーンショット 2018-12-29 17.49.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/319282/2db199e6-b36e-9d32-9402-e806fbbd16f7.png">



#5. 参考
JavaScript
- XMLHttpRequest : https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
- FormDataオブジェクト : https://developer.mozilla.org/ja/docs/Web/Guide/Using_FormData_Objects

Servlet
- commons fileUpload
    - http://commons.apache.org/proper/commons-fileupload/using.html
    - https://www.javadrive.jp/servlet/fileupload_tutorial/index4.html
    - https://www.javadrive.jp/servlet/fileupload_tutorial/index5.html
    - http://www.ne.jp/asahi/hishidama/home/tech/apache/commons/fileup.html