EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

Spring Boot でファイルのアップロードを実装する

EatSmart社が提供しているクスパというサービスで、動画の配信を行う事になりました。 今回は、動画をアップロードするAPIを実装した上でのメモになります。

cookingschool.jp

動画をアップロードする

APIの構成は、[nginx]->[Spring Boot] のようにしました。 Spring Boot でファイルをアップロードするにあたり、まずは MultipartFile を利用して実装してみました。

@RequestMapping(value = "/api/upload", method = { RequestMethod.POST })
public Result upload(@RequestParam("file") MultipartFile multipartFile) throws IOException {
    if (multipartFile.isEmpty()) {
        return new Result("failure");
    }

    try {
        final File file = new File("/upload/" + multipartFile.getOriginalFilename());
        byte[] bytes = multipartFile.getBytes();
        Files.write(file.toPath(), bytes);

        return new Result("success");
    } catch (Exception e) {
        return new Result("failure");
    }
}

nginxへAPIへ接続する設定を追加して、動作を確認することができました。

バッファリングをやめる

API単独のテストができたので、APIを利用するサービスと連携したテストを開始しました。 ここで、サービスがサポートする最大サイズの動画をアップロードすると、処理に非常に時間がかかることがわかりました。 原因を調査するためログを確認すると、nginxとSpring Bootでリクエストを受け取る時間に差があることがわかりました。 このことから、nginxがアップロードされたファイルをバッファリングしていることが予想できました。 そこで、以下のようにバッファリングを無効にする設定を追加しました。

proxy_request_buffering off;

ここで再度テストを行うと、処理にかかる時間は多少改善されましたが、まだ時間がかかります。 そこで、次にSpring Boot側の処理を調査すると、ここでもアップロードされたファイルのバッファリングが行われていることがわかりました。 このことから、ファイルのバッファリングを行わない実装に切り替えることにしました。 今回はcommons-fileuploadを利用して、以下のように改修を行いました。

@RequestMapping(value = "/api/upload", method = { RequestMethod.POST })
public Result upload(HttpServletRequest request) throws IOException, FileUploadException {
    boolean isMultipart = ServletFileUpload.isMultipartContent(request);
    if (!isMultipart) {
        return new Result("failure");
    }

    ServletFileUpload upload = new ServletFileUpload();
    FileItemIterator iterStream = upload.getItemIterator(request);

    while (iterStream.hasNext()) {
        FileItemStream item = iterStream.next();

        try {
            final File file = new File("/upload/" + item.getName());
            try (InputStream uploadedStream = item.openStream();
                    OutputStream out = new FileOutputStream(file)) {
                IOUtils.copy(uploadedStream, out);
            }

            return new Result("success");
        } catch (Exception e) {
            return new Result("failure");
        }
    }

    return null;
}

再びテストを行うと、処理にかかる時間が改善されたことを確認できました。