fix pipelining request release
This commit is contained in:
parent
e87e8f7acc
commit
bd2658601d
15 changed files with 187 additions and 34 deletions
|
@ -1,6 +1,6 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.72.0
|
version = 4.1.72.1
|
||||||
|
|
||||||
org.gradle.warning.mode = ALL
|
org.gradle.warning.mode = ALL
|
||||||
gradle.wrapper.version = 7.3
|
gradle.wrapper.version = 7.3
|
||||||
|
|
|
@ -68,6 +68,8 @@ public interface ServerConfig {
|
||||||
|
|
||||||
boolean isDecompressionEnabled();
|
boolean isDecompressionEnabled();
|
||||||
|
|
||||||
|
boolean isPipeliningEnabled();
|
||||||
|
|
||||||
boolean isInstallHttp2Upgrade();
|
boolean isInstallHttp2Upgrade();
|
||||||
|
|
||||||
Http2Settings getHttp2Settings();
|
Http2Settings getHttp2Settings();
|
||||||
|
@ -200,6 +202,10 @@ public interface ServerConfig {
|
||||||
*/
|
*/
|
||||||
int MAX_CONTENT_LENGTH = 256 * 1024 * 1024;
|
int MAX_CONTENT_LENGTH = 256 * 1024 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP/1 pipelining. Enabled by default.
|
||||||
|
*/
|
||||||
|
boolean ENABLE_PIPELINING = true;
|
||||||
/**
|
/**
|
||||||
* HTTP/1 pipelining capacity. 1024 is very high, it means
|
* HTTP/1 pipelining capacity. 1024 is very high, it means
|
||||||
* 1024 requests can be present for a single client.
|
* 1024 requests can be present for a single client.
|
||||||
|
|
|
@ -22,7 +22,7 @@ public interface ServerResponse extends Flushable {
|
||||||
|
|
||||||
Long getResponseId();
|
Long getResponseId();
|
||||||
|
|
||||||
ByteBufOutputStream getOutputStream();
|
ByteBufOutputStream newOutputStream();
|
||||||
|
|
||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ public class DefaultServerConfig implements ServerConfig {
|
||||||
|
|
||||||
private int maxContentLength = Defaults.MAX_CONTENT_LENGTH;
|
private int maxContentLength = Defaults.MAX_CONTENT_LENGTH;
|
||||||
|
|
||||||
|
private boolean isPipeliningEnabled = Defaults.ENABLE_PIPELINING;
|
||||||
|
|
||||||
private int pipeliningCapacity = Defaults.PIPELINING_CAPACITY;
|
private int pipeliningCapacity = Defaults.PIPELINING_CAPACITY;
|
||||||
|
|
||||||
private int maxCompositeBufferComponents = Defaults.MAX_COMPOSITE_BUFFER_COMPONENTS;
|
private int maxCompositeBufferComponents = Defaults.MAX_COMPOSITE_BUFFER_COMPONENTS;
|
||||||
|
@ -283,6 +285,15 @@ public class DefaultServerConfig implements ServerConfig {
|
||||||
return maxContentLength;
|
return maxContentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerConfig setPipelining(boolean isPipeliningEnabled) {
|
||||||
|
this.isPipeliningEnabled = isPipeliningEnabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPipeliningEnabled() {
|
||||||
|
return isPipeliningEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public ServerConfig setPipeliningCapacity(int pipeliningCapacity) {
|
public ServerConfig setPipeliningCapacity(int pipeliningCapacity) {
|
||||||
this.pipeliningCapacity = pipeliningCapacity;
|
this.pipeliningCapacity = pipeliningCapacity;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -662,6 +662,16 @@ public final class Server implements AutoCloseable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder enablePipelining(boolean enablePipelining) {
|
||||||
|
this.serverConfig.setPipelining(enablePipelining);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPipeliningCapacity(int pipeliningCapacity) {
|
||||||
|
this.serverConfig.setPipeliningCapacity(pipeliningCapacity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setInstallHttp2Upgrade(boolean installHttp2Upgrade) {
|
public Builder setInstallHttp2Upgrade(boolean installHttp2Upgrade) {
|
||||||
this.serverConfig.setInstallHttp2Upgrade(installHttp2Upgrade);
|
this.serverConfig.setInstallHttp2Upgrade(installHttp2Upgrade);
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -108,8 +108,10 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
pipeline.addLast("http-server-ws-handler",
|
pipeline.addLast("http-server-ws-handler",
|
||||||
serverConfig.getWebSocketFrameHandler());
|
serverConfig.getWebSocketFrameHandler());
|
||||||
}
|
}
|
||||||
pipeline.addLast("http-server-pipelining",
|
if (serverConfig.isPipeliningEnabled()) {
|
||||||
new HttpPipeliningHandler(serverConfig.getPipeliningCapacity()));
|
pipeline.addLast("http-server-pipelining",
|
||||||
|
new HttpPipeliningHandler(serverConfig.getPipeliningCapacity()));
|
||||||
|
}
|
||||||
pipeline.addLast("http-server-handler",
|
pipeline.addLast("http-server-handler",
|
||||||
new ServerMessages(server));
|
new ServerMessages(server));
|
||||||
pipeline.addLast("http-idle-timeout-handler",
|
pipeline.addLast("http-idle-timeout-handler",
|
||||||
|
@ -149,8 +151,22 @@ public class Http1ChannelInitializer extends ChannelInitializer<Channel>
|
||||||
ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
||||||
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
|
transport.requestReceived(ctx, fullHttpRequest, httpPipelinedRequest.getSequenceId());
|
||||||
}
|
}
|
||||||
fullHttpRequest.release();
|
|
||||||
}
|
}
|
||||||
|
if (httpPipelinedRequest.refCnt() > 0) {
|
||||||
|
httpPipelinedRequest.release();
|
||||||
|
}
|
||||||
|
} else if (msg instanceof FullHttpRequest){
|
||||||
|
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
|
||||||
|
if (fullHttpRequest.protocolVersion().majorVersion() == 2) {
|
||||||
|
// PRI * HTTP/2.0
|
||||||
|
DefaultHttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
|
||||||
|
HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
|
||||||
|
ctx.channel().writeAndFlush(response);
|
||||||
|
} else {
|
||||||
|
ServerTransport transport = server.newTransport(fullHttpRequest.protocolVersion());
|
||||||
|
transport.requestReceived(ctx, fullHttpRequest, 0);
|
||||||
|
}
|
||||||
|
fullHttpRequest.release();
|
||||||
} else {
|
} else {
|
||||||
super.channelRead(ctx, msg);
|
super.channelRead(ctx, msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package org.xbib.netty.http.server.protocol.http1;
|
package org.xbib.netty.http.server.protocol.http1;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
import io.netty.util.ReferenceCounted;
|
||||||
|
|
||||||
public class HttpPipelinedRequest {
|
public class HttpPipelinedRequest implements ReferenceCounted {
|
||||||
|
|
||||||
private final LastHttpContent request;
|
private final LastHttpContent request;
|
||||||
|
|
||||||
|
@ -13,6 +16,10 @@ public class HttpPipelinedRequest {
|
||||||
this.sequenceId = sequenceId;
|
this.sequenceId = sequenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpPipelinedResponse createHttpResponse(FullHttpResponse response, ChannelPromise promise) {
|
||||||
|
return new HttpPipelinedResponse(response, promise, sequenceId);
|
||||||
|
}
|
||||||
|
|
||||||
public LastHttpContent getRequest() {
|
public LastHttpContent getRequest() {
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
@ -20,4 +27,43 @@ public class HttpPipelinedRequest {
|
||||||
public int getSequenceId() {
|
public int getSequenceId() {
|
||||||
return sequenceId;
|
return sequenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return request.refCnt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted retain() {
|
||||||
|
request.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted retain(int increment) {
|
||||||
|
request.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted touch() {
|
||||||
|
request.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted touch(Object hint) {
|
||||||
|
request.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return request.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int decrement) {
|
||||||
|
return request.release(decrement);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package org.xbib.netty.http.server.protocol.http1;
|
package org.xbib.netty.http.server.protocol.http1;
|
||||||
|
|
||||||
import io.netty.channel.ChannelPromise;
|
import io.netty.channel.ChannelPromise;
|
||||||
|
import io.netty.handler.codec.http.FullHttpResponse;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.util.ReferenceCounted;
|
||||||
|
|
||||||
public class HttpPipelinedResponse implements Comparable<HttpPipelinedResponse> {
|
public class HttpPipelinedResponse implements ReferenceCounted, Comparable<HttpPipelinedResponse> {
|
||||||
|
|
||||||
|
private final FullHttpResponse response;
|
||||||
|
|
||||||
private final HttpResponse response;
|
|
||||||
private final ChannelPromise promise;
|
private final ChannelPromise promise;
|
||||||
|
|
||||||
private final int sequenceId;
|
private final int sequenceId;
|
||||||
|
|
||||||
public HttpPipelinedResponse(HttpResponse response, ChannelPromise promise, int sequenceId) {
|
public HttpPipelinedResponse(FullHttpResponse response, ChannelPromise promise, int sequenceId) {
|
||||||
this.response = response;
|
this.response = response;
|
||||||
this.promise = promise;
|
this.promise = promise;
|
||||||
this.sequenceId = sequenceId;
|
this.sequenceId = sequenceId;
|
||||||
|
@ -29,6 +33,45 @@ public class HttpPipelinedResponse implements Comparable<HttpPipelinedResponse>
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(HttpPipelinedResponse other) {
|
public int compareTo(HttpPipelinedResponse other) {
|
||||||
return this.sequenceId - other.sequenceId;
|
return Integer.compare(this.sequenceId, other.sequenceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return response.refCnt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted retain() {
|
||||||
|
response.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted retain(int increment) {
|
||||||
|
response.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted touch() {
|
||||||
|
response.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReferenceCounted touch(Object hint) {
|
||||||
|
response.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return response.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int decrement) {
|
||||||
|
return response.release(decrement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.PriorityQueue;
|
import java.util.PriorityQueue;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
@ -46,15 +47,17 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
|
||||||
public HttpPipeliningHandler(int pipelineCapacity) {
|
public HttpPipeliningHandler(int pipelineCapacity) {
|
||||||
this.pipelineCapacity = pipelineCapacity;
|
this.pipelineCapacity = pipelineCapacity;
|
||||||
this.lock = new ReentrantLock();
|
this.lock = new ReentrantLock();
|
||||||
this.httpPipelinedResponses = new PriorityQueue<>(3);
|
this.httpPipelinedResponses = new PriorityQueue<>(1);
|
||||||
this.requestCounter = new AtomicInteger();
|
this.requestCounter = new AtomicInteger();
|
||||||
this.writtenRequests = new AtomicInteger();
|
this.writtenRequests = new AtomicInteger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
if (msg instanceof LastHttpContent) {
|
if (msg instanceof LastHttpContent) {
|
||||||
super.channelRead(ctx, new HttpPipelinedRequest((LastHttpContent) msg, requestCounter.getAndIncrement()));
|
ctx.fireChannelRead(new HttpPipelinedRequest((LastHttpContent) msg, requestCounter.getAndIncrement()));
|
||||||
|
} else {
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +93,23 @@ public class HttpPipeliningHandler extends ChannelDuplexHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
|
||||||
|
if (!httpPipelinedResponses.isEmpty()) {
|
||||||
|
ClosedChannelException closedChannelException = new ClosedChannelException();
|
||||||
|
HttpPipelinedResponse pipelinedResponse;
|
||||||
|
while ((pipelinedResponse = httpPipelinedResponses.poll()) != null) {
|
||||||
|
try {
|
||||||
|
pipelinedResponse.release();
|
||||||
|
pipelinedResponse.getPromise().setFailure(closedChannelException);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "unexpected error while releasing pipelined http responses", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.close(promise);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
String message = cause.getMessage() == null ? "null" : cause.getMessage();
|
String message = cause.getMessage() == null ? "null" : cause.getMessage();
|
||||||
|
|
|
@ -98,7 +98,7 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBufOutputStream getOutputStream() {
|
public ByteBufOutputStream newOutputStream() {
|
||||||
return new ByteBufOutputStream(ctx.alloc().buffer());
|
return new ByteBufOutputStream(ctx.alloc().buffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBufOutputStream getOutputStream() {
|
public ByteBufOutputStream newOutputStream() {
|
||||||
return new ByteBufOutputStream(ctx.alloc().buffer());
|
return new ByteBufOutputStream(ctx.alloc().buffer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInstance;
|
import org.junit.jupiter.api.TestInstance;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -49,14 +48,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@Disabled /* flaky */
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
@ExtendWith(NettyHttpTestExtension.class)
|
@ExtendWith(NettyHttpTestExtension.class)
|
||||||
class HttpPipeliningHandlerTest {
|
class HttpPipeliningHandlerTest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(HttpPipeliningHandlerTest.class.getName());
|
private static final Logger logger = Logger.getLogger(HttpPipeliningHandlerTest.class.getName());
|
||||||
|
|
||||||
private static Map<String, CountDownLatch> waitingRequests = new ConcurrentHashMap<>();
|
private static final Map<String, CountDownLatch> waitingRequests = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
void closeResources() {
|
void closeResources() {
|
||||||
|
@ -67,7 +65,7 @@ class HttpPipeliningHandlerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testThatPipeliningWorksWithFastSerializedRequests() {
|
void testThatPipeliningWorksWithFastSerializedRequests() {
|
||||||
WorkEmulatorHandler handler = new WorkEmulatorHandler();
|
WorkEmulatorHandler handler = new WorkEmulatorHandler(Executors.newCachedThreadPool());
|
||||||
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
|
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
|
||||||
handler);
|
handler);
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
|
@ -85,7 +83,7 @@ class HttpPipeliningHandlerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testThatPipeliningWorksWhenSlowRequestsInDifferentOrder() {
|
void testThatPipeliningWorksWhenSlowRequestsInDifferentOrder() {
|
||||||
WorkEmulatorHandler handler = new WorkEmulatorHandler();
|
WorkEmulatorHandler handler = new WorkEmulatorHandler(Executors.newCachedThreadPool());
|
||||||
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
|
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(10000),
|
||||||
handler);
|
handler);
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
|
@ -105,7 +103,7 @@ class HttpPipeliningHandlerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testThatPipeliningWorksWithChunkedRequests() {
|
void testThatPipeliningWorksWithChunkedRequests() {
|
||||||
WorkEmulatorHandler handler = new WorkEmulatorHandler();
|
WorkEmulatorHandler handler = new WorkEmulatorHandler(Executors.newCachedThreadPool());
|
||||||
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new AggregateUrisAndHeadersHandler(),
|
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new AggregateUrisAndHeadersHandler(),
|
||||||
new HttpPipeliningHandler(10000), handler);
|
new HttpPipeliningHandler(10000), handler);
|
||||||
DefaultHttpRequest httpRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/0");
|
DefaultHttpRequest httpRequest = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/0");
|
||||||
|
@ -126,7 +124,7 @@ class HttpPipeliningHandlerTest {
|
||||||
@Test
|
@Test
|
||||||
void testThatPipeliningClosesConnectionWithTooManyEvents() {
|
void testThatPipeliningClosesConnectionWithTooManyEvents() {
|
||||||
assertThrows(ClosedChannelException.class, () -> {
|
assertThrows(ClosedChannelException.class, () -> {
|
||||||
WorkEmulatorHandler handler = new WorkEmulatorHandler();
|
WorkEmulatorHandler handler = new WorkEmulatorHandler(Executors.newCachedThreadPool());
|
||||||
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(2),
|
EmbeddedChannel embeddedChannel = new EmbeddedChannel(new HttpPipeliningHandler(2),
|
||||||
handler);
|
handler);
|
||||||
embeddedChannel.writeInbound(createHttpRequest("/0"));
|
embeddedChannel.writeInbound(createHttpRequest("/0"));
|
||||||
|
@ -170,7 +168,11 @@ class HttpPipeliningHandlerTest {
|
||||||
|
|
||||||
private static class WorkEmulatorHandler extends SimpleChannelInboundHandler<HttpPipelinedRequest> {
|
private static class WorkEmulatorHandler extends SimpleChannelInboundHandler<HttpPipelinedRequest> {
|
||||||
|
|
||||||
private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
private final ExecutorService executorService;
|
||||||
|
|
||||||
|
WorkEmulatorHandler(ExecutorService executorService) {
|
||||||
|
this.executorService = executorService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest pipelinedRequest) {
|
protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest pipelinedRequest) {
|
||||||
|
@ -188,7 +190,6 @@ class HttpPipeliningHandlerTest {
|
||||||
httpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
|
httpResponse.headers().add(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
waitingRequests.put(uri, latch);
|
waitingRequests.put(uri, latch);
|
||||||
// can cause RejectedExecutionException if executorService is too small
|
|
||||||
executorService.submit(() -> {
|
executorService.submit(() -> {
|
||||||
try {
|
try {
|
||||||
latch.await(2, TimeUnit.SECONDS);
|
latch.await(2, TimeUnit.SECONDS);
|
||||||
|
|
|
@ -44,17 +44,16 @@ class CleartextTest {
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
AtomicInteger counter = new AtomicInteger();
|
AtomicInteger counter = new AtomicInteger();
|
||||||
final ResponseListener<HttpResponse> responseListener = resp -> {
|
|
||||||
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
|
|
||||||
logger.log(Level.INFO, resp.getBodyAsString(StandardCharsets.UTF_8));
|
|
||||||
counter.incrementAndGet();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
||||||
.url(server.getServerConfig().getAddress().base())
|
.url(server.getServerConfig().getAddress().base())
|
||||||
.content("Hello world", "text/plain")
|
.content("Hello world", "text/plain")
|
||||||
.setResponseListener(responseListener)
|
.setResponseListener(resp -> {
|
||||||
|
if (resp.getStatus().getCode() == HttpResponseStatus.OK.code()) {
|
||||||
|
logger.log(Level.INFO, resp.getBodyAsString(StandardCharsets.UTF_8));
|
||||||
|
counter.incrementAndGet();
|
||||||
|
}
|
||||||
|
})
|
||||||
.build();
|
.build();
|
||||||
client.execute(request).get();
|
client.execute(request).get();
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -74,7 +73,8 @@ class CleartextTest {
|
||||||
.setContentType("text/plain").build()
|
.setContentType("text/plain").build()
|
||||||
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain).build();
|
Server server = Server.builder(domain)
|
||||||
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
|
|
@ -29,7 +29,7 @@ class StreamTest {
|
||||||
ByteBufInputStream inputStream = request.getInputStream();
|
ByteBufInputStream inputStream = request.getInputStream();
|
||||||
String content = inputStream.readLine();
|
String content = inputStream.readLine();
|
||||||
assertEquals("my body parameter", content);
|
assertEquals("my body parameter", content);
|
||||||
ByteBufOutputStream outputStream = response.getOutputStream();
|
ByteBufOutputStream outputStream = response.newOutputStream();
|
||||||
outputStream.writeBytes("Hello World");
|
outputStream.writeBytes("Hello World");
|
||||||
response.getBuilder().setStatus(HttpResponseStatus.OK.code()).setContentType("text/plain").build()
|
response.getBuilder().setStatus(HttpResponseStatus.OK.code()).setContentType("text/plain").build()
|
||||||
.write(outputStream);
|
.write(outputStream);
|
||||||
|
|
|
@ -26,7 +26,7 @@ class StreamTest {
|
||||||
ByteBufInputStream inputStream = request.getInputStream();
|
ByteBufInputStream inputStream = request.getInputStream();
|
||||||
String content = inputStream.readLine();
|
String content = inputStream.readLine();
|
||||||
assertEquals("my body parameter", content);
|
assertEquals("my body parameter", content);
|
||||||
ByteBufOutputStream outputStream = response.getOutputStream();
|
ByteBufOutputStream outputStream = response.newOutputStream();
|
||||||
outputStream.writeBytes("Hello World");
|
outputStream.writeBytes("Hello World");
|
||||||
response.getBuilder().setStatus(HttpResponseStatus.OK.code())
|
response.getBuilder().setStatus(HttpResponseStatus.OK.code())
|
||||||
.setContentType("text/plain")
|
.setContentType("text/plain")
|
||||||
|
|
Loading…
Reference in a new issue