JSPとServletでファイルアップロード

JSP と Servlet 使ってファイルアップロードをやってみる

# アップロードでやるざっくり内容

- フォルダ
    - アップロード用のフォルダ作成 `/java-webapp-clump/src/main/webapp/upload`
    - tomcat で読み込まれるように blankファイルを配置
- JSP
    - 入力送信用のformタグに `enctype="multipart/form-data"` を追加
    - 入力送信用のformタグの中にある input タグに `type="file"` を追加
    - アップロード表示用のタグを配置 (ここでは 画像 img, 動画(・音声) video のサンプルでやってみます )
- Servlet
    - クラスのアノテーションに `@MultipartConfig` を追加
    - `request.getPart("JSPで設定したname")` で入力したファイルを取得
    - `getServletContext()` でサーバに展開してるフォルダパスを取得し `/upload/アップロードファイル名` で保存
    - JSP に保存したファイルパス ( `/upload/アップロードファイル名` ) を返す

※アップロード直後に表示すると保存・転送が間に合わずにアップロードファイルが開けない．．．
※リロードすればそのうち取得できます
※サンプルではJavaScriptの処理いれて簡易なローディングっぽいの差し込んでみてます．．．

# JSP, Servlet 使ってファイルアップロード(ファイル保存して表示)
ここで使ってるサンプルのおおもとはこちら [JSP と Servlet ざっくりまとめ](https://qiita.com/sueasen/items/37d97ebda6117f6f63c3) を参考にお願いします

## 1. アップロードファイルを保存するフォルダを作成

- `/java-webapp-clump/src/main/webapp/` の直下に フォルダ `upload` を作成
- フォルダ `upload` の直下に blank ファイルを作成
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/acbadb03-22ed-dcc8-7833-2b69803ba208.png)

## 2. JSP : 入力送信用のタグ(form タグ) にファイルアップロード設定, アップロード表示用のタグを配置

- `/java-webapp-clump/src/main/webapp/WEB-INF/view/uploadFile.jsp` を作成
- form タグの `action="upload_file"`, `method="post"`,  `enctype="multipart/form-data"` を設定
- form タグの中の input タグに `type="file"` を追加, 必須チェックとして `required` を設定

```JSP:uploadFile.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page isELIgnored="false" %>

<!doctype html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>JavaDevelop</title>
    <link rel="shortcut icon" href="<c:url value="/assets/favicon.ico" />">
    <link rel="stylesheet" href="<c:url value="/assets/bootstrap.min.css" />">
    <link rel="stylesheet" href="<c:url value="/assets/bootstrap-icons.css" />">
    <link rel="stylesheet" href="<c:url value="/assets/styles.css" />">
</head>

<body>

    <%@ include file="./_headerNavbar.jsp" %>

    <main>
        <div class="container workspace">
            <form action="upload_file" method="post" enctype="multipart/form-data">
                <label>ファイル 画像：</label>
                <input type="file" name="img" />
                <br/>
                <label>ファイル 音声：</label>
                <input type="file" name="audio" />
                <br/>
                <label>ファイル 動画：</label>
                <input type="file" name="video" />
                <br/>
                <input type="submit" />
            </form>
            <hr/>
            <div class="upload">
                <p>アップロードファイル：画像</p>
                <c:if test="${!empty resultDto.imgPath}">
                    <div class="spinner-border d-none uploadFileLoading"></div>
                    <img class="uploadFile" src="<c:url value="${resultDto.imgPath}" />" />
                </c:if>
            </div>
            <hr/>
            <div class="upload">
                <p>アップロードファイル：音声</p>
                <c:if test="${!empty resultDto.audioPath}">
                    <div class="spinner-border d-none uploadFileLoading"></div>
                    <audio class="uploadFile" src="<c:url value="${resultDto.audioPath}" />" controls></audio>
                </c:if>
            </div>
            <hr/>
            <div class="upload">
                <p>アップロードファイル：動画</p>
                <c:if test="${!empty resultDto.videoPath}">
                    <div class="spinner-border d-none uploadFileLoading"></div>
                    <video class="uploadFile" src="<c:url value="${resultDto.videoPath}" />" controls></video>
                </c:if>
            </div>
            <hr/>
        </div>
    </main>

    <footer class="footer mt-auto fixed-bottom py-3 bg-secondary"></footer>

    <script src="<c:url value="/assets/bootstrap.bundle.min.js" />"></script>
    <script src="<c:url value="/assets/script.js" />"></script>
</body>

</html>
```

