ファイルアップロード

以下に、<input type="file">によるファイルアップロード用タグを利用したHTMLファイルの内容を示します。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
</head>
<body bgcolor="lightblue" text="black" Link="navy" vLink="navy">

<form action="test.jsp" enctype="multipart/form-data" method="post">

  ユーザー<input type="text" name="usrname" size="40"><br>
  アップファイル<input type="file" name="upfilename" size="40"><br>
  送信元のOS: 
  <select name="ostype">
  <option value="MSWin32" selected>MS-Windows 
  <option value="MacOS">MacOS 
  <option value="">UNIX 
  </select><br>
  <input type="submit" value="送信実行"><br>
  <input type='hidden' name='op' value=''><br>

</form>
</body>
</html>

上記のsubmitボタンにより、サーバーに送信されるリクエストメッセージの例を示します。
multipart/form-dataを指定することで、様々なプロトコルで転送する事ができます。

POST /all.app.JWebExc.class HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Referer: http://192.168.0.33/all.app.JWebExc.class
Accept-Language: ja
Content-Type: multipart/form-data; boundary=---------------------------7d84e3b240534
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 2.0.50727)
Host: 192.168.0.33
Content-Length: 558
Connection: Keep-Alive
Cache-Control: no-cache


-----------------------------7d84e3b240534
Content-Disposition: form-data; name="usrname"

tarou
-----------------------------7d84e3b240534
Content-Disposition: form-data; name="upfilename"; filename="D:\work\test.txt"
Content-Type: text/plain

ABC WXYZ
 123
-----------------------------7d84e3b240534
Content-Disposition: form-data; name="ostype"

MSWin32
-----------------------------7d84e3b240534
Content-Disposition: form-data; name="op"


-----------------------------7d84e3b240534--

HTTPヘッダのContent-Length: 558で、ヘッダ直後のボディサイズが558byteであること分かる。
この例では、最初の-----------------------------7d84e3b240534から始まり、
最後の-----------------------------7d84e3b240534--までのボディ全体のサイズである。
上記の場合は、この間に3つの-----------------------------7d84e3b240534があり4つの情報を送っていることになる。
(Partを区切る文字列は、boundary=直後の文字列先頭に対して、"--" が多いことや、終端文字列は後ろに"--"が多いことに注意)

このように、それぞれHTTPのヘッダとボディがあり、異なる情報を送ることができるのがMultiPartの名の所以でしょう。
以下で、アップロードされたファイルをバイナリ情報として、アップされたファイル名でサーバ側に保存する簡単なtest.jspの例を示します。
まず、ボディサイズと、Partを区切る文字列の取得を行っておきます。次のボディ内のPartを操作する繰り返しです。

