はじめに

ObjectOutputStreamObjectInputStreamを使うことで、Serializable JavaオブジェクトのHTTPを通した送信、受信ができる。オブジェクトにbyte[]が含まれていれば、バイナリファイルのアップロードとダウンロードもできることになる。

ObjectOutputStream/ObjectInputStreamでJavaオブジェクトを送受信する場合、サーバサイドだけでなくクライアントもJava実装を強いられるため、複数のクライアントが対応しようとするときの障壁になる。基本は、JSONでのやりとりやmultipart/form-data形式で送受信した方がブラウザをはじめとして多種多様なクライアントとのやり取りが簡単になるので、Javaオブジェクトを送受信しないほうがいい。

今回、他社に作られたObjectOutputStream/ObjectInputStreamで送受信するシステムと接続することになり、こちらの環境でその外部システムのスタブを作ることになったので、それをSpring Bootで実装してみた。

version:
Spring Boot 1.4.0

ファイルの送受信の種類

ObjectOutputStream/ObjectInputStreamで作るのは今回のような前提がない限り勧められないので、Spring Bootでの実装に移る前に、送受信の種類を一覧にしておく。特に理由がない限り各項目の上位に記載されている方法から順に実装を検討するといい。

送信

  • multipart/form-data形式(HTML)
  • Base64 EncodeしてJSONにセットする
  • ObjectOutputStream/ObjectInputStream

受信

  • ダウンロード形式(Content-Disposition: attachment)
  • Base64 EncodeしてJSONにセットする
  • ObjectOutputStream/ObjectInputStream

ダウンロード形式ではファイルのバイナリデータ以外にデータを送ることができないが、ファイル名であればContent-Dispositionヘッダにセットされる。

Content-Disposition: attachment; filename*=UTF-8''ファイル名

参考:Spring Bootで日本語ファイル名のファイルダウンロード

ファイル名以外にも大量に情報を受信するのには向かないが、一つ二つ追加したいくらいであれば独自ヘッダを定義してヘッダに情報を書くのもいいだろう。

Spring Bootでの実装

Spring Bootでのサーバサイド実装

※サンプルのためエラーは全て握りつぶす。
new ObjectInputStream(req.getInputStream())readObject(), new ObjectOutputStream(res.getOutputStream())writeObject(ret)がObjectOutputStream/ObjectInputStreamを使う上でキーになるコード。

package jp.co.sample.web;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

import jp.co.sample.SampleObject;

@Controller
public class SampleController {

    @PostMapping(value = "/upload")
    public String upload(HttpServletRequest req, HttpServletResponse res) {
        // Javaオブジェクトをリクエストから取り出し
        SampleObject sampleObject = null;
        try (ObjectInputStream ois = new ObjectInputStream(req.getInputStream())) {
            sampleObject = (SampleObject) ois.readObject();

            if (sampleObject == null) {
                res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                return null;
            }
        } catch (IOException | ClassNotFoundException e) {
            res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return null;
        }

        // 取り出したJavaオブジェクトを使って仕事をする
        byte[] docBody = sampleObject.getDocumentBody();
        String fileName = sampleObject.getDocumentName();
        SampleObject ret = new SampleObject();
        try {
            Files.write(Paths.get(fileName), docBody);
        } catch (IOException e) {
            ret.setErrorFlg("1");
        }

        // Javaオブジェクトをレスポンスにする
        try (ObjectOutputStream oos = new ObjectOutputStream(res.getOutputStream())) {

            oos.writeObject(ret);
        } catch (IOException e) {
            res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return null;
        }

        return null;
    }
}

URLConnectionを使ったクライアント実装

Java標準ライブラリのURLConnectionを使ってクライアントを書く。
uc.setRequestProperty("Content-Type", "application/octet-stream");がないと、サーバでreq.getInputStream()をしたときにEOFExceptionが発生してしまった。

// 送信するJavaオブジェクトの作成
SampleObject sampleObject = new SampleObject();
sampleObject.setDocumentBody(Files.readAllBytes(Paths.get("sampledirectory", "sample.txt")));
sampleObject.setDocumentName("sample.txt");

// サーバ接続
URL url = new URL(connectUrl);
URLConnection uc = url.openConnection();
uc.setDoOutput(true);
uc.setRequestProperty("Content-Type", "application/octet-stream");

// リクエスト送信
OutputStream os = uc.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(sampleObject);
oos.close();

// レスポンス受信
InputStream is = uc.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
SampleObject returned = (SampleObject) ois.readObject();