アプレットからWebサーバへファイルアップロード
アプレットからのファイルアップロード。これはなかなか大変だった。
具体的には、httpでmultipartのPOSTをしたい。複数のファイルや、一緒にテキストデータなんかも一緒にPOSTしたい。
Jakarta Commons HttpClientをアプレットで使うのは難しい
Jakarta Commons HttpClientというコンポーネントがあって、これを使うとhttp通信が手軽に使えるらしい。
これを使って実際に作ってみたけど、ダメだった。
- 自分の作ったjarだけじゃなく、このHttpClientのjarにも署名が必要
- 一緒に使う必要があるJakarta Commons Loggingのバグにより、権限エラーになる
- 日本語ファイル名の文字化け
どれも回避しようと思えばできるんだけど、後でのメンテも含めて面倒になるため、あきらめた。
自分で通信部分を実装
Javaの標準機能だけを使って自分で通信部分を実装した。
String boundary = generateBoundary(); // 接続 URL url = new URL("http://example.com/upload.do"); // 送信先 URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); DataOutputStream out = new DataOutputStream(conn.getOutputStream()); // テキストフィールド送信 out.writeBytes("--" + boundary + "\r\n"); out.writeBytes("Content-Disposition: form-data; name=\"text\"\r\n"); out.writeBytes("Content-Type: text/plain; charset=Shift_JIS\r\n\r\n"); out.write("テキスト".getBytes(this.charset)); out.writeBytes("\r\n"); // ファイルフィールド送信 File file = new File("c:\\files\\file.zip"); out.writeBytes("--" + boundary + "\r\n"); out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\""); out.write(file.getName().getBytes("Shift_JIS")); out.writeBytes("\"\r\n"); out.writeBytes("Content-Type: application/octet-stream\r\n\r\n"); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); int buff = 0; while((buff = in.read()) != -1){ out.write(buff); } out.writeBytes("\r\n"); in.close(); // 送信終わり out.writeBytes("--" + boundary + "--"); out.flush(); out.close(); // レスポンスを受信 (これをやらないと通信が完了しない) InputStream stream = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); String responseData = null; while((responseData = reader.readLine()) != null){ System.out.print(responseData); } stream.close();
テキストフィールドとファイルフィールドを送信しているところは、連続して何回でも呼べる (multipart のパートとなる)。
あと、最初にboudaryを取得してるところ (generateBoundary) は、以下のような感じで書いた。
private String generateBoundary(){ String chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; Random rand = new Random(); String boundary = ""; for(int i = 0; i < 40; i++){ int r = rand.nextInt(chars.length()); boundary += chars.substring(r, r + 1); } return boundary; }
boundaryっていうのは、multipartのデータを送信するときにデータ(各パート)の区切りを表す文字列。どんな文字列でも良いが、boundary以外のデータに同じ文字列が含まれると、そこで区切られたことになってしまって良くない。
今回は、ランダムに40文字の文字列を生成しただけ。このやり方は、ものすごく運が悪いと、ファイル内のデータに含まれる文字列と重複しちゃうかも知れない。
けど、たぶん相当なレアケースだと思う (確率は計算してないけど) ので、気にしないことにした。