Springブートファイルのアップロードの例 – AjaxとREST
この記事では、Ajaxリクエストを使用して、SpringブートWebアプリケーション(REST構造)にファイルをアップロードする方法を説明します。
この記事で使用されているツール:
-
春のブート1.4.3.RELEASE
-
Spring 4.3.5.RELEASE
-
タイメレフ
-
jQuery(webjars)
-
Maven
-
埋め込みTomcat 8.5.6
-
Google Chromeブラウザ(ネットワーク検査)
1.プロジェクトの構成
標準のMavenプロジェクト構造。
プロジェクトの依存関係
HTML形式のAjaxリクエストに対して、追加の
jQuery
Webjar依存関係を宣言します。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4__0__0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mkyong</groupId> <artifactId>spring-boot-file-upload</artifactId> <packaging>jar</packaging> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- hot swapping, disable cache for template, enable live reload --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.2.4</version> </dependency> </dependencies> <build> <plugins> <!-- Package as an executable jar/war --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.ファイルアップロード
Ajaxの要求と応答をサポートするために、最も簡単な解決法が `ResponseEntity`を返します。
3.1以下の例は、ファイルをアップロードする3つの方法を示しています。
-
単一ファイルアップロード –
MultipartFile
-
複数ファイルのアップロード –
MultipartFile[]
-
ファイルをモデルにマップする –
@ ModelAttribute
RestUploadController.java
package com.mkyong.controller; import com.mkyong.model.UploadModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @RestController public class RestUploadController { private final Logger logger = LoggerFactory.getLogger(RestUploadController.class); //Save the uploaded file to this folder private static String UPLOADED__FOLDER = "F://temp//"; //3.1.1 Single file upload @PostMapping("/api/upload") //If not @RestController, uncomment this //@ResponseBody public ResponseEntity<?> uploadFile( @RequestParam("file") MultipartFile uploadfile) { logger.debug("Single file upload!"); if (uploadfile.isEmpty()) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfile)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD__REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK); } //3.1.2 Multiple file upload @PostMapping("/api/upload/multi") public ResponseEntity<?> uploadFileMulti( @RequestParam("extraField") String extraField, @RequestParam("files") MultipartFile[]uploadfiles) { logger.debug("Multiple file upload!"); //Get file name String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename()) .filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , ")); if (StringUtils.isEmpty(uploadedFileName)) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfiles)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD__REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadedFileName, HttpStatus.OK); } //3.1.3 maps html form to a Model @PostMapping("/api/upload/multi/model") public ResponseEntity<?> multiUploadFileModel(@ModelAttribute UploadModel model) { logger.debug("Multiple file upload! With UploadModel"); try { saveUploadedFiles(Arrays.asList(model.getFiles())); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD__REQUEST); } return new ResponseEntity("Successfully uploaded!", HttpStatus.OK); } //save file private void saveUploadedFiles(List<MultipartFile> files) throws IOException { for (MultipartFile file : files) { if (file.isEmpty()) { continue;//next pls } byte[]bytes = file.getBytes(); Path path = Paths.get(UPLOADED__FOLDER + file.getOriginalFilename()); Files.write(path, bytes); } } }
3.2上の例の単純なモデル3.1.3 –
@ ModelAttribute
UploadModel.java
package com.mkyong.model; import org.springframework.web.multipart.MultipartFile; public class UploadModel { private String extraField; private MultipartFile[]files; //getters and setters }
4.ビュー
複数のファイルをアップロードするためのHTMLフォーム。
upload.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot - Multiple file upload example - AJAX</h1> <form method="POST" enctype="multipart/form-data" id="fileUploadForm"> <input type="text" name="extraField"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="submit" value="Submit" id="btnSubmit"/> </form> <h1>Ajax Post Result</h1> <pre> <span id="result"></span> </pre> <script type="text/javascript" src="webjars/jquery/2.2.4/jquery.min.js"></script> <script type="text/javascript" src="js/main.js"></script> </body> </html>
5. jQuery – Ajaxリクエスト
jQueryを使用してフォームを取得し、Ajaxリクエストを介してマルチパートフォームデータを送信します。
resources/static/js/main.js
$(document).ready(function () { $("#btnSubmit").click(function (event) { //stop submit the form, we will post it manually. event.preventDefault(); fire__ajax__submit(); }); }); function fire__ajax__submit() { //Get form var form = $('#fileUploadForm')[0]; var data = new FormData(form); data.append("CustomField", "This is some extra data, testing"); $("#btnSubmit").prop("disabled", true); $.ajax({ type: "POST", enctype: 'multipart/form-data', url: "/api/upload/multi", data: data, //http://api.jquery.com/jQuery.ajax/ //https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using__FormData__Objects processData: false,//prevent jQuery from automatically transforming the data into a query string contentType: false, cache: false, timeout: 600000, success: function (data) { $("#result").text(data); console.log("SUCCESS : ", data); $("#btnSubmit").prop("disabled", false); }, error: function (e) { $("#result").text(e.responseText); console.log("ERROR : ", e); $("#btnSubmit").prop("disabled", false); } }); }
6.例外ハンドラ
Ajaxリクエストからの例外を処理するために、
ResponseEntityExceptionHandler`を拡張し、
ResponseEntity`を返してください。
RestGlobalExceptionHandler.java
package com.mkyong.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; //http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { //Catch file size exceeded exception! @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(ex.getMessage(), status); //example //return new ResponseEntity("success", responseHeaders, HttpStatus.OK); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status__code"); if (statusCode == null) { return HttpStatus.INTERNAL__SERVER__ERROR; } return HttpStatus.valueOf(statusCode); } }
デモ
デフォルトの埋め込みTomcat `mvn spring-boot:run`でSpringブートを開始します。
7.1
http://localhost:8080/
にアクセスし、いくつかのファイルを選択し、submitをクリックしてajaxリクエストを発行します。
7.2 Google Chromeでは、「ネットワーク検査」でリクエストと応答を確認します。
7.3 Google Chrome、「リクエストペイロード」
8. cURLテスト
`cURL`コマンドでより多くのテスト。
8.1単一ファイルのアップロードをテストします。
ターミナル
$ curl -F file=@"f:\\data.txt" http://localhost:8080/api/upload/Successfully uploaded - data.txt
8.2複数ファイルのアップロードをテストする。
ターミナル
$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/Successfully uploaded - data.txt , data2.txt
8.3複数ファイルのアップロードをテストし、モデルにマップします。
ターミナル
$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/model Successfully uploaded!
8.4大きなムービーファイル(100MB)をテストすると、次のエラーメッセージが表示されます。
ターミナル
$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/Attachment size exceeds the allowable limit! (10MB)
9. cURLテスト+カスタムエラーオブジェクト
9.1エラーの詳細を格納するオブジェクトを作成します。
CustomError.java
package com.mkyong.exception; public class CustomError { String errCode; String errDesc; public CustomError(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } //getters and setters }
9.2グローバル例外ハンドラを更新して、 `CustomError`オブジェクトをサポートします。
RestGlobalExceptionHandler.java
package com.mkyong.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(new CustomError("0x000123", "Attachment size exceeds the allowable limit! (10MB)"), status); //return new ResponseEntity("Attachment size exceeds the allowable limit! (10MB)", status); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status__code"); if (statusCode == null) { return HttpStatus.INTERNAL__SERVER__ERROR; } return HttpStatus.valueOf(statusCode); } }
9.3 cURLを実行して大きなファイルを再度アップロードします。
ターミナル
$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ {"errCode":"0x000123","errDesc":"Attachment size exceeds the allowable limit! (10MB)"}
完了しました。フィードバックは大歓迎です。
10.ソースコードをダウンロードする
ダウンロード:
spring-boot-file-upload-ajax-rest.zip
(11 KB)
参考文献
ブート – エラー処理]。 link://spring-boot/spring-boot-file-upload-example/[Springブートファイル
アップロードの例]。 link://spring-mvc/spring-mvc-file-upload-example/[Spring MVCファイル
アップロードの例]。リンク://spring-boot/spring-boot-hello-world-example-thymeleaf/[Spring
ブートHello Worldの例 – Thymeleaf]。
Wikipedia – cURL
-
https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using
FormData
Objects[MDN
ajax
curl
リンク://タグ/ファイルアップロード/[ファイルアップロード]リンク://タグ/jquery/[jquery]
multipart
rest
spring boot