software.amazon.awssdk.http.SdkAsyncHttpClientH1TestSuite Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http-client-tests Show documentation
Show all versions of http-client-tests Show documentation
A set of acceptance tests that can be extended by an SDK HTTP plugin implementation to help ensure it behaves in
manner consistent with what is expected by the SDK.
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.http;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
/**
* A set of tests validating that the functionality implemented by a {@link SdkAsyncHttpClient} for HTTP/1 requests
*
* This is used by an HTTP plugin implementation by extending this class and implementing the abstract methods to provide this
* suite with a testable HTTP client implementation.
*/
public abstract class SdkAsyncHttpClientH1TestSuite {
private Server server;
private SdkAsyncHttpClient client;
protected abstract SdkAsyncHttpClient setupClient();
@BeforeEach
public void setup() throws Exception {
server = new Server();
server.init();
this.client = setupClient();
}
@AfterEach
public void teardown() throws InterruptedException {
if (server != null) {
server.shutdown();
}
if (client != null) {
client.close();
}
server = null;
}
@Test
public void connectionReceiveServerErrorStatusShouldNotReuseConnection() {
server.return500OnFirstRequest = true;
server.closeConnection = false;
HttpTestUtils.sendGetRequest(server.port(), client).join();
HttpTestUtils.sendGetRequest(server.port(), client).join();
assertThat(server.channels.size()).isEqualTo(2);
}
@Test
public void connectionReceiveOkStatusShouldReuseConnection() throws Exception {
server.return500OnFirstRequest = false;
server.closeConnection = false;
HttpTestUtils.sendGetRequest(server.port(), client).join();
// The request-complete-future does not await the channel-release-future
// Wait a small amount to allow the channel release to complete
Thread.sleep(100);
HttpTestUtils.sendGetRequest(server.port(), client).join();
assertThat(server.channels.size()).isEqualTo(1);
}
@Test
public void connectionReceiveCloseHeaderShouldNotReuseConnection() throws InterruptedException {
server.return500OnFirstRequest = false;
server.closeConnection = true;
HttpTestUtils.sendGetRequest(server.port(), client).join();
Thread.sleep(1000);
HttpTestUtils.sendGetRequest(server.port(), client).join();
assertThat(server.channels.size()).isEqualTo(2);
}
@Test
public void headRequestResponsesHaveNoPayload() {
byte[] responseData = HttpTestUtils.sendHeadRequest(server.port(), client).join();
// The SDK core differentiates between NO data and ZERO bytes of data. Core expects it to be NO data, not ZERO bytes of
// data for head requests.
assertThat(responseData).isNull();
}
@Test
public void naughtyHeaderCharactersDoNotGetToServer() {
String naughtyHeader = "foo\r\nbar";
assertThatThrownBy(() -> HttpTestUtils.sendRequest(client,
SdkHttpFullRequest.builder()
.uri(URI.create("https://localhost:" + server.port()))
.method(SdkHttpMethod.POST)
.appendHeader("h", naughtyHeader)
.build())
.join())
.hasCauseInstanceOf(Exception.class);
}
@Test
public void connectionsArePooledByHostAndPort() throws InterruptedException {
HttpTestUtils.sendRequest(client,
SdkHttpFullRequest.builder()
.uri(URI.create("https://127.0.0.1:" + server.port() + "/foo?foo"))
.method(SdkHttpMethod.GET)
.build())
.join();
Thread.sleep(1_000);
HttpTestUtils.sendRequest(client,
SdkHttpFullRequest.builder()
.uri(URI.create("https://127.0.0.1:" + server.port() + "/bar?bar"))
.method(SdkHttpMethod.GET)
.build())
.join();
assertThat(server.channels.size()).isEqualTo(1);
}
private static class Server extends ChannelInitializer {
private static final byte[] CONTENT = "helloworld".getBytes(StandardCharsets.UTF_8);
private ServerBootstrap bootstrap;
private ServerSocketChannel serverSock;
private List channels = new ArrayList<>();
private final NioEventLoopGroup group = new NioEventLoopGroup();
private SslContext sslCtx;
private boolean return500OnFirstRequest;
private boolean closeConnection;
private volatile HttpRequest lastRequestReceived;
public void init() throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
bootstrap = new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG))
.group(group)
.childHandler(this);
serverSock = (ServerSocketChannel) bootstrap.bind(0).sync().channel();
}
public void shutdown() throws InterruptedException {
group.shutdownGracefully().await();
serverSock.close();
}
public int port() {
return serverSock.localAddress().getPort();
}
@Override
protected void initChannel(Channel ch) {
channels.add(ch);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new BehaviorTestChannelHandler());
}
private class BehaviorTestChannelHandler extends ChannelDuplexHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
lastRequestReceived = (HttpRequest) msg;
HttpResponseStatus status;
if (ctx.channel().equals(channels.get(0)) && return500OnFirstRequest) {
status = INTERNAL_SERVER_ERROR;
} else {
status = OK;
}
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
Unpooled.wrappedBuffer(CONTENT));
response.headers()
.set(CONTENT_TYPE, TEXT_PLAIN)
.setInt(CONTENT_LENGTH, response.content().readableBytes());
if (closeConnection) {
response.headers().set(CONNECTION, CLOSE);
}
ctx.writeAndFlush(response);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy