Как загрузить файл с помощью сервиса JAVA REST и потока данных
у меня 3 машины:
- сервер, на котором находится файл
- сервер, на котором работает служба REST (Джерси)
- клиент (браузер) с доступом ко 2-му серверу, но без доступа к 1-му серверу
Как я могу напрямую (без сохранения файла на 2-м сервере) загрузить файл с 1-го сервера на машину клиента?
С 2nd сервера я могу получить ByteArrayOutputStream чтобы получить файл с 1-го сервера, могу ли я передать этот поток дальше клиенту, используя службу REST?
Он будет работать таким образом?
поэтому в основном я хочу, чтобы клиент мог загрузить файл с 1-го сервера, используя службу REST на 2-м сервере (поскольку нет прямого доступа от клиента к 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();
обновление 2
Итак, мой код теперь с пользовательским MessageBodyWriter выглядит просто:
ByteArrayOutputStream baos = новый ByteArrayOutputStream(2048) ; клиент.downloadFile (location, spaceId, filePath, baos); ответный ответ.хорошо (баос).build();
но я получаю ту же ошибку при попытке кучи с большими файлами.
UPDATE3 Наконец удалось заставить его работать ! StreamingOutput сделал трюк.
спасибо @peeskillet ! Большое спасибо !
3 ответов
" как я могу напрямую (без сохранения файла на 2-м сервере) загрузить файл с 1-го сервера на клиентскую машину?"
просто использовать 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-это разные классы, поэтому вы можете посмотреть на это, если вам не все равно к.
со 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
, что он просто записывает данные из одного потока в другой. Поэтому с нашей реализацией выше мы должны написать дважды.
как мы можем получить только одну запись? Простой возврат 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);
}
}
Asides:
-
Client
объекты являются дорогостоящими ресурсами. Возможно, вы захотите повторно использовать то же самоеClient
для запроса. Вы можете извлечьWebTarget
от клиента для каждого запроса.WebTarget target = client.target(url); InputStream is = target.request().get(InputStream.class);
я думаю
WebTarget
можно даже поделиться. Я ничего не могу найти в Джерси 2.х документация (только потому, что это большой документ, и я слишком ленив, чтобы просмотреть его прямо сейчас :-), а в Нью-Джерси 1.х документация она говоритClient
иWebResource
(что эквивалентноWebTarget
в 2.x)может совместно использоваться между потоками. Так что я предполагаю Джерси 2.x будет таким же. но вы можете подтвердить это сами. вы не должны использовать
Client
API-интерфейс. Загрузка может быть легко достигнута с помощьюjava.net
пакета API-интерфейсы. Но так как вы уже используете Джерси, не помешает использовать его Аписвыше предполагая Джерси 2.х. Для Джерси 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();
ОХ!
обновление 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
С StreamingOutput
' s 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
), где 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();
}
см. пример здесь: входные и выходные двоичные потоки с использованием Джерси?
псевдо-код будет примерно таким (есть несколько других подобных опций в вышеупомянутом сообщении):
@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
}
}
}