とあるサーバがマルチパートでリクエストを受けて、マルチパートでレスポンスを返すというものだったので、それに対応する実装のメモ
環境
- Java 1.8.0_77
- commons-fileupload-1.3.2.jar
- commons-io-2.5.jar
実装
クラスの準備
マルチパートのレスポンスを処理する用にcommons-fileuploadのUploadContextをimplementsしたクラスを作ります(レスポンスを受け取るのにUploadとか変な感じですが)。
また、UploadContextのメソッド実装用にレスポンスを受け取るInputStreamはメンバとして定義します。今回レスポンスがマルチパートかヘッダをチェックするのでHttpURLConnectionもメンバにしてます。
あと、マルチパートを処理するのに必要なUploadContextのメソッドを実装しておきます。
public class MultipartRequestResponse implements UploadContext {
private InputStream is;
private HttpURLConnection connection;
// 最低限getInputStreamの中身は実装する
@Override
public InputStream getInputStream() throws IOException {
return is;
}
// レスポンスヘッダチェック用に必要があれば実装する
@Override
public String getContentType() {
return connection.getHeaderField("Content-Type");
}
// 他のOverrideが必要なメソッドはとりあえずの実装だけなので省略
// ・・・
}
リクエスト
準備
パートの区切り文字(boundary)を作っておく
バウンダリは繰り返し使うので先にクラス変数として作っておいたほうがやりやすいです。
今回はテストなので固定にしてますが、ランダムで発行できるようにした方がバウンダリの仕様や何かあった際の調査を考えると良いと思います。
private static final String BOUNDARY = "--boundary";
リクエストの設定
リクエスト先のURLやリクエストヘッダなどを準備します。
リクエストヘッダはサーバの仕様に従って設定していきます。
URL url = new URL("https://hogehoge");
connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
connection.setRequestProperty("host", url.getHost());
// 他、必要な設定があれば
リクエスト送信
マルチパートをリクエストボディにセットする
HttpURLConnectionのインスタンスからOutputStreamを取得してボディを書き込んでいきます。
ボディはマルチパートの仕様に従って改行やバウンダリも書き込む必要があります。
ちなみに改行はCRLFにする必要があります(1回LFで送ってしまってマルチパートをうまく処理してもらえなかったことアリ)。
private static final String CRLF = "\r\n";
// サーバと通信開始
OutputStream os = connection.getOutputStream();
StringBuilder multipartBody = new StringBuilder();
multipartBody.append(CRLF);
// パート(JSON)
multipartBody.append(BOUNDARY).append(CRLF);
multipartBody.append("Content-Disposition: form-data; name=\"parameter1\"").append(CRLF);
multipartBody.append("Content-Type: application/json; charset=utf-8").append(CRLF);
multipartBody.append("Content-Transfer-Encoding: 8bit").append(CRLF);
multipartBody.append(CRLF);
multipartBody.append("{\"hoge\": \"fuga\"}" + CRLF);
// 終了パート
multipartBody.append(BOUNDARY).append("--");
// OutputStreamにボディを書き込む
os.write(multipartBody.toString().getBytes());
// サーバへボディを送信
os.close();
HttpURLConnection#getOutputStream()を呼ぶとサーバと通信を開始し、ヘッダが送られます。
OutputStream#close()を呼ぶとwriteしたリクエストボディをサーバへ送ります。
パート毎にOutputStream#flush()を呼べば都度サーバへボディを飛ばせそうですが、送られませんでした。この辺りは別途検証してまた記事を書きたいと思います。
レスポンス
サーバ側がレスポンスをマルチパートで返してくる場合の処理ですが、レスポンスのInputStreamを頑張ってパースすればできなくもなさそうな気がしますが、apache commonsの力を借りて処理していきます。
// レスポンス受け取り
is = connection.getInputStream();
// レスポンスがマルチパートかチェック
// getContentType()の返り値がチェックされる
if (!FileUpload.isMultipartContent((RequestContext) this)) {
// レスポンスがマルチパートではなかった場合の処理
}
// InputStreamの内容をIteratorに変換してくれる
FileUpload upload = new FileUpload();
FileItemIterator fit = upload.getItemIterator(this);
// レスポンスボディの1パート毎にループ
while (fit.hasNext()) {
FileItemStream fis = fit.next();
// パートのフィールド名取得
String field = fis.getFieldName();
// パートのInputStream取得
InputStream isPart = fis.openStream();
// あとはパートの中身を処理しておけばOK
// ・・・
}
FileItemIteratorでレスポンスを受け取れれば、あとはループで回してパートを一つずつ処理していけばOKです。
サンプルのコードはキレイにして後ほどgithubに公開しておこうと思います。