<%@page contentType="text/html; charset=Shift_JIS" language="java" 
import="java.io.*" 
import="java.util.*" 
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
</head>
<body>
<%
	String msg = "ファイルのアップロード失敗";//成功すれば変更される。

	//Partを区切る文字列の取得と、ボディサイズの取得を行っておきます。
	int sizeBody = request.getContentLength();//ボディサイズ
	String contentType = request.getContentType();
	int i =contentType.indexOf("boundary=") + "boundary=".length();
	String boundary=contentType.substring(i);//Partを区切る文字列取得
	boundary = "--" +boundary;//Partを区切る先頭文字列
	String boundaryEnd = boundary  +"--";//Part終了文字列

	ServletInputStream is = request.getInputStream();//HTTPレスポンスのボディを取得するストリーム

	byte [] binary = new byte[sizeBody];//読み取りバッファ用
	int count = 0;//読み取り済みバイト数のカウント用
	int numb = is.readLine(binary,0, sizeBody);//読み取ったバイト数の一時記憶用
	String s = new String(binary,0,numb);//Partの区切り文字のはず
	count+=numb;
	String filename=""; 
	for(;;){//Partを読む繰り返し

		// Part内ヘッダを読み取る繰り返し
		while((numb = is.readLine(binary,0, sizeBody-count )) != -1){
			s = new String(binary,0,numb);
			if(s.equals("\r\n")) break; //Part内ヘッダの終わり
			i =s.indexOf("name=\"upfilename\"");
			if(i != -1){//アップロードファイルがあるPartか?
				i=s.indexOf("filename=\"");
				if(i != -1){//アップロードのファイル名がセット
					filename = s.substring(i+"filename=\"".length());
					i=filename.indexOf("\"");
					filename = filename.substring(0,i);
					i=filename.lastIndexOf("\\");
					if(i != -1){//ファイル区切り文字を含む場合は、除く
						filename = filename.substring(i);
					}
				}
			}
			count+=numb;
		}

		// Part内ボディの終わり(次の区切りを含む)まで読み取る繰り返し
		int sizePartBody=0;
		while((numb = is.readLine(binary,sizePartBody, sizeBody-count )) != -1){
			s = new String(binary,sizePartBody,numb);
			if(s.startsWith(boundary)) {
				if(filename != ""){//アップロードのファイル名がセットされている?
					String dir = this.getServletContext().getRealPath("/");
					String path=dir + filename;
					try{
						FileOutputStream fos = new FileOutputStream(path);//ファイル生成
						fos.write(binary,0,sizePartBody-2); //最後の改行を除いて書き込み
						fos.close();
						msg = path+"のファイル保存しました。";
					}
					catch(Exception er){  msg += er.getLocalizedMessage(); }
				}
				break;
			}
			sizePartBody+=numb;
			sizeBody+=numb;
		}
		if(s.startsWith(boundaryEnd)) break;
		if(count >=sizeBody) break;//ボディ読み取り終了
	}
%>
<div><p>
<%= msg %>
</p></div>
</body>
</html>

上記は、multipart/form-dataの構造を知るために作ったコードですが、 実際にシステムで使う場合は、セキュリティ対策などが必要です。 また、akarta Commonsサブプロジェクトから公開されているFileUploadライブラリを利用することで、 本来なら煩雑なアップロード処理をより簡単に書くことができます。
その場合、org.apache.commons.fileuploadのDiskFileUpload、FileItem、FileUploadExceptionクラスを利用します。

なお、 MultiPart/Form-Data の定義は元々 [RFC 1867] にて設計され、その後 [HTML40] に組み入れられた。 multipart/form-data メディアタイプは、[RFC 2046] にて概説されたような全ての multipart MIME データストリームの規定に従う。


参考にphpのアップロードコードの例を示します。
phpでは、アップロードに対応する処理が予め存在しており、特別にプログラムする必要はありません。 アップロードファイルは一時的に$_FILES["upfilename"]["tmp_name"]のテンポラリファイル名で記憶されます。
upfilenameは、<input type="file" name="upfilename" size="40"><br>のname属性値です。
そしてファイルがアップロードされたかどうかは、is_uploaded_file関数で調べることができます。
アップロードされたファイルはmove_uploaded_fileで希望のファイル名で移動させています。 (この移動でアップされたテンポラリファイルは無くなることになります。)
下記例では、$_FILES["upfilename"]["name"]にアップロードで指定されたファイル名が記憶されていいるので、 $filenameの変数に記憶して、その名前で移動させています。

$filename=$_FILES["upfilename"]["name"];
if (is_uploaded_file($_FILES["upfilename"]["tmp_name"])) {
	if (move_uploaded_file($_FILES["upfilename"]["tmp_name"], "希望のディレクトリ/{$filename}")) {
		chmod("希望のディレクトリ/{$filename}", 0644);
		echo "<a href='希望のディレクトリ/{$filename}'>{$filename}</a>" . "がアップロードされました。";
	} else {
		echo "{$filename}のファイルをアップロードできません。";
	}
} else {
	echo "ファイルが選択されていません。";
}