## 3. Servlet : ファイル保存してJSPに保存したファイルを返す

- `/java-webapp-clump/src/main/java/presentation/servlet/UploadFileServlet.java` を作成
- クラスのアノテーションに `@WebServlet("/uplaod_file")`, `@MultipartConfig` を設定
- `Part part = request.getPart("JSPで設定した name ");` で入力したファイルを取得
- `String filePath = "/upload/" + part.getSubmittedFileName();` でJSPで使うファイルパスを作成
- `part.write(getServletContext().getRealPath(filePath));` でアップロードしたファイルをサーバに保存
- `request.setAttribute("resultDto", new Dto(filePath));` でファイルパスを DTO に設定して JSP に返す
    ※ DTO(UploadFile.java) を作成, インスタンス変数 `imgPath, audioPath, videoPath` を追加してコンストラクタ、getter(, setter) を追加しておく

```Java:UplaodFileServlet.java
package presentation.servlet;

import java.io.IOException;
import java.util.function.Consumer;

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

import dto.UploadFile;

/**
 * {@link UploadFileServlet}
 */
@WebServlet("/upload_file")
@MultipartConfig
public class UploadFileServlet extends HttpServlet {

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String view = "/WEB-INF/view/uploadFile.jsp";
		RequestDispatcher dispatcher = request.getRequestDispatcher(view);
		dispatcher.forward(request, response);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");

		UploadFile dto = new UploadFile();
		fileUpload(request.getPart("img"), (s) -> dto.setImgPath(s));
		fileUpload(request.getPart("audio"), (s) -> dto.setAudioPath(s));
		fileUpload(request.getPart("video"), (s) -> dto.setVideoPath(s));
		request.setAttribute("resultDto", dto);

		doGet(request, response);
	}

	/**
	 * ファイルアップロードしてファイルパスを設定します
	 *
	 * @param part パート
	 * @param function ファイルパス設定処理
	 * @throws IOException ファイル書込時の例外
	 */
	private void fileUpload(Part part, Consumer<String> function) throws IOException {
		if ("".equals(part.getSubmittedFileName())) {
			return;
		}
		String filePath = "/upload/" + part.getSubmittedFileName();
		part.write(getServletContext().getRealPath(filePath));
		function.accept(filePath);
	}
}
```

```Java:UploadFile.java
package dto;

/**
 * {@link UploadFile}
 */
public class UploadFile {

	/** 画像パス */
	private String imgPath;

	/** 音声パス */
	private String audioPath;

	/** 動画パス */
	private String videoPath;

	/**
	 * コンストラクタ
	 */
	public UploadFile() {
	}

	/**
	 * コンストラクタ
	 * @param imgPath 画像パス
	 * @param audioPath 音声パス
	 * @param videoPath 動画パス
	 */
	public UploadFile(String imgPath, String audioPath, String videoPath) {
		this.imgPath = imgPath;
		this.audioPath = audioPath;
		this.videoPath = videoPath;
	}

	/**
	 * @return imgPath
	 */
	public String getImgPath() {
		return imgPath;
	}

	/**
	 * @param imgPath セットする imgPath
	 */
	public void setImgPath(String imgPath) {
		this.imgPath = imgPath;
	}

	/**
	 * @return audioPath
	 */
	public String getAudioPath() {
		return audioPath;
	}

	/**
	 * @param audioPath セットする audioPath
	 */
	public void setAudioPath(String audioPath) {
		this.audioPath = audioPath;
	}

	/**
	 * @return videoPath
	 */
	public String getVideoPath() {
		return videoPath;
	}

	/**
	 * @param videoPath セットする videoPath
	 */
	public void setVideoPath(String videoPath) {
		this.videoPath = videoPath;
	}

}

```

## 4. ここまでやったら動作確認

- http://localhost:8080/java-wabapp-clump/upload_file をブラウザで表示
- ファイルを選択して送信を実行
- ブラウザ表示でエラーがないのにアップロードファイル表示されない時は更新すれば表示されるはず

## XX. アップロードファイルが表示されるように自動更新を JavaScript でやってみる

`uploadFile.jsp` にローディング用のタグはすでに設定済みなので JavaScript のみ対応を追加

### XX-1. JavaScript にアップロードファイル更新の処理を追加 

