work.log

エンジニアの備忘録的ブログ

HTTP経由で大容量のファイルをサーバに分割アップロードする

投稿:

とあるシステム制作の要件で、大容量の動画ファイルをサーバにアップロードする仕組みを実装する事になりました。

扱うファイルは mp4 形式の動画ファイルでサイズは 200MB – 1GB 程度とバラつきが大きく、アップロード後に FFmpeg を利用してサムネイル画像を作ったり、動画の再生時間を取得したりっていう処理を自動で走らせたい、という感じ。

ただし、サーバのメモリが小さいから WEB サーバ側のアップロード上限を上げて対応するってのは無しという事で他の方法を調べると「jQuery-File-Upload」という jQuery プラグインが使えそう!という事でちょっと試してみました。

スポンサーリンク

jQuery-File-Upload について

jQuery-File-Upload は下記 URL よりダウンロードが出来ます。

外部リンク
jQuery-File-Upload (GitHub)

これは ajax を利用した高機能なファイルアップローダーで、デモを見ると大体の感じがわかると思います。

外部リンク
jQuery-File-Upload デモ

jQuery-File-Upload を利用した分割アップロードサンプル

今回は jQuery-File-Upload の分割アップロード機能 (Chunked file upload) が使えそうという事で、あちこちを見ながらカスタマイズして WEB サーバに設置してみました。

WEB サーバは下記 Nginx 1.10.1 と PHP 7.0.8 で構成していて関連する設定値は下記の通りデフォルトのままなので、その状態でも動作するように設定しています。

  • Nginx のアップロード上限はデフォルトの 1MB (client_max_body_size)
  • php.ini に設定したアップロード上限はデフォルトの 2MB (upload_max_filesize)

とりあえずサンプルファイル一式は zip にまとめてアップしました。

中身は下記のようになっていて “upload.html” と “up.php” 以外は公式の配布ファイルから必要な物だけを取って再配置しただけです。

この zip ファイルを WEB サーバの公開領域に展開すればそのまま使えるはず。updir に WEB 経由からの書き込み権限を付与するのをお忘れなく。

sample/
│
├── css/
│   │
│   ├── jquery.fileupload.css
│   └── style.css
│
├── js/
│   │
│   └── jQuery-File-Upload/
│       │
│       └── js/
│           │
│           ├── app.js
│           │
│           ├── cors/
│           │   │
│           │   ├── jquery.postmessage-transport.js
│           │   └── jquery.xdr-transport.js
│           │
│           ├── jquery.fileupload-angular.js
│           ├── jquery.fileupload-audio.js
│           ├── jquery.fileupload-image.js
│           ├── jquery.fileupload-jquery-ui.js
│           ├── jquery.fileupload.js
│           ├── jquery.fileupload-process.js
│           ├── jquery.fileupload-ui.js
│           ├── jquery.fileupload-validate.js
│           ├── jquery.fileupload-video.js
│           ├── jquery.iframe-transport.js
│           ├── main.js
│           │
│           └── vendor/
│               │
│               └── jquery.ui.widget.js
│
├── updir/
│
├── UploadHandler.php
├── upload.html
└── up.php

動作イメージ

mp4 形式のファイルを選択するとすぐにアップロードが開始されます。

Firebug を覗いているとちゃんと分割されてアップロードされている様子が確認できます。

chunk-upload-01

Nginx 側のログはこんな感じに出力されます。

192.168.0.1 - - [18/Jul/2016:20:21:25 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:29 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:34 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:38 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:43 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:47 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:52 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"
192.168.0.1 - - [18/Jul/2016:20:21:56 +0900] "POST /up.php HTTP/1.1" 200 172 "http://example.com/upload.html" "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0"

プログレスバーが100%になるとアップロード完了で、完了後はダウンロードリンクが表示されます。

chunk-upload-02

upload.html の主要箇所

チャンクの設定等は upload.html 中に直接書いてあるのでその箇所を抜粋。

$(function () {
	$('.reload').hide();

	'use strict';
	var url = 'up.php';
	$('#fileupload').fileupload({
		url: url,
		dataType: 'json',
		maxChunkSize: 1047552,
		maxFileSize: 2147483648,
		acceptFileTypes: /\.mp4$/i,
	})
	.on('fileuploadstart', function (e) {
		$('.fileinput-button').hide();
		$('.reload').show();
	})
	.on('fileuploadprogressall', function (e, data) {
		var progress = parseInt(data.loaded / data.total * 100, 10);
		$('#progress .progress-bar').css(
			'width',
			progress + '%'
		);
		$('.progress-bar').text(progress + '%');
	})
	.on('fileuploaddone', function (e, data) {
		var file = data.result.files[0];
		console.log(file);
		$('.reload').text('Start Over');
		$('<p/>').text('[complete] File upload completed. File name: ' + file.name).appendTo('#files');
		$('#files').append('URL: <a href="' + file.url + '" target="_blank">' + file.url + '</a>');
		$('#progress .progress-bar').removeClass('progress-bar-striped active');
	})
	.on('fileuploadchunksend', function (e, data) {})
	.on('fileuploadchunkdone', function (e, data) {})
	.on('fileuploadchunkalways', function (e, data) {});
});
</script>

</body>
</html>

一番大事な部分は “maxChunkSize” でここを WEB サーバ側のアップロード上限に合わせる必要がある。Nginx は 1MB で PHP は 2MB なので、今回は小さい Nginx に合わせました。

設定を触れるならここはもう少し大きくした方が効率が良くなりそうですが、今回は WEB サーバ側を触らない前提だったのでこのような形に。

ちなみに、1MB だと 1048576Byte になるのですがそれだと Nginx で “413 Request Entity Too Large” が出てしまうため、1KB 引いて 1047552Byte にしています。

その他、”maxFileSize” でアップロード上限を 2GB までに制限したり、”acceptFileTypes” でアップロードできるファイルの種類を制限したりしています。

このファイルはここを参考にしました。

up.php の主要箇所

サーバ側の処理は UploadHandler.php を up.php で呼び出して利用しますが、アップロードディレクトリを変更する設定が用意されていないので少し改良をします。

UploadHandler.php を直接編集するのは避けたいので up.php で対応します。

これは下記参考ページをそのまま使わせて貰いました。

<?php
/*
 * jQuery File Upload Plugin PHP Example
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2010, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://www.opensource.org/licenses/MIT
 */

error_reporting(E_ALL | E_STRICT);

class myOptions {

	function get_server_var($id) {
		return isset($_SERVER[$id]) ? $_SERVER[$id] : '';
	}

	function get_full_url() {
		$https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0;
		return
		($https ? 'https://' : 'http://').
		(!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
		(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
		($https && $_SERVER['SERVER_PORT'] === 443 ||
			$_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
			substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/')
		);
	}

	function uploadDir(){
		return dirname($this->get_server_var('SCRIPT_FILENAME'));
	}

	function uploadUrl(){
		return $this->get_full_url();
	}

}

$myOptions = new myOptions();
$upDirName = 'updir';
$upDir = '/' . $upDirName . '/';

$options = array(
	'upload_dir' => $myOptions->uploadDir().$upDir,
	'upload_url' => $myOptions->uploadUrl().$upDir,
	'accept_file_types' => '/\.mp4$/i',
);

require('UploadHandler.php');
$upload_handler = new UploadHandler($options);

?>

一応ここでも “accept_file_types” でアップロードできるファイルを mp4 に限定しています。

とりあえずこんな感じです。

Nginx で「a client request body is buffered to a temporary file」の警告が出る問題

一応、大容量ファイルを WEB サーバの小さな制限内でアップロードするという目的は達成したのですが、このままだと Nginx のエラーログに下記のような警告メッセージが出力されます。

2016/07/18 20:32:08 [warn] 7805#0: *781 a client request body is buffered to a temporary file /home/www/tmp/client_body_temp/0000000225, client: 192.168.0.1, server: example.com, request: "POST /up.php HTTP/1.1", host: "example.com", referrer: "http://example.com/upload.html"

“client_body_buffer_size” で設定したバッファサイズを超えて “client_body_temp_path” 以下にファイルとして書きだされたという内容のメッセージなのですが、これを出力させないようにするには Nginx の “client_body_buffer_size” のサイズを upload.html で設定した “maxChunkSize” と同じにする必要がありそうです。(結局弄るのかよ・・・)

まぁ今回は、そこまで I/O を気にする事も無いので無視するというのも手ですが、バッファサイズを上げる以外には下記のような方法もあるらしいです。

ここはどうするかまだ決めていませんが、とりあえず分割アップロードはこんな感じに出来ました。

後はアップロード後に FFmpeg の処理と連携させるだけですがこれは試したらまた書きます。

スポンサーリンク

コメント

コメントを残す

よく読まれている記事

  • 今日
  • 週間
  • 月間