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");
Ответы
Ответ 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();
Это ошибка Джерси?