- `/java-webapp-clump/src/main/webapp/assets/script.js` を修正・追記
- html 読込時の処理 に function(メソッド)呼び出しを追加 `applyUploadFileWait();`
- function(メソッド) ファイル更新待ち設定 `applyUploadFileWait()` 作成
- function(メソッド) ファイル更新 `updateSrc(dom)` 作成
- function(メソッド) ファイル表示 `loadSrc(dom, domLoading)` 作成

```JavaScript:script.js

・
・
/**
 * html 読込時の処理
 */
window.addEventListener('load', (e) => {
  pageLinks.forEach((links, i) => {
    applyNavbar(links);
    applyCarousel(links, i);
  });
  // 追加するのは下の行のfunction(メソッド)呼び出し
  applyUploadFileWait();
});
・
・
// function(メソッド)を追加
/**
 * アップロードファイル更新待ち設定
 */
function applyUploadFileWait() {
  document.querySelectorAll('.upload').forEach(uploadDom => {
    let dom = uploadDom.querySelector('.uploadFile');
    if (!dom || !['IMG', 'VIDEO', 'AUDIO'].includes(dom.tagName)) return;

    let domLoding = uploadDom.querySelector('.uploadFileLoading');
    dom.onerror = () => updateSrc(dom);
    if (dom.tagName == 'IMG') {
      dom.onload = () => loadSrc(dom, domLoding);
    } else {
      dom.onloadedmetadata = () => loadSrc(dom, domLoding);
    }

    dom.classList.add('d-none');
    domLoding.classList.remove('d-none');
    updateSrc(dom);
  });
}

/**
 * ファイル更新
 * 
 * @param dom
 *            メディアDOM(img, video など)
 */
function updateSrc(dom) {
  let src = dom.src;
  dom.src = '';
  dom.src = src;
}

/**
 * ファイル表示
 * 
 * @param dom
 *            メディアDOM(img, video など)
 * @param domLoding
 *            ローディングDOM
 */
function loadSrc(dom, domLoding) {
  domLoding.classList.add('d-none');
  dom.classList.remove("d-none");
}

```

### XX-2. ここまでやったら動作確認

- http://localhost:8080/java-wabapp-clump/upload_file をブラウザで表示
- ファイルを選択して送信を実行
- ローディングが表示されてアップロードファイルがそのうち表示されるはず

### ここから先はご自由に！

画像、動画、音声を分けて登録できるようにするとかでそれっぽくなりそう
いれたやつの一覧表示とか．．．

# JSP, Servlet 使ってファイルアップロード(サンプル WorkServlet.java, work.jsp に追加)
ここで使ってるサンプルのおおもとはこちら [JSP と Servlet ざっくりまとめ](https://qiita.com/sueasen/items/37d97ebda6117f6f63c3) を参考にお願いします
WorkServlet.java, work.jsp に追加する形で進めてます

## 1. アップロードファイルを保存するフォルダを作成

- `/java-webapp-clump/src/main/webapp/` の直下に フォルダ `upload` を作成
- フォルダ `upload` の直下に blank ファイルを作成
   ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/acbadb03-22ed-dcc8-7833-2b69803ba208.png)

## 2. JSP : 入力送信用のタグ(form タグ) にファイルアップロードの設定を追加

- `/java-webapp-clump/src/main/webapp/WEB-INF/view/work.jsp` を修正(mainタブのdivタブ(container workspace)の中)
- form タグに `enctype="multipart/form-data"` を追加
- form タグの中の input タグに `type="file"` を追加, 必須チェックとして `required` を設定

```JSP:work.jsp
            <!-- 追加するのは下の行の enctype="multipart/form-data" の部分 -->
            <form action="web_app_work" method="post" enctype="multipart/form-data">
                <label>入力：</label>
                <input type="text" name="input1" />
                <!-- 追加するのはここから4行 input タブの name はなんでもOK, ここでは次の Servlet と合わせて file1 にしてる -->
                <br/>
                <label>ファイル：</label>
                <input type="file" name="file1" required />
                <br/>
                <!-- 追加するのはここまで -->
                <input type="submit" />
            </form>
```

## 3. Servlet : ファイル保存してJSPに保存したファイルを返す

- `/java-webapp-clump/src/main/java/presentation/servlet/WorkServlet.java` を修正(doPostメソッドの中)
- クラスのアノテーションに `@MultipartConfig` を追加
- `Part part = request.getPart("file");` で入力したファイルを取得
- `String filePath = "/upload/" + part.getSubmittedFileName();` でJSPで使うファイルパスを作成
- `part.write(getServletContext().getRealPath(filePath));` でアップロードしたファイルをサーバに保存
- `request.setAttribute("resultDto", new Work(input1, filePath));` でファイルパスを DTO に設定して JSP に返す
    ※ DTO にインスタンス変数 `filePath` を追加してコンストラクタ、getter(, setter) を追加しておく

```Java:WorkServlet.java

@WebServlet("/web_app_work")
@MultipartConfig // この行を追加
public class WorkServlet extends HttpServlet {

・
・
・

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");

		String input1 = request.getParameter("input1");
		request.setAttribute("result1", "入力したのは " + input1);

		// 追加・修正するのはここから6行, コメントアウトしてる sysout は実際の保存するファイルパス確認用
		Part part = request.getPart("file1");
		String filePath = "/upload/" + part.getSubmittedFileName();
		part.write(getServletContext().getRealPath(filePath));
		// System.out.println(getServletContext().getRealPath(filePath));

		request.setAttribute("resultDto", new Work(input1, filePath));
		// 追加・修正するのはここまで

		doGet(request, response);
	}

・
・
・

}
```

念のため Work.java の修正・追加箇所をピックアップ
```Java:Work.java

	/** ファイルパス */
	private String filePath;

	/**
	 * コンストラクタ
	 * @param input 入力値
	 * @param input ファイルパス
	 */
	public Work(String input, String filePath) {
		this.input = input;
		this.filePath = filePath;
	}

	/**
	 * filePath を取得します
	 * @return filePath
	 */
	public String getFilePath() {
		return filePath;
	}

	/**
	 * filePath を設定します
	 * @param filePath ファイルパス
	 */
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

```

## 4. JSP : 表示用のタグを配置（画像 img, 動画(・音声) video）

- `/java-webapp-clump/src/main/webapp/WEB-INF/view/work.jsp` を修正(mainタブのdivタブ(container workspace)の中)
- taglibの `c:if` を使って `resultDto.filePath` があるときのみ表示用タグを配置するようにする
- 画像アップロードのときは imgタグ、動画(・音声)アップロードのときは videoタグを使う
- img or video タグの src に `<c:url value="${resultDto.filePath}" />` を設定してアップロード保存したファイルを指定

```JSP:work.jsp
    <main>
        <div class="container workspace">
            ・
            ・
            ・
            <!-- 追加するのは divタグ(container workspace) の最後のところ -->
            <hr/>
            <p>アップロードファイルを表示してみる</p>
            <c:if test="${!empty resultDto.filePath}">
                <!-- 画像アップロードのとき ※どっちかのみ残す-->
                <img class="uploadFile" src="<c:url value="${resultDto.filePath}" />" />
                <!-- 動画(・音声)アップロードのとき ※どっちかのみ残す -->
                <video class="uploadFile" src="<c:url value="${resultDto.filePath}" />" controls></video>
            </c:if>

        </div>
    </main>
```

## 5. ここまでやったら動作確認

- http://localhost:8080/java-wabapp-clump/web_app_work をブラウザで表示
- ファイルを選択して送信を実行
- ブラウザ表示でエラーがないのにアップロードファイル表示されない時は更新すれば表示されるはず

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/29edbc13-1467-3a4e-79f7-28190359a2e3.png)


## XX. アップロードファイルが表示されるように自動更新を JavaScript でやってみる

### XX-1. JSP にローディングの枠を追加 

- `/java-webapp-clump/src/main/webapp/WEB-INF/view/work.jsp` を修正(mainタブのdivタブ(container workspace)の中)
- ローディング表示のdivタグ `<div class="spinner-border d-none uploadFileLoading"></div>` を配置
- メディアタグ、ローディングタグをdivタグ `<div class="upload">・・・</div>` で括る

```JSP:work.jsp
    <main>
        <div class="container workspace">
            ・
            ・
            ・
            <hr/>
            <div class="upload">
                <p>アップロードファイルを表示してみる</p>
                <c:if test="${!empty resultDto.filePath}">
                    <!-- 追加するのは以下の行のみ class="spinner-border" がローディングの画像になる -->
                    <div class="spinner-border d-none uploadFileLoading"></div>
                    <!-- 画像アップロードのとき ※どっちかのみ残す -->
                    <img class="uploadFile" src="<c:url value="${resultDto.filePath}" />" />
                    <!-- 動画(・音声)アップロードのとき ※どっちかのみ残す -->
                    <video class="uploadFile" src="<c:url value="${resultDto.filePath}" />" controls></video>
                </c:if>
            </div>
        </div>
    </main>

```

