use of com.hotels.styx.api.Buffer in project styx by ExpediaGroup.
the class HttpResponseWriterTest method completesFutureOnlyAfterAllWritesAreSuccessfullyCompleted.
@Test
public void completesFutureOnlyAfterAllWritesAreSuccessfullyCompleted() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new CaptureChannelArgumentsHandler(channelArgs), new LoggingHandler(), new SimpleChannelInboundHandler<LiveHttpResponse>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LiveHttpResponse response) throws Exception {
HttpResponseWriter writer = new HttpResponseWriter(ctx);
CompletableFuture<Void> future = writer.write(response);
assertThat(future.isDone(), is(false));
contentObservable.onNext(new Buffer("aaa", UTF_8));
assertThat(future.isDone(), is(false));
contentObservable.onComplete();
assertThat(future.isDone(), is(false));
// For response headers
writeAck(channelArgs);
// For content chunk
writeAck(channelArgs);
// For EMPTY_LAST_CHUNK
writeAck(channelArgs);
assertThat(future.isDone(), is(true));
channelRead.set(true);
}
});
ch.writeInbound(response(OK).body(new ByteStream(contentObservable)).build());
assertThat(channelRead.get(), is(true));
}
use of com.hotels.styx.api.Buffer in project styx by ExpediaGroup.
the class HttpResponseWriterTest method failsTheResultWhenContentWriteFails.
@Test
public void failsTheResultWhenContentWriteFails() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new CaptureChannelArgumentsHandler(channelArgs), new SimpleChannelInboundHandler<LiveHttpResponse>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LiveHttpResponse response) throws Exception {
HttpResponseWriter writer = new HttpResponseWriter(ctx);
CompletableFuture<Void> future = writer.write(response);
writeAck(channelArgs);
assertThat(future.isDone(), is(false));
contentObservable.onNext(new Buffer("aaa", UTF_8));
assertThat(future.isDone(), is(false));
contentObservable.onComplete();
assertThat(future.isDone(), is(false));
writeError(channelArgs);
assertThat(future.isDone(), is(true));
future.get(200, MILLISECONDS);
}
});
assertThrows(ExecutionException.class, () -> ch.writeInbound(response(OK).body(new ByteStream(contentObservable)).build()));
}
use of com.hotels.styx.api.Buffer in project styx by ExpediaGroup.
the class HttpResponseWriterTest method logsSentAndAcknowledgedBytes.
@Test
public void logsSentAndAcknowledgedBytes() {
EmbeddedChannel ch = new EmbeddedChannel(new SimpleChannelInboundHandler<LiveHttpResponse>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LiveHttpResponse response) throws Exception {
HttpResponseWriter writer = new HttpResponseWriter(ctx);
CompletableFuture<Void> future = writer.write(response);
assertThat(future.isDone(), is(false));
contentObservable.onNext(new Buffer("aaa", UTF_8));
assertThat(future.isDone(), is(false));
contentObservable.onNext(new Buffer("bbbb", UTF_8));
assertThat(future.isDone(), is(false));
contentObservable.onError(new TransportLostException(new InetSocketAddress(getLoopbackAddress(), 5050), newOriginBuilder("localhost", 5050).build()));
assertThat(future.isDone(), is(true));
channelRead.set(true);
}
});
ch.writeInbound(response(OK).body(new ByteStream(contentObservable)).build());
assertThat(LOGGER.lastMessage(), is(loggingEvent(Level.WARN, "Content observable error. Written content bytes 7/7 \\(ackd/sent\\). Write events 3/3 \\(ackd/writes\\).*", TransportLostException.class, "Connection to origin lost. origin=\"generic-app:anonymous-origin:localhost:5050\", remoteAddress=\"localhost/127.0.0.1:5050.*")));
}
use of com.hotels.styx.api.Buffer in project styx by ExpediaGroup.
the class HttpPipelineHandler method exceptionToResponse.
private LiveHttpResponse exceptionToResponse(Throwable cause, LiveHttpRequest request, CharSequence originsHeaderName) {
HttpResponseStatus status = status(cause instanceof PluginException ? cause.getCause() : cause);
String message = status.code() >= 500 ? "Site temporarily unavailable." : status.description();
LiveHttpResponse.Transformer builder = responseEnhancer.enhance(response(status).body(new ByteStream(Flux.just(new Buffer(message, UTF_8)))).build().newBuilder(), request).header(CONTENT_LENGTH, message.getBytes(UTF_8).length).header(CONNECTION, "close");
if (originsHeaderName != null && originFromException(cause) != null) {
return builder.header(originsHeaderName, originFromException(cause)).build();
} else {
return builder.build();
}
}
use of com.hotels.styx.api.Buffer in project styx by ExpediaGroup.
the class HttpResponseWriter method write.
// CHECKSTYLE:OFF
public CompletableFuture<Void> write(LiveHttpResponse response) {
CompletableFuture<Void> future = new CompletableFuture<>();
try {
writeHeaders(response).addListener((ChannelFutureListener) writeOp -> {
if (writeOp.isSuccess()) {
writeOpsAcked.incrementAndGet();
} else {
LOGGER.warn("Unable to send response headers. Written content bytes {}/{} (ackd/sent). Write events {}/{} (ackd/writes). Exception={}", new Object[] { contentBytesAcked.get(), contentBytesWritten.get(), writeOpsAcked.get(), writeOps.get(), writeOp.cause() });
future.completeExceptionally(writeOp.cause());
}
});
response.body().subscribe(new BaseSubscriber<Buffer>() {
@Override
public void hookOnSubscribe(Subscription subscription) {
future.handle((ignore, cause) -> {
if (future.isCompletedExceptionally() && cause instanceof CancellationException) {
subscription.cancel();
}
return null;
});
subscription.request(1);
}
@Override
public void hookOnComplete() {
if (!future.isDone()) {
nettyWriteAndFlush(EMPTY_LAST_CONTENT).addListener((ChannelFutureListener) this::onWriteEmptyLastChunkOutcome);
contentCompleted.set(true);
completeIfAllSent(future);
}
}
@Override
public void hookOnError(Throwable cause) {
LOGGER.warn("Content observable error. Written content bytes {}/{} (ackd/sent). Write events {}/{} (ackd/writes). Exception={}", new Object[] { contentBytesAcked.get(), contentBytesWritten.get(), writeOpsAcked.get(), writeOps.get(), cause });
future.completeExceptionally(cause);
}
@Override
public void hookOnNext(Buffer buffer) {
ByteBuf byteBuf = Buffers.toByteBuf(buffer);
if (future.isDone()) {
byteBuf.release();
} else {
long bufSize = (long) byteBuf.readableBytes();
contentBytesWritten.addAndGet(bufSize);
nettyWriteAndFlush(new DefaultHttpContent(byteBuf)).addListener(it -> onWriteOutcome((ChannelFuture) it, bufSize));
}
}
private void onWriteOutcome(ChannelFuture writeOp, long bufSize) {
if (writeOp.isSuccess()) {
contentBytesAcked.addAndGet(bufSize);
writeOpsAcked.incrementAndGet();
request(1);
completeIfAllSent(future);
} else if (!future.isDone()) {
// Suppress messages if future has already failed, or completed for other reason:
cancel();
LOGGER.warn("Write error. Written content bytes {}/{} (ackd/sent). Write events {}/{} (ackd/writes), Exception={}", new Object[] { contentBytesAcked.get(), contentBytesWritten.get(), writeOpsAcked.get(), writeOps.get(), response, writeOp.cause() });
future.completeExceptionally(writeOp.cause());
}
}
private void onWriteEmptyLastChunkOutcome(ChannelFuture writeOp) {
writeOpsAcked.incrementAndGet();
completeIfAllSent(future);
cancel();
}
});
return future;
} catch (Throwable cause) {
LOGGER.warn("Failed to convert response headers. response={}, Cause={}", new Object[] { response, cause });
toObservable(response.body()).forEach(it -> Buffers.toByteBuf(it).release());
future.completeExceptionally(cause);
return future;
}
}
Aggregations