大きなファイルをJavaScriptで分割アップロードする

# FileReader関数を利用して分割アップロードする
アップロード前に画像のプレビューを表示するくらいしか使い道が思いつかなかったFileReader関数ですが、実用的な使途がありました。

## サーバーの制限を回避する
php.iniなど設定を変更できないサーバーを使っている場合、タイムアウトやメモリ－不足などでアップロードが失敗してしまいます。
`<?php
ini_set('upload_max_filesize', '128M');
ini_set('max_execution_time', '120');
?>`
などの記述で回避できる場合もありますが、これらを書いても効かない場合もあります。ならば分割して少しずつ送信すれば良いわけです。

アップロードがメインになるようなサービスを作ろうというわけではないので、極力シンプルに書いてみたのが、以下のコードです。

### ブラウザーからの分割アップロード(html部分)
```html:FileReader.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>dividing_upload</title>
<script src="./dividing_upload.js" type="text/javascript"></script>
</head>
<body>
	<form>
		<input id="file" name="file" type="file" />
	</form>
	<span id="msg"></span>
	<form>
		<input type="text" id="from" name="from" readonly  />
		<textarea id="data" name="data" readonly></textarea>
	</form>
</body>
</html>
```
実用上は、2つめのformは非表示にした方が良いでしょうね。

###  ブラウザーからの分割アップロード(js部分)
```javascript:dividing_upload.js
var	cnt=0;
var	total=0;

function post(result,from,size)
{
	plus=0;
	data=result.substr(from,size);
	if(data.substr(size-1,1)=="\r"){	// \r\nを分断すると\nを補ってくれちゃうので
		plus=1;
		data=result.substr(from,size+plus);
	}
	document.querySelector('#data').value=data;
	document.querySelector('#from').value=from;
	postForm('FileReaderPOST.php',document.forms[1],function(responseText){
		if(responseText==''){
			cnt+=data.length;
			if(cnt<total){	// 2回め以降の送信
				document.querySelector('#msg').innerHTML =""+cnt+"/"+total;
				post(result,from+size+plus,size);
			}else{			// 完了時のリセット
				document.forms[0].reset();
				document.forms[1].reset();
				cnt=0;
				total=0;
				document.querySelector('#msg').innerHTML ='completed => <a target="_blank" href="FileReaderPOST.txt">download</a><br />';
			}
		}else{
			alert(responseText);
		}
	});
}
function postForm(uri,formNode,callback1)
{
	form = new FormData(formNode);
	var xhr = new XMLHttpRequest();
	xhr.open('POST', uri);
	xhr.onreadystatechange=function(){
		if(xhr.readyState == 4 && xhr.status == 200){
			callback1(xhr.responseText);
		}
	};
	xhr.send(form);
}
window.addEventListener('DOMContentLoaded', function() {
	document.querySelector("#file").addEventListener('change', function(e) {
		if (window.File) {
			var input = document.querySelector('#file').files[0];
			var reader = new FileReader();
			reader.addEventListener('load', function(e) {
				base64=btoa(reader.result);	// 文字バケ防止のエンコード
				post(base64,0,32768);		// 初回送信、分割サイズ指定
				total=base64.length;
				document.querySelector('#msg').innerHTML ="送信中";
			}, true);
			reader.readAsBinaryString(input);
		}
	}, true);
});
```
[FileReaderを使う部分はコチラをベースに使いました](https://thinkit.co.jp/story/2013/02/06/3953?page=0%2C2)

### 分割をまとめるため、サーバー側で再結合する処理が必要です。
```php:FileReaderPOST.php
<?php
	$filename='FileReaderPOST.txt';	// とりあえず固定値
	if(is_file($filename)){
		if($_POST['from']==0){		// 初回送信なら前回のファイルを削除
			$data='';
			unlink($filename);
		}else{
			$data=file_get_contents($filename);
		}
	}else{
		$data='';
	}
	file_put_contents(	// デコードしてから追加書き込み
		$filename,
		$data.base64_decode ($_POST['data']));
?>
```
file_put_contentsじゃなくてfopen("……","a")にしろ、ってところでしょうか。要するにデコードして追加書き込みをするだけなので、phpでなくても構いません。
"$filename"がハードコーディングで".txt"になっていますが、エンコードされるのでバイナリーデータも送信できます。その場合はform内にファイルタイプを追加して、拡張子を変更させれば良いでしょう。