### XX-2. JavaScript にアップロードファイル更新の処理を追加 

- `/java-webapp-clump/src/main/webapp/assets/script.js` を修正・追記
- html 読込時の処理 に function(メソッド)呼び出しを追加 `applyUploadFileWait();`
- function(メソッド) ファイル更新待ち設定 `applyUploadFileWait()` 作成
- function(メソッド) ファイル更新 `updateSrc(dom)` 作成
- function(メソッド) ファイル表示 `loadSrc(dom, domLoading)` 作成

```JavaScript:script.js

・
・
/**
 * html 読込時の処理
 */
window.addEventListener('load', (e) => {
  pageLinks.forEach((links, i) => {
    applyNavbar(links);
    applyCarousel(links, i);
  });
  // 追加するのは下の行のfunction(メソッド)呼び出し
  applyUploadFileWait();
});
・
・
// function(メソッド)を追加
/**
 * アップロードファイル更新待ち設定
 */
function applyUploadFileWait() {
  document.querySelectorAll('.upload').forEach(uploadDom => {
    let dom = uploadDom.querySelector('.uploadFile');
    if (!dom || !['IMG', 'VIDEO', 'AUDIO'].includes(dom.tagName)) return;

    let domLoding = uploadDom.querySelector('.uploadFileLoading');
    dom.onerror = () => updateSrc(dom);
    if (dom.tagName == 'IMG') {
      dom.onload = () => loadSrc(dom, domLoding);
    } else {
      dom.onloadedmetadata = () => loadSrc(dom, domLoding);
    }

    dom.classList.add('d-none');
    domLoding.classList.remove('d-none');
    updateSrc(dom);
  });
}

/**
 * ファイル更新
 * 
 * @param dom
 *            メディアDOM(img, video など)
 */
function updateSrc(dom) {
  let src = dom.src;
  dom.src = '';
  dom.src = src;
}

/**
 * ファイル表示
 * 
 * @param dom
 *            メディアDOM(img, video など)
 * @param domLoding
 *            ローディングDOM
 */
function loadSrc(dom, domLoding) {
  domLoding.classList.add('d-none');
  dom.classList.remove("d-none");
}

```

### XX-3. ここまでやったら動作確認

- http://localhost:8080/java-wabapp-clump/web_app_work をブラウザで表示
- ファイルを選択して送信を実行
- ローディングが表示されてアップロードファイルがそのうち表示されるはず

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/800a07cd-e72b-8dfa-3683-c4dfdd9b319b.png)

### ここから先はご自由に！

画像、動画、音声を分けて登録できるようにするとかでそれっぽくなりそう
いれたやつの一覧表示とか．．．

# JSP, Servlet 使ってファイルアップロード(ファイル保存せず表示)
ファイルを保存せずに直接、ファイルを img タグに表示する方法

### 1. JSP 作成
- ファイルアップロード `form action="url_path1" method="post" enctype="multipart/form-data"`
- ファイル表示 `<img src="url_path2" />`

```JSP:アップロード・ファイル表示
            <form action="url_upload" method="post" enctype="multipart/form-data">
                <label>ファイル：</label>
                <input type="file" name="file1" required />
                <input type="submit" />
            </form>
            <hr/>
            <img src="url_image" />
            <hr/>
```

### 2. Servlet 作成
- ファイルアップロード：画像を `byte[]` にする
- ファイル表示：`response.setContentType("image/png");` の設定, `BufferedImage` を書き込んで返す

```Java:アップロード
@WebServlet("/url_upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.setCharacterEncoding("UTF-8");
		Part part = request.getPart("file1");
		request.getSession().setAttribute("imgBytes", readAllBytes(part.getInputStream()));
		doGet(request, response);
	}

	public byte[] readAllBytes(InputStream inputStream) throws IOException {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		int nRead;
		byte[] data = new byte[16777215];
		while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
			buffer.write(data, 0, nRead);
		}
		return buffer.toByteArray();
	}
}
```

```Java:ファイル表示
@WebServlet("/url_image")
@MultipartConfig
public class ImageServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		InputStream inputStream = new ByteArrayInputStream((byte[]) request.getSession().getAttribute("imgBytes"));
		BufferedImage img = ImageIO.read(new BufferedInputStream(inputStream));

		response.setContentType("image/png");
		OutputStream outputStream = response.getOutputStream();
		ImageIO.write(img, "png", outputStream);
		outputStream.flush();
	}
}
```