3台のマシンがあります。
- ファイルが置かれているサーバー
- RESTサービスが実行されているサーバー(ジャージー)
- 2番目のサーバーにはアクセスできるが、1番目のサーバーにはアクセスできないクライアント(ブラウザー)
(2番目のサーバーにファイルを保存せずに)直接1番目のサーバーからクライアントのマシンにファイルをダウンロードするにはどうすればよいですか?
2番目のサーバーからByteArrayOutputStreamを取得して1番目のサーバーからファイルを取得できますが、RESTサービスを使用してこのストリームをクライアントにさらに渡すことができますか?
このように動作しますか?
だから基本的に私が達成したいのは、データストリームのみを使用して、クライアントが第2サーバーのRESTサービスを使用して第1サーバーからファイルをダウンロードできるようにすることです(クライアントから第1サーバーへの直接アクセスがないため)そのため、2番目のサーバーのファイルシステムにデータが接触することはありません。
EasyStreamライブラリで今試していること:
final FTDClient client = FTDClient.getInstance();
try {
final InputStreamFromOutputStream<String> isOs = new InputStreamFromOutputStream<String>() {
@Override
public String produce(final OutputStream dataSink) throws Exception {
return client.downloadFile2(location, Integer.valueOf(spaceId), URLDecoder.decode(filePath, "UTF-8"), dataSink);
}
};
try {
String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
int length;
byte[] buffer = new byte[1024];
while ((length = isOs.read(buffer)) != -1){
outputStream.write(buffer, 0, length);
}
outputStream.flush();
}
};
return Response.ok(output, MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", "attachment; filename=\"" + fileName + "\"" )
.build();
PDATE2
したがって、カスタムMessageBodyWriterを使用した私のコードは簡単に見えます。
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); client.downloadFile(location、spaceId、filePath、baos); return Response.ok(baos).build();
しかし、大きなファイルを使用しようとすると、同じヒープエラーが発生します。
PDATEやっと機能するようになりました! StreamingOutputがトリックを行いました。
@peeskilletありがとう!どうもありがとう !
「(2番目のサーバーにファイルを保存せずに)1番目のサーバーからクライアントのマシンにファイルを直接ダウンロードするにはどうすればよいですか?」
Client
APIを使用して、応答からInputStream
を取得するだけです
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);
InputStream
を取得するには2つのフレーバーがあります。使用することもできます
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();
どちらがより効率的ですか?よくわかりませんが、返されるInputStream
sは異なるクラスですので、気になったらそれを調べてください。
2番目のサーバーからByteArrayOutputStreamを取得して1番目のサーバーからファイルを取得できますが、RESTサービスを使用してこのストリームをクライアントにさらに渡すことができますか?
@ GradyGCooperが提供するリンク に表示される回答のほとんどは、StreamingOutput
の使用を支持しているようです。実装例は次のようなものです
final InputStream responseStream = client.target(url).request().get(InputStream.class);
System.out.println(responseStream.getClass());
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException, WebApplicationException {
int length;
byte[] buffer = new byte[1024];
while((length = responseStream.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
out.flush();
responseStream.close();
}
};
return Response.ok(output).header(
"Content-Disposition", "attachment, filename=\"...\"").build();
しかし、 StreamingOutputProviderのソースコード を見ると、writeTo
で、あるストリームから別のストリームにデータを書き込むだけであることがわかります。したがって、上記の実装では、2回書く必要があります。
書き込みを1つだけ取得するにはどうすればよいですか? InputStream
をResponse
として単純に返す
final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
"Content-Disposition", "attachment, filename=\"...\"").build();
InputStreamProviderのソースコード を見ると、単に ReadWriter.writeTo(in, out)
に委任されます。これは、上記のStreamingOutput
実装で行ったことを単純に実行します。
public static void writeTo(InputStream in, OutputStream out) throws IOException {
int read;
final byte[] data = new byte[BUFFER_SIZE];
while ((read = in.read(data)) != -1) {
out.write(data, 0, read);
}
}
脇:
Client
オブジェクトは高価なリソースです。同じClient
をリクエストに再利用することもできます。リクエストごとにクライアントからWebTarget
を抽出できます。
WebTarget target = client.target(url);
InputStream is = target.request().get(InputStream.class);
WebTarget
も共有できると思います。 Jersey 2.x documentation (これは大きなドキュメントであり、今はスキャンするのが面倒だからです:-)には何も見つかりませんが、 Jersey 1.xドキュメント 、スレッド間でClient
およびWebResource
(2.xのWebTarget
と同等)を共有できると記載されています。だから、Jersey 2.xも同じだと思います。ただし、自分で確認することもできます。
Client
APIを使用する必要はありません。ダウンロードは、Java.net
パッケージAPIを使用して簡単に実現できます。ただし、すでにJerseyを使用しているので、そのAPIを使用しても害はありません。
上記はJersey 2.xを想定しています。 Jersey 1.xの場合、単純なGoogle検索では、API(または上記にリンクしたドキュメント)を操作することで多くのヒットが得られます。
私はそのようなデュファです。 OPと私はByteArrayOutputStream
をInputStream
に変える方法を考えていますが、私はMessageBodyWriter
にByteArrayOutputStream
を書くという最も単純な解決策を見逃しました
import Java.io.ByteArrayOutputStream;
import Java.io.IOException;
import Java.io.OutputStream;
import Java.lang.annotation.Annotation;
import Java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
@Provider
public class OutputStreamWriter implements MessageBodyWriter<ByteArrayOutputStream> {
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return ByteArrayOutputStream.class == type;
}
@Override
public long getSize(ByteArrayOutputStream t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return -1;
}
@Override
public void writeTo(ByteArrayOutputStream t, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
t.writeTo(entityStream);
}
}
次に、応答でByteArrayOutputStream
を返すだけです
return Response.ok(baos).build();
D'OH!
ここに私が使用したテストがあります(
リソースクラス
@Path("test")
public class TestResource {
final String path = "some_150_mb_file";
@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response doTest() throws Exception {
InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[4096];
while ((len = is.read(buffer, 0, buffer.length)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println("Server size: " + baos.size());
return Response.ok(baos).build();
}
}
クライアントテスト
public class Main {
public static void main(String[] args) throws Exception {
Client client = ClientBuilder.newClient();
String url = "http://localhost:8080/api/test";
Response response = client.target(url).request().get();
String location = "some_location";
FileOutputStream out = new FileOutputStream(location);
InputStream is = (InputStream)response.getEntity();
int len = 0;
byte[] buffer = new byte[4096];
while((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
out.close();
is.close();
}
}
したがって、この特定のユースケースの最終的な解決策は、OPがOutputStream
のStreamingOutput
メソッドからwrite
を単に渡すことでした。引数としてOutputStream
が必要なサードパーティAPIのようです。
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) {
thirdPartyApi.downloadFile(.., .., .., out);
}
}
return Response.ok(output).build();
確かではありませんが、ByteArrayOutputStream`を使用したリソースメソッド内の読み取り/書き込みは、メモリに何かを実現したようです。
downloadFile
を受け入れるOutputStream
メソッドのポイントは、指定されたOutputStream
に結果を直接書き込むことができるようにすることです。たとえば、FileOutputStream
をファイルに書き込んだ場合、ダウンロードの受信中にファイルに直接ストリーミングされます。
OutputStream
を参照しようとしていたので、baos
への参照を保持するつもりはありません。
そのため、機能する方法で、提供された応答ストリームに直接書き込みます。メソッドwrite
は、実際にwriteTo
メソッドが(MessageBodyWriter
内で)OutputStream
に渡されるまで呼び出されません。
私が書いたMessageBodyWriter
を見ると、より良い画像が得られます。基本的にwriteTo
メソッドで、ByteArrayOutputStream
をStreamingOutput
に置き換えてから、メソッド内でstreamingOutput.write(entityStream)
を呼び出します。回答の前半で提供したリンクを見ることができます。ここで、StreamingOutputProvider
にリンクしています。これはまさに起こることです
これを参照してください:
@RequestMapping(value="download", method=RequestMethod.GET)
public void getDownload(HttpServletResponse response) {
// Get your file stream from wherever.
InputStream myStream = someClass.returnFile();
// Set the content type and attachment header.
response.addHeader("Content-disposition", "attachment;filename=myfilename.txt");
response.setContentType("txt/plain");
// Copy the stream to the response's output stream.
IOUtils.copy(myStream, response.getOutputStream());
response.flushBuffer();
}
ここの例を参照してください。 JERSEYを使用したバイナリストリームの入力と出力
擬似コードは次のようなものになります(上記の投稿には他にも同様のオプションがいくつかあります)。
@Path("file/")
@GET
@Produces({"application/pdf"})
public StreamingOutput getFileContent() throws Exception {
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
//
// 1. Get Stream to file from first server
//
while(<read stream from first server>) {
output.write(<bytes read from first server>)
}
} catch (Exception e) {
throw new WebApplicationException(e);
} finally {
// close input stream
}
}
}