jersey — StreamingOutput как объект ответа

Я реализовал потоковый вывод в своем классе ресурсов Джерси.

@GET
@Path("xxxxx")
@Produces(BulkConstants.TEXT_XML_MEDIA_TYPE})   
public Response getFile() {

    FeedReturnStreamingOutput sout = new FeedReturnStreamingOutput();
    response = Response.ok(sout).build();
    return response;
}

class FeedReturnStreamingOutput implements StreamingOutput {

    public FeedReturnStreamingOutput()

    @Override
    public void write(OutputStream outputStream)  {
        //write into Output Stream
    }
}

Проблема заключается в том, что ответ отправляется обратно от ресурса до того, как FeedReturnStreamingOutput вызывается. Клиент Джерси ждет завершения выполнения FeedReturnStreamingOutput.

Код клиента:

Client client = Client.create();

ClientResponse response = webResource
    //headers
    .get(ClientResponse.class);

//The codes underneath executes after FeedReturnStreamingOutput is executed which undermines the necessity of streaming

OutputStream os = new FileOutputStream("c:\\test\\feedoutput5.txt");
System.out.println(new Date() + " : Reached point A");

if (response.getStatus() == 200) {
    System.out.println(new Date() + " : Reached point B");
    InputStream io = response.getEntityInputStream();

    byte[] buff = new byte[1024000];
    int count = 0;

    while ((count = io.read(buff, 0, buff.length)) != -1) {
        os.write(buff, 0, count);
    }

    os.close();
    io.close();

} else {
    System.out.println("Response code :" + response.getStatus());
}

System.out.println("Time taken -->> "+(System.currentTimeMillis()-startTime)+" ms");

person PrabhaT    schedule 14.04.2015    source источник


Ответы (3)


Проблема заключается в буферизации OutputStream, которую Джерси использует для буферизации сущности, чтобы определить заголовок Content-Length. Размер буфера по умолчанию 8 кб. Вы можете отключить буферизацию, если хотите, или просто изменить размер буфера с помощью свойства

ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER

Целочисленное значение, определяющее размер буфера, используемого для буферизации объекта ответа на стороне сервера, чтобы определить его размер и установить значение заголовка HTTP Content-Length.

Если размер объекта превышает сконфигурированный размер буфера, буферизация будет отменена, и размер объекта не будет определен. Значение меньше или равное нулю отключает буферизацию сущности вообще.

Это свойство можно использовать на стороне сервера для переопределения значения размера буфера исходящих сообщений — по умолчанию или глобального пользовательского значения, установленного с помощью глобального свойства jersey.config.contentLength.buffer.

Значение по умолчанию — 8192.

Вот пример

@Path("streaming")
public class StreamingResource {

    @GET
    @Produces("application/octet-stream")
    public Response getStream() {
        return Response.ok(new FeedReturnStreamingOutput()).build();
    }

    public static class FeedReturnStreamingOutput implements StreamingOutput {

        @Override
        public void write(OutputStream output)
                throws IOException, WebApplicationException {
            try {
                for (int i = 0; i < 10; i++) {
                    output.write(String.format("Hello %d\n", i).getBytes());
                    output.flush();
                    TimeUnit.MILLISECONDS.sleep(500);
                }
            } catch (InterruptedException e) {  throw new RuntimeException(e); }
        }
    }
}

Вот результат без установки свойства

введите здесь описание изображения

А вот результат после установки значения свойства на 0

public class AppConfig extends ResourceConfig {
    public AppConfig() {
        ...
        property(ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 0);
    }
}

введите здесь описание изображения

person Paul Samsotha    schedule 18.12.2015
comment
Это отличное решение. Вещь, которую я только что нашел - это работает, только если у вас есть символ новой строки в конце ввода :) - person pandaadb; 17.05.2017
comment
@Paul Вы имеете в виду, что без установки OUTBOUND_CONTENT_LENGTH_BUFFER = 0 мы не можем ничего транслировать? то есть, если мы не установим для этого буфера значение 0, он будет буферизовать все в память, а затем сбрасывать его? (что может привести к выходу памяти за пределы, если ответ большой) - person prime; 26.03.2018
comment
@Paul Samsotha, есть ли способ сделать это стандартным способом Java/Jakarta EE? Я использую зависимость jakarta-ee-api и не могу найти класс ResourceConfig, я думаю, потому что он принадлежит Джерси? - person ctwx; 06.10.2020
comment
откуда берется метод свойства? - person DragonMoon; 30.12.2020
comment
@DragonMoon ResourceConfig - person Paul Samsotha; 30.12.2020

Попробуйте вызывать outputStream.flush() из метода FeedReturnStreamingOutput.write(...) через каждые X байтов, записанных в выходной поток, или что-то в этом роде.

Я предполагаю, что буфер соединения не заполнен данными, которые вы возвращаете. Таким образом, служба ничего не возвращает, пока Джерси не вызовет outputStream.close().

В моем случае у меня есть служба, которая передает данные, и я делаю это точно так же, как и вы: возвращая Response.ok(<instance of StreamingOutput>).build();.

Моя служба возвращает данные из базы данных, и я вызываю outputStream.flush() после записи каждой строки в выходной поток.

Я знаю, что служба выполняет потоковую передачу данных, потому что вижу, что клиент начинает получать данные до того, как служба завершит отправку всего результата.

person Montecarlo    schedule 28.04.2015
comment
Я добавлял outputstream.flush каждый раз, когда пишу в выходной поток. Не имеет значения - person PrabhaT; 29.04.2015
comment
@user1016496 user1016496 Откуда вы знаете, что служба не транслируется? Может ли быть так, что служба на самом деле передает данные, но клиент не записывает то, что получает в потоковом режиме? - person Montecarlo; 30.04.2015

Либо ваш ответ слишком мал и никогда не разбивается на части, поэтому сервер сразу сбрасывает весь запрос. Или у вас есть проблема на стороне сервера, когда ваша библиотека jax-rs ожидает полного потока перед очисткой.

Однако это больше похоже на проблему клиента. И вы, кажется, используете старую версию jersey-client.

Кроме того, .get(ClientResponse.class) выглядит подозрительно.

Попробуйте использовать стандарт JAX-RS в его нынешнем виде (по крайней мере в клиент):

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;

Client client = ClientBuilder.newBuilder().build();
WebTarget target = client.target("http://localhost:8080/");
Response response = target.path("path/to/resource").request().get();

При наличии трикотажного клиента 2.17 в пути к классам:

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.17</version>
</dependency>
person Daniel Sperry    schedule 03.05.2015