Help us understand the problem. What is going on with this article?

HttpURLConnection(と、Apache Commons)によるマルチパートのリクエスト・レスポンス

More than 3 years have passed since last update.

とあるサーバがマルチパートでリクエストを受けて、マルチパートでレスポンスを返すというものだったので、それに対応する実装のメモ

環境

  • 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に公開しておこうと思います。

kobarasukimaro
バックエンド開発メイン。 Java, PHP, Swift, Python, Javascript, AWS
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした