この記事では、Ajaxリクエストを使用して、SpringブートWebアプリケーション(REST構造)にファイルをアップロードする方法を説明します。

この記事で使用されているツール:

  1. 春のブート1.4.3.RELEASE

  2. Spring 4.3.5.RELEASE

  3. タイメレフ

  4. jQuery(webjars)

  5. Maven

  6. 埋め込みTomcat 8.5.6

  7. Google Chromeブラウザ(ネットワーク検査)

1.プロジェクトの構成

標準のMavenプロジェクト構造。


spring-boot-file-upload-ajax-directory-1

プロジェクトの依存関係

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つの方法を示しています。

  1. 単一ファイルアップロード –

    MultipartFile

  2. 複数ファイルのアップロード –

    MultipartFile[]

  3. ファイルをモデルにマップする –

    @ 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リクエストを発行します。


spring-boot-file-upload-ajax-1

7.2 Google Chromeでは、「ネットワーク検査」でリクエストと応答を確認します。


spring-boot-file-upload-ajax-2

7.3 Google Chrome、「リクエストペイロード」


spring-boot-file-upload-ajax-3

8. cURLテスト

`cURL`コマンドでより多くのテスト。

8.1単一ファイルのアップロードをテストします。

ターミナル

$ curl -F [email protected]"f:\\data.txt" http://localhost:8080/api/upload/Successfully uploaded - data.txt

8.2複数ファイルのアップロードをテストする。

ターミナル

$ curl -F extraField="abc" -F [email protected]"f://data.txt" -F [email protected]"f://data2.txt"  http://localhost:8080/api/upload/multi/Successfully uploaded - data.txt , data2.txt

8.3複数ファイルのアップロードをテストし、モデルにマップします。

ターミナル

$ curl -F extraField="abc" -F [email protected]"f://data.txt" -F [email protected]"f://data2.txt"  http://localhost:8080/api/upload/multi/model
Successfully uploaded!

8.4大きなムービーファイル(100MB)をテストすると、次のエラーメッセージが表示されます。

ターミナル

$ curl -F [email protected]"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 [email protected]"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


ajax


curl

リンク://タグ/ファイルアップロード/[ファイルアップロード]リンク://タグ/jquery/[jquery]

multipart


rest


spring boot