我有三台机器:
我如何才能直接(不保存在第二服务器上的文件)下载文件从第一服务器到客户的机器?
在第二台服务器上,我可以从第一台服务器获得一个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();
}
}UPDATE2
因此,现在使用自定义MessageBodyWriter的代码看起来很简单:
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048) ;
client.downloadFile(location, spaceId, filePath, baos);
return Response.ok(baos).build();但是,在尝试处理大型文件时,我也会遇到相同的堆错误。
UPDATE3终于成功了!StreamingOutput成功了。
谢谢你@peeskillet!非常感谢!
发布于 2015-04-18 05:05:18
“我如何直接(不将文件保存在第二服务器上)将文件从第一服务器下载到客户端的计算机?”
只需使用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是不同的类,所以如果您愿意的话,您可能想了解一下。
在第二台服务器上,我可以从第一台服务器获得一个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),而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);
}
}Asides:
Client对象是昂贵的资源。您可能希望为请求重用相同的Client。您可以为每个请求从客户端提取一个WebTarget。
WebTarget target = client.target(url);InputStream is = target.request().get(InputStream.class);
我认为WebTarget甚至可以共享。我在泽西岛2.x文件中找不到任何东西(只是因为它是一个更大的文档,而且我现在太懒于浏览它:-),但是在泽西岛1.x文件中,它说Client和WebResource (相当于2.x中的WebTarget )可以在线程之间共享。所以我猜泽西的2.x也是一样的。但你可能想自己确认一下。Client API。通过java.net包API可以轻松地下载。但是既然你已经在使用泽西,使用它的API也没什么坏处更新
我真是个蠢货。当OP和我正在考虑将ByteArrayOutputStream转换为InputStream的方法时,我忽略了最简单的解决方案,那就是为ByteArrayOutputStream编写一个MessageBodyWriter
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简单地从StreamingOutput的write方法中传递write,似乎第三方API需要一个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中)被传递给它之前实际上不会被调用。
您可以更好地查看我所写的MessageBodyWriter。基本上在writeTo方法中,用StreamingOutput替换ByteArrayOutputStream,然后在方法中调用streamingOutput.write(entityStream)。您可以看到我在答案的前面部分提供的链接,在这里我链接到StreamingOutputProvider。这就是发生的事
发布于 2015-04-18 04:11:19
参见这里的示例:使用泽西输入和输出二进制流?
伪代码应该是这样的(在上面提到的帖子中还有其他一些类似的选项):
@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
}
}
}发布于 2016-11-10 10:47:20
请参阅:
@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();
}https://stackoverflow.com/questions/29712554
复制相似问题