“如何直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端计算机?”
只需使用 API 并从响应中获取Client
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();
哪一个更有效率?我不确定,但是返回的s是不同的类,所以如果你愿意,你可能想研究一下。InputStream
从第二台服务器,我可以获取一个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);
我认为甚至可以分享。我在泽西岛2.x文档中找不到任何东西(只是因为它是一个更大的文档,我现在懒得扫描它:-),但在泽西岛1.x文档中,它说and(相当于2.x)可以在线程之间共享。所以我猜泽西岛2.x也是一样的。但你可能想自己确认。WebTarget
Client
WebResource
WebTarget
您不必使用 API。使用包 API 可以轻松实现下载。但是,由于您已经在使用泽西岛,因此使用其API并没有什么坏处。Client
java.net
以上是假设泽西岛2.x。对于泽西岛1.x,一个简单的Google搜索应该可以让你获得一堆使用API(或我上面链接到的文档)的点击量。
更新
我真是个傻瓜。虽然OP和我正在考虑将a变成a的方法,但我错过了最简单的解决方案,那就是简单地编写一个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();
哦!
更新 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简单地传递来自 的方法。似乎第三方API,需要一个作为参数。OutputStream
StreamingOutput
write
OutputStream
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