Skip to content

Commit c276509

Browse files
committed
Add core async HTTP/2 request-timeout test asserting HttpStreamResetException
1 parent 31bbf5b commit c276509

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
package org.apache.hc.core5.testing.async;
28+
29+
import java.io.IOException;
30+
import java.net.InetSocketAddress;
31+
import java.net.URI;
32+
import java.nio.ByteBuffer;
33+
import java.util.List;
34+
import java.util.concurrent.ExecutionException;
35+
import java.util.concurrent.Future;
36+
import java.util.concurrent.atomic.AtomicBoolean;
37+
38+
import org.apache.hc.core5.function.Supplier;
39+
import org.apache.hc.core5.http.EntityDetails;
40+
import org.apache.hc.core5.http.Header;
41+
import org.apache.hc.core5.http.HttpException;
42+
import org.apache.hc.core5.http.HttpRequest;
43+
import org.apache.hc.core5.http.HttpResponse;
44+
import org.apache.hc.core5.http.HttpStreamResetException;
45+
import org.apache.hc.core5.http.Message;
46+
import org.apache.hc.core5.http.URIScheme;
47+
import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
48+
import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
49+
import org.apache.hc.core5.http.impl.routing.RequestRouter;
50+
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
51+
import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
52+
import org.apache.hc.core5.http.nio.CapacityChannel;
53+
import org.apache.hc.core5.http.nio.DataStreamChannel;
54+
import org.apache.hc.core5.http.nio.ResponseChannel;
55+
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
56+
import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder;
57+
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
58+
import org.apache.hc.core5.http.protocol.HttpContext;
59+
import org.apache.hc.core5.http2.HttpVersionPolicy;
60+
import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
61+
import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
62+
import org.apache.hc.core5.reactor.IOReactorConfig;
63+
import org.apache.hc.core5.reactor.ListenerEndpoint;
64+
import org.apache.hc.core5.testing.SSLTestContexts;
65+
import org.apache.hc.core5.testing.extension.nio.H2AsyncRequesterResource;
66+
import org.apache.hc.core5.testing.extension.nio.H2AsyncServerResource;
67+
import org.apache.hc.core5.util.Timeout;
68+
import org.junit.jupiter.api.Assertions;
69+
import org.junit.jupiter.api.Test;
70+
import org.junit.jupiter.api.extension.RegisterExtension;
71+
72+
class AsyncH2SocketTimeoutCoreTest {
73+
74+
private static final Timeout SOCKET_TIMEOUT = Timeout.ofSeconds(1);
75+
private static final Timeout RESULT_TIMEOUT = Timeout.ofSeconds(30);
76+
77+
@RegisterExtension
78+
private final H2AsyncServerResource serverResource = new H2AsyncServerResource();
79+
80+
@RegisterExtension
81+
private final H2AsyncRequesterResource clientResource = new H2AsyncRequesterResource();
82+
83+
public AsyncH2SocketTimeoutCoreTest() {
84+
serverResource.configure(bootstrap -> bootstrap
85+
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
86+
.setTlsStrategy(new H2ServerTlsStrategy(SSLTestContexts.createServerSSLContext()))
87+
.setIOReactorConfig(
88+
IOReactorConfig.custom()
89+
.setSoTimeout(Timeout.ofSeconds(30))
90+
.build())
91+
.setRequestRouter(
92+
RequestRouter.<Supplier<AsyncServerExchangeHandler>>builder()
93+
.addRoute(
94+
RequestRouter.LOCAL_AUTHORITY,
95+
"*",
96+
SimpleDelayingHandler::new)
97+
.resolveAuthority(RequestRouter.LOCAL_AUTHORITY_RESOLVER)
98+
.build()
99+
)
100+
);
101+
102+
clientResource.configure(bootstrap -> bootstrap
103+
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2)
104+
.setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
105+
.setIOReactorConfig(
106+
IOReactorConfig.custom()
107+
.setSoTimeout(SOCKET_TIMEOUT)
108+
.build())
109+
);
110+
}
111+
112+
@Test
113+
void testHttp2RequestTimeoutYieldsStreamReset() throws Exception {
114+
final InetSocketAddress address = startServer();
115+
final HttpAsyncRequester requester = clientResource.start();
116+
117+
final URI requestUri = new URI("http://localhost:" + address.getPort() + "/timeout");
118+
119+
final AsyncRequestProducer requestProducer = AsyncRequestBuilder.get(requestUri).build();
120+
final BasicResponseConsumer<String> responseConsumer =
121+
new BasicResponseConsumer<>(new StringAsyncEntityConsumer());
122+
123+
final Future<Message<HttpResponse, String>> future =
124+
requester.execute(requestProducer, responseConsumer, SOCKET_TIMEOUT, null);
125+
126+
final ExecutionException ex = Assertions.assertThrows(ExecutionException.class, () -> future.get(RESULT_TIMEOUT.getDuration(), RESULT_TIMEOUT.getTimeUnit()));
127+
128+
final Throwable cause = ex.getCause();
129+
Assertions.assertInstanceOf(HttpStreamResetException.class, cause, "Expected HttpStreamResetException, but got: " + cause);
130+
}
131+
132+
private InetSocketAddress startServer() throws Exception {
133+
final HttpAsyncServer server = serverResource.start();
134+
final ListenerEndpoint listener = server.listen(new InetSocketAddress(0), URIScheme.HTTP).get();
135+
return (InetSocketAddress) listener.getAddress();
136+
}
137+
138+
static final class SimpleDelayingHandler implements AsyncServerExchangeHandler {
139+
140+
private final AtomicBoolean completed = new AtomicBoolean(false);
141+
142+
@Override
143+
public void handleRequest(
144+
final HttpRequest request,
145+
final EntityDetails entityDetails,
146+
final ResponseChannel responseChannel,
147+
final HttpContext context) throws HttpException, IOException {
148+
// Intentionally do nothing: no response is sent back.
149+
}
150+
151+
@Override
152+
public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
153+
// Accept any amount of request data.
154+
capacityChannel.update(Integer.MAX_VALUE);
155+
}
156+
157+
@Override
158+
public void consume(final ByteBuffer src) throws IOException {
159+
// Discard request body if present.
160+
if (src != null) {
161+
src.position(src.limit());
162+
}
163+
}
164+
165+
@Override
166+
public void streamEnd(final List<? extends Header> trailers)
167+
throws HttpException, IOException {
168+
// Nothing special to do on stream end for this test.
169+
}
170+
171+
@Override
172+
public int available() {
173+
// No response body to produce.
174+
return 0;
175+
}
176+
177+
@Override
178+
public void produce(final DataStreamChannel channel) throws IOException {
179+
// In this test we never send a response; just ensure the stream is closed
180+
// if produce gets called.
181+
if (completed.compareAndSet(false, true)) {
182+
channel.endStream();
183+
}
184+
}
185+
186+
@Override
187+
public void failed(final Exception cause) {
188+
// No-op for this simple test handler.
189+
}
190+
191+
@Override
192+
public void releaseResources() {
193+
// No resources to release.
194+
}
195+
196+
}
197+
198+
}

0 commit comments

Comments
 (0)