Ale*_*lex 27 java rest download jersey bytestream
我有3台机器:
- 文件所在的服务器
- 正在运行REST服务的服务器(Jersey)
- 客户端(浏览器)可访问第二台服务器但无法访问第一台服务器
如何直接(不保存第二台服务器上的文件)将文件从第一台服务器下载到客户机器?
从第二台服务器我可以得到一个ByteArrayOutputStream来从第一台服务器获取文件,我可以使用REST服务将此流进一步传递给客户端吗?
它会这样工作吗?
基本上我想要实现的是允许客户端使用第二台服务器上的REST服务从第一台服务器下载文件(因为没有从客户端到第一台服务器的直接访问)只使用数据流(所以没有数据接触文件第二服务器系统).
我现在尝试使用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();
Run Code Online (Sandbox Code Playgroud)
UPDATE2
所以现在使用自定义MessageBodyWriter的代码看起来很简单:
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); client.downloadFile(location,spaceId,filePath,baos); return Response.ok(baos).build();
但是在尝试使用大文件时遇到了相同的堆错误.
UPDATE3 终于成功了!StreamingOutput成功了.
谢谢@peeskillet!非常感谢 !
Pau*_*tha 33
"我如何直接(不保存第二台服务器上的文件)将文件从第一台服务器下载到客户机器?"
只需使用ClientAPI并InputStream从响应中获取
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);
Run Code Online (Sandbox Code Playgroud)
有两种口味可以得到InputStream.你也可以使用
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();
Run Code Online (Sandbox Code Playgroud)
哪一个效率更高?我不确定,但返回的InputStreams是不同的类,所以如果你愿意,你可能想要调查一下.
从第二台服务器我可以得到一个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();
Run Code Online (Sandbox Code Playgroud)
但是,如果我们查看StreamingOutputProvider的源代码,您会看到writeTo它只是将数据从一个流写入另一个流.因此,通过上面的实现,我们必须写两次.
我们怎么才能得到一个写?简单的回归InputStream作为Response
final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
"Content-Disposition", "attachment, filename=\"...\"").build();
Run Code Online (Sandbox Code Playgroud)
如果我们查看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);
}
}
Run Code Online (Sandbox Code Playgroud)
旁白:
Client对象是昂贵的资源.您可能希望Client对请求重用相同的内容.您可以WebTarget从客户端为每个请求提取a .
WebTarget target = client.target(url);
InputStream is = target.request().get(InputStream.class);
Run Code Online (Sandbox Code Playgroud)
我认为WebTarget甚至可以分享.我在Jersey 2.x文档中找不到任何内容(仅因为它是一个更大的文档,我现在懒得扫描它:-),但在Jersey 1.x文档中,它说Client和WebResource(WebTarget在2.x中相当于)可以在线程之间共享.所以我猜测泽西岛2.x会是一样的.但你可能想要自己确认一下.
您不必使用ClientAPI.使用java.net包API 可以轻松实现下载.但由于你已经在使用Jersey,因此使用它的API并没有什么坏处
以上是假设泽西岛2.x. 对于Jersey 1.x,一个简单的谷歌搜索应该会让你获得一堆使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以简单地返回ByteArrayOutputStream响应
return Response.ok(baos).build();
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
客户测试
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();
}
}
Run Code Online (Sandbox Code Playgroud)
因此,对于这个特定的用例的最终解决方案是为OP简单地通过OutputStream从所述StreamingOutput的write方法.似乎是第三方API,需要一个OutputStream参数.
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) {
thirdPartyApi.downloadFile(.., .., .., out);
}
}
return Response.ok(output).build();
Run Code Online (Sandbox Code Playgroud)
不太确定,但似乎资源方法中的读/写,使用ByteArrayOutputStream`,在内存中实现了一些东西.
downloadFile接受a 的方法的要点OutputStream是它可以将结果直接写入OutputStream提供的.例如FileOutputStream,如果您将其写入文件,则在下载进入时,它将直接流式传输到该文件.
这并不意味着我们要继续引用OutputStream,就像你试图做的那样baos,这是内存实现的来源.
因此,通过有效的方式,我们将直接写入为我们提供的响应流.该方法write实际上不会被调用,直到传递给它的writeTo方法(在MessageBodyWriter)中OutputStream.
你可以看到MessageBodyWriter我写的更好的图片.基本上在writeTo方法中,替换ByteArrayOutputStreamwith StreamingOutput,然后在方法内部调用streamingOutput.write(entityStream).您可以在答案的前面部分看到我提供的链接,我链接到该链接StreamingOutputProvider.这正是发生的事情