Подтвердить что ты не робот

Jersey - StreamingOutput как объект ответа

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

@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 называется Jersey client ждет завершения выполнения 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");
4b9b3361

Ответ 1

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

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);
    }
}

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

Ответ 2

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

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

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

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

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

Ответ 3

Либо ваш ответ слишком мал и никогда не получает chunked, поэтому сервер сразу же очищает весь запрос. Или у вас есть проблема с сервером, так как ваша библиотека 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>

Ответ 4

Я просто понял, что если вы используете подкласс StreamingOutput, Джерси не передает ответ:

// works fine
Response.ok(new StreamingOutput() { ... }).build();

// don't work
public interface MyStreamingOutput extends StreamingOutput { }
Response.ok(new MyStreamingOutput() { ... }).build();

Это ошибка Джерси?