“我怎样才能直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端的机器上?”
只需使用Client
API 并InputStream
从响应中获取
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);
有两种口味可以得到InputStream
。你也可以使用
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();
哪个效率更高?我不确定,但返回InputStream
的 s 是不同的类,所以如果你愿意的话,你可能想研究一下。
从第二台服务器我可以得到一个 ByteArrayOutputStream 来从第一台服务器获取文件,我可以使用 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
,它只是将数据从一个流写入另一个流。所以对于我们上面的实现,我们必须写两次。
我们怎样才能只写一篇文章?简单地返回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 文档中找不到任何东西(只是因为它是一个更大的文档,我现在懒得浏览它:-),但是在Jersey 1.x 文档中,它说Client
和WebResource
(相当于WebTarget
2.x 中的)可以在线程之间共享。所以我猜Jersey 2.x 会是一样的。但您可能需要自己确认。
您不必使用Client
API。java.net
使用包 API可以轻松实现下载。但是由于您已经在使用 Jersey,所以使用它的 API 并没有什么坏处
以上假设 Jersey 2.x。对于 Jersey 1.x,一个简单的 Google 搜索应该会为您提供大量使用 API(或我上面链接到的文档)的点击率
更新
我真是个笨蛋。虽然我和 OP 正在考虑将 aByteArrayOutputStream
转换为 a 的方法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();
哦!
更新 2
这是我使用的测试(
资源类
@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();
}
}
更新 3
所以这个特定用例的最终解决方案是让 OP 简单地传递OutputStream
fromStreamingOutput
的write
方法。似乎是第三方 API,需要 aOutputStream
作为参数。
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) {
thirdPartyApi.downloadFile(.., .., .., out);
}
}
return Response.ok(output).build();
不太确定,但似乎资源方法中的读/写,使用 ByteArrayOutputStream`,在内存中实现了一些东西。
downloadFile
接受 an的方法的重点OutputStream
是它可以将结果直接写入OutputStream
提供的。例如 a FileOutputStream
,如果您将其写入文件,则在下载时,它将直接流式传输到文件中。
这并不意味着我们要保留对 的引用OutputStream
,就像您尝试对 进行的那样baos
,这是内存实现的来源。
因此,通过这种工作方式,我们直接写入为我们提供的响应流。在传递给它的方法(在 中)之前,该方法write
实际上不会被调用。writeTo
MessageBodyWriter
OutputStream
看MessageBodyWriter
我写的你可以得到更好的图片。基本上在writeTo
方法中,替换为ByteArrayOutputStream
,StreamingOutput
然后在方法中,调用streamingOutput.write(entityStream)
。您可以看到我在答案前面部分提供的链接,我在其中链接到StreamingOutputProvider
. 这正是发生的事情