From 245a9f7cf701cad68c9c39fb81e226d73cd816e9 Mon Sep 17 00:00:00 2001 From: Mateusz Rzeszutek Date: Mon, 30 Jan 2023 08:46:05 +0100 Subject: [PATCH] Rewrite netty 3.8 tests to Java (#7666) Part of #7195 --- .../src/test/groovy/Netty38ClientTest.groovy | 147 ------------ .../src/test/groovy/Netty38ServerTest.groovy | 171 -------------- .../netty/v3_8/client/Netty38ClientTest.java | 181 +++++++++++++++ .../netty/v3_8/server/Netty38ServerTest.java | 211 ++++++++++++++++++ 4 files changed, 392 insertions(+), 318 deletions(-) delete mode 100644 instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ClientTest.groovy delete mode 100644 instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ServerTest.groovy create mode 100644 instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/Netty38ClientTest.java create mode 100644 instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/Netty38ServerTest.java diff --git a/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ClientTest.groovy b/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ClientTest.groovy deleted file mode 100644 index ed4c44a10df8..000000000000 --- a/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ClientTest.groovy +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import com.ning.http.client.AsyncCompletionHandler -import com.ning.http.client.AsyncHttpClient -import com.ning.http.client.AsyncHttpClientConfig -import com.ning.http.client.Request -import com.ning.http.client.RequestBuilder -import com.ning.http.client.Response -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.context.Context -import io.opentelemetry.context.Scope -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpClientTest -import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import spock.lang.AutoCleanup -import spock.lang.Shared - -import java.nio.channels.ClosedChannelException - -class Netty38ClientTest extends HttpClientTest implements AgentTestTrait { - - @Shared - @AutoCleanup - AsyncHttpClient client = new AsyncHttpClient(getClientConfig()) - - def getClientConfig() { - def builder = new AsyncHttpClientConfig.Builder() - .setUserAgent("test-user-agent") - - if (builder.metaClass.getMetaMethod("setConnectTimeout", int) != null) { - builder.setConnectTimeout(CONNECT_TIMEOUT_MS) - } else { - builder.setRequestTimeoutInMs(CONNECT_TIMEOUT_MS) - } - if (builder.metaClass.getMetaMethod("setFollowRedirect", boolean) != null) { - builder.setFollowRedirect(true) - } else { - builder.setFollowRedirects(true) - } - if (builder.metaClass.getMetaMethod("setMaxRedirects", int) != null) { - builder.setMaxRedirects(3) - } else { - builder.setMaximumNumberOfRedirects(3) - } - // with connection pooling is enabled there are occasional failures in high concurrency test - if (builder.metaClass.getMetaMethod("setAllowPoolingConnections", boolean) != null) { - builder.setAllowPoolingConnections(false) - } else { - builder.setAllowPoolingConnection(false) - } - - return builder.build() - } - - @Override - Request buildRequest(String method, URI uri, Map headers) { - def requestBuilder = new RequestBuilder(method) - .setUrl(uri.toString()) - headers.entrySet().each { - requestBuilder.addHeader(it.key, it.value) - } - return requestBuilder.build() - } - - @Override - int sendRequest(Request request, String method, URI uri, Map headers) { - return client.executeRequest(request).get().statusCode - } - - @Override - void sendRequestWithCallback(Request request, String method, URI uri, Map headers, HttpClientResult requestResult) { - // TODO: context is not automatically propagated into callbacks - Context context = Context.current() - // TODO(anuraaga): Do we also need to test ListenableFuture callback? - client.executeRequest(request, new AsyncCompletionHandler() { - @Override - Void onCompleted(Response response) throws Exception { - try (Scope scope = context.makeCurrent()) { - requestResult.complete(response.statusCode) - } - return null - } - - @Override - void onThrowable(Throwable throwable) { - try (Scope scope = context.makeCurrent()) { - requestResult.complete(throwable) - } - } - }) - } - - @Override - String userAgent() { - return "test-user-agent" - } - - @Override - String expectedClientSpanName(URI uri, String method) { - switch (uri.toString()) { - case "http://localhost:61/": // unopened port - case "http://192.0.2.1/": // non routable address - return "CONNECT" - default: - return super.expectedClientSpanName(uri, method) - } - } - - @Override - Throwable clientSpanError(URI uri, Throwable exception) { - switch (uri.toString()) { - case "http://localhost:61/": // unopened port - exception = exception.getCause() != null ? exception.getCause() : new ConnectException("Connection refused: localhost/127.0.0.1:61") - break - case "http://192.0.2.1/": // non routable address - exception = exception.getCause() != null ? exception.getCause() : new ClosedChannelException() - } - return exception - } - - @Override - Set> httpAttributes(URI uri) { - switch (uri.toString()) { - case "http://localhost:61/": // unopened port - case "http://192.0.2.1/": // non routable address - return [] - } - def attributes = super.httpAttributes(uri) - attributes.remove(SemanticAttributes.NET_PEER_NAME) - attributes.remove(SemanticAttributes.NET_PEER_PORT) - return attributes - } - - @Override - boolean testRedirects() { - false - } - - @Override - boolean testHttps() { - false - } -} diff --git a/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ServerTest.groovy b/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ServerTest.groovy deleted file mode 100644 index 968ffcdb8d56..000000000000 --- a/instrumentation/netty/netty-3.8/javaagent/src/test/groovy/Netty38ServerTest.groovy +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import io.opentelemetry.api.common.AttributeKey -import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import org.jboss.netty.bootstrap.ServerBootstrap -import org.jboss.netty.buffer.ChannelBuffer -import org.jboss.netty.buffer.ChannelBuffers -import org.jboss.netty.channel.ChannelHandlerContext -import org.jboss.netty.channel.ChannelPipeline -import org.jboss.netty.channel.ChannelPipelineFactory -import org.jboss.netty.channel.DefaultChannelPipeline -import org.jboss.netty.channel.DownstreamMessageEvent -import org.jboss.netty.channel.ExceptionEvent -import org.jboss.netty.channel.FailedChannelFuture -import org.jboss.netty.channel.MessageEvent -import org.jboss.netty.channel.SimpleChannelHandler -import org.jboss.netty.channel.SucceededChannelFuture -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory -import org.jboss.netty.handler.codec.http.DefaultHttpResponse -import org.jboss.netty.handler.codec.http.HttpRequest -import org.jboss.netty.handler.codec.http.HttpResponse -import org.jboss.netty.handler.codec.http.HttpResponseStatus -import org.jboss.netty.handler.codec.http.HttpServerCodec -import org.jboss.netty.handler.codec.http.QueryStringDecoder -import org.jboss.netty.handler.logging.LoggingHandler -import org.jboss.netty.logging.InternalLogLevel -import org.jboss.netty.logging.InternalLoggerFactory -import org.jboss.netty.logging.Slf4JLoggerFactory -import org.jboss.netty.util.CharsetUtil - -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.CAPTURE_HEADERS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS -import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.forPath -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE -import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LOCATION -import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1 - -class Netty38ServerTest extends HttpServerTest implements AgentTestTrait { - - static final LoggingHandler LOGGING_HANDLER - static { - InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()) - LOGGING_HANDLER = new LoggingHandler(SERVER_LOGGER.name, InternalLogLevel.DEBUG, true) - } - - ChannelPipeline channelPipeline() { - ChannelPipeline channelPipeline = new DefaultChannelPipeline() - channelPipeline.addFirst("logger", LOGGING_HANDLER) - - channelPipeline.addLast("http-codec", new HttpServerCodec()) - channelPipeline.addLast("controller", new SimpleChannelHandler() { - @Override - void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) throws Exception { - if (msg.getMessage() instanceof HttpRequest) { - def request = msg.getMessage() as HttpRequest - def uri = URI.create(request.getUri()) - ServerEndpoint endpoint = forPath(uri.path) - ctx.sendDownstream controller(endpoint) { - HttpResponse response - ChannelBuffer responseContent = null - switch (endpoint) { - case SUCCESS: - case ERROR: - responseContent = ChannelBuffers.copiedBuffer(endpoint.body, CharsetUtil.UTF_8) - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.setContent(responseContent) - break - case INDEXED_CHILD: - responseContent = ChannelBuffers.EMPTY_BUFFER - endpoint.collectSpanAttributes { new QueryStringDecoder(uri).getParameters().get(it).find() } - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.setContent(responseContent) - break - case QUERY_PARAM: - responseContent = ChannelBuffers.copiedBuffer(uri.query, CharsetUtil.UTF_8) - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.setContent(responseContent) - break - case REDIRECT: - responseContent = ChannelBuffers.EMPTY_BUFFER - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.setContent(responseContent) - response.headers().set(LOCATION, endpoint.body) - break - case CAPTURE_HEADERS: - responseContent = ChannelBuffers.copiedBuffer(endpoint.body, CharsetUtil.UTF_8) - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.headers().set("X-Test-Response", request.headers().get("X-Test-Request")) - response.setContent(responseContent) - break - case EXCEPTION: - throw new Exception(endpoint.body) - default: - responseContent = ChannelBuffers.copiedBuffer(NOT_FOUND.body, CharsetUtil.UTF_8) - response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status)) - response.setContent(responseContent) - break - } - response.headers().set(CONTENT_TYPE, "text/plain") - if (responseContent) { - response.headers().set(CONTENT_LENGTH, responseContent.readableBytes()) - } - return new DownstreamMessageEvent( - ctx.getChannel(), - new SucceededChannelFuture(ctx.getChannel()), - response, - ctx.getChannel().getRemoteAddress()) - } - } - } - - @Override - void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception { - def message = ex.cause == null ? " " + ex.message : ex.cause.message == null ? "" : ex.cause.message - ChannelBuffer buffer = ChannelBuffers.copiedBuffer(message, CharsetUtil.UTF_8) - HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR) - response.setContent(buffer) - response.headers().set(CONTENT_TYPE, "text/plain") - response.headers().set(CONTENT_LENGTH, buffer.readableBytes()) - ctx.sendDownstream(new DownstreamMessageEvent( - ctx.getChannel(), - new FailedChannelFuture(ctx.getChannel(), ex.getCause()), - response, - ctx.getChannel().getRemoteAddress())) - } - }) - - return channelPipeline - } - - @Override - ServerBootstrap startServer(int port) { - ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory()) - bootstrap.setParentHandler(LOGGING_HANDLER) - bootstrap.setPipelineFactory(new ChannelPipelineFactory() { - @Override - ChannelPipeline getPipeline() throws Exception { - return channelPipeline() - } - }) - - InetSocketAddress address = new InetSocketAddress(port) - bootstrap.bind(address) - return bootstrap - } - - @Override - void stopServer(ServerBootstrap server) { - server?.shutdown() - } - - @Override - Set> httpAttributes(ServerEndpoint endpoint) { - def attributes = super.httpAttributes(endpoint) - attributes.remove(SemanticAttributes.HTTP_ROUTE) - attributes - } -} diff --git a/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/Netty38ClientTest.java b/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/Netty38ClientTest.java new file mode 100644 index 000000000000..f73df1d64da4 --- /dev/null +++ b/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/client/Netty38ClientTest.java @@ -0,0 +1,181 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.netty.v3_8.client; + +import static java.util.Collections.emptySet; + +import com.ning.http.client.AsyncCompletionHandler; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.AsyncHttpClientConfig; +import com.ning.http.client.Request; +import com.ning.http.client.RequestBuilder; +import com.ning.http.client.Response; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.lang.reflect.Method; +import java.net.ConnectException; +import java.net.URI; +import java.nio.channels.ClosedChannelException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; + +class Netty38ClientTest extends AbstractHttpClientTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + static final String USER_AGENT = "test-user-agent"; + + AsyncHttpClient client; + + @BeforeEach + void setUp() throws Exception { + AsyncHttpClientConfig.Builder builder = + new AsyncHttpClientConfig.Builder().setUserAgent(USER_AGENT); + + Method setConnectTimeout; + try { + setConnectTimeout = + AsyncHttpClientConfig.Builder.class.getMethod("setConnectTimeout", int.class); + } catch (NoSuchMethodException e) { + setConnectTimeout = + AsyncHttpClientConfig.Builder.class.getMethod("setRequestTimeoutInMs", int.class); + } + setConnectTimeout.invoke(builder, (int) CONNECTION_TIMEOUT.toMillis()); + + Method setFollowRedirect; + try { + setFollowRedirect = + AsyncHttpClientConfig.Builder.class.getMethod("setFollowRedirect", boolean.class); + } catch (NoSuchMethodException e) { + setFollowRedirect = + AsyncHttpClientConfig.Builder.class.getMethod("setFollowRedirects", boolean.class); + } + setFollowRedirect.invoke(builder, true); + + Method setMaxRedirects; + try { + setMaxRedirects = AsyncHttpClientConfig.Builder.class.getMethod("setMaxRedirects", int.class); + } catch (NoSuchMethodException e) { + setMaxRedirects = + AsyncHttpClientConfig.Builder.class.getMethod("setMaximumNumberOfRedirects", int.class); + } + setMaxRedirects.invoke(builder, 3); + + Method setAllowPoolingConnections; + try { + setAllowPoolingConnections = + AsyncHttpClientConfig.Builder.class.getMethod( + "setAllowPoolingConnections", boolean.class); + } catch (NoSuchMethodException e) { + setAllowPoolingConnections = + AsyncHttpClientConfig.Builder.class.getMethod("setAllowPoolingConnection", boolean.class); + } + setAllowPoolingConnections.invoke(builder, false); + + client = new AsyncHttpClient(builder.build()); + } + + @Override + public Request buildRequest(String method, URI uri, Map headers) { + RequestBuilder requestBuilder = new RequestBuilder(method).setUrl(uri.toString()); + headers.forEach(requestBuilder::addHeader); + return requestBuilder.build(); + } + + @Override + public int sendRequest(Request request, String method, URI uri, Map headers) + throws Exception { + return client.executeRequest(request).get().getStatusCode(); + } + + @Override + public void sendRequestWithCallback( + Request request, + String method, + URI uri, + Map headers, + HttpClientResult httpClientResult) + throws Exception { + + // TODO: context is not automatically propagated into callbacks + Context context = Context.current(); + // TODO(anuraaga): Do we also need to test ListenableFuture callback? + client.executeRequest( + request, + new AsyncCompletionHandler() { + @Override + public Void onCompleted(Response response) { + try (Scope ignored = context.makeCurrent()) { + httpClientResult.complete(response.getStatusCode()); + } + return null; + } + + @Override + public void onThrowable(Throwable throwable) { + try (Scope ignored = context.makeCurrent()) { + httpClientResult.complete(throwable); + } + } + }); + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.disableTestRedirects(); + optionsBuilder.disableTestHttps(); + + optionsBuilder.setUserAgent(USER_AGENT); + + optionsBuilder.setExpectedClientSpanNameMapper( + (uri, method) -> { + // unopened port or non routable address + if ("http://localhost:61/".equals(uri.toString()) + || "http://192.0.2.1/".equals(uri.toString())) { + return "CONNECT"; + } + return HttpClientTestOptions.DEFAULT_EXPECTED_CLIENT_SPAN_NAME_MAPPER.apply(uri, method); + }); + + optionsBuilder.setClientSpanErrorMapper( + (uri, error) -> { + if ("http://localhost:61/".equals(uri.toString())) { // unopened port + error = + error.getCause() != null + ? error.getCause() + : new ConnectException("Connection refused: localhost/127.0.0.1:61"); + } else if ("http://192.0.2.1/".equals(uri.toString())) { // non routable address + error = error.getCause() != null ? error.getCause() : new ClosedChannelException(); + } + return error; + }); + + optionsBuilder.setHttpAttributes( + uri -> { + // unopened port or non routable address + if ("http://localhost:61/".equals(uri.toString()) + || "http://192.0.2.1/".equals(uri.toString())) { + return emptySet(); + } + Set> attributes = + new HashSet<>(HttpClientTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(SemanticAttributes.NET_PEER_NAME); + attributes.remove(SemanticAttributes.NET_PEER_PORT); + return attributes; + }); + } +} diff --git a/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/Netty38ServerTest.java b/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/Netty38ServerTest.java new file mode 100644 index 000000000000..5e6a43d711dc --- /dev/null +++ b/instrumentation/netty/netty-3.8/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/netty/v3_8/server/Netty38ServerTest.java @@ -0,0 +1,211 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.netty.v3_8.server; + +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.NOT_FOUND; +import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.forPath; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LOCATION; +import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpServerTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpServerTestOptions; +import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.HashSet; +import java.util.Set; +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.DefaultChannelPipeline; +import org.jboss.netty.channel.DownstreamMessageEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.FailedChannelFuture; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelHandler; +import org.jboss.netty.channel.SucceededChannelFuture; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; +import org.jboss.netty.handler.codec.http.DefaultHttpResponse; +import org.jboss.netty.handler.codec.http.HttpRequest; +import org.jboss.netty.handler.codec.http.HttpResponse; +import org.jboss.netty.handler.codec.http.HttpResponseStatus; +import org.jboss.netty.handler.codec.http.HttpServerCodec; +import org.jboss.netty.handler.codec.http.QueryStringDecoder; +import org.jboss.netty.handler.logging.LoggingHandler; +import org.jboss.netty.logging.InternalLogLevel; +import org.jboss.netty.logging.InternalLoggerFactory; +import org.jboss.netty.logging.Slf4JLoggerFactory; +import org.jboss.netty.util.CharsetUtil; +import org.junit.jupiter.api.extension.RegisterExtension; + +class Netty38ServerTest extends AbstractHttpServerTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpServerInstrumentationExtension.forAgent(); + + static final LoggingHandler LOGGING_HANDLER; + + static { + InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory()); + LOGGING_HANDLER = + new LoggingHandler(Netty38ServerTest.class.getName(), InternalLogLevel.DEBUG, true); + } + + @Override + protected ServerBootstrap setupServer() { + ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory()); + bootstrap.setParentHandler(LOGGING_HANDLER); + bootstrap.setPipelineFactory(Netty38ServerTest::channelPipeline); + + InetSocketAddress address = new InetSocketAddress(port); + bootstrap.bind(address); + return bootstrap; + } + + @Override + protected void stopServer(ServerBootstrap server) { + server.shutdown(); + } + + @Override + protected void configure(HttpServerTestOptions options) { + options.setHttpAttributes( + serverEndpoint -> { + Set> attributes = + new HashSet<>(HttpServerTestOptions.DEFAULT_HTTP_ATTRIBUTES); + attributes.remove(SemanticAttributes.HTTP_ROUTE); + return attributes; + }); + + options.setExpectedException(new IllegalArgumentException(ServerEndpoint.EXCEPTION.getBody())); + } + + private static ChannelPipeline channelPipeline() { + ChannelPipeline channelPipeline = new DefaultChannelPipeline(); + channelPipeline.addFirst("logger", LOGGING_HANDLER); + + channelPipeline.addLast("http-codec", new HttpServerCodec()); + channelPipeline.addLast( + "controller", + new SimpleChannelHandler() { + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) { + if (msg.getMessage() instanceof HttpRequest) { + HttpRequest request = (HttpRequest) msg.getMessage(); + URI uri = URI.create(request.getUri()); + ServerEndpoint endpoint = forPath(uri.getPath()); + ctx.sendDownstream( + controller( + endpoint, + () -> { + HttpResponse response; + ChannelBuffer responseContent; + switch (endpoint) { + case SUCCESS: + case ERROR: + responseContent = + ChannelBuffers.copiedBuffer(endpoint.getBody(), CharsetUtil.UTF_8); + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response.setContent(responseContent); + break; + case INDEXED_CHILD: + responseContent = ChannelBuffers.EMPTY_BUFFER; + endpoint.collectSpanAttributes( + name -> + new QueryStringDecoder(uri) + .getParameters().get(name).stream() + .findFirst() + .orElse(null)); + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response.setContent(responseContent); + break; + case QUERY_PARAM: + responseContent = + ChannelBuffers.copiedBuffer(uri.getQuery(), CharsetUtil.UTF_8); + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response.setContent(responseContent); + break; + case REDIRECT: + responseContent = ChannelBuffers.EMPTY_BUFFER; + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response.setContent(responseContent); + response.headers().set(LOCATION, endpoint.getBody()); + break; + case CAPTURE_HEADERS: + responseContent = + ChannelBuffers.copiedBuffer(endpoint.getBody(), CharsetUtil.UTF_8); + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response + .headers() + .set("X-Test-Response", request.headers().get("X-Test-Request")); + response.setContent(responseContent); + break; + case EXCEPTION: + throw new IllegalArgumentException(endpoint.getBody()); + default: + responseContent = + ChannelBuffers.copiedBuffer(NOT_FOUND.getBody(), CharsetUtil.UTF_8); + response = + new DefaultHttpResponse( + HTTP_1_1, HttpResponseStatus.valueOf(endpoint.getStatus())); + response.setContent(responseContent); + break; + } + response.headers().set(CONTENT_TYPE, "text/plain"); + if (responseContent != null) { + response.headers().set(CONTENT_LENGTH, responseContent.readableBytes()); + } + return new DownstreamMessageEvent( + ctx.getChannel(), + new SucceededChannelFuture(ctx.getChannel()), + response, + ctx.getChannel().getRemoteAddress()); + })); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) { + String message = + ex.getCause() == null + ? "" + : ex.getCause().getMessage() == null ? "" : ex.getCause().getMessage(); + ChannelBuffer buffer = ChannelBuffers.copiedBuffer(message, CharsetUtil.UTF_8); + HttpResponse response = + new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR); + response.setContent(buffer); + response.headers().set(CONTENT_TYPE, "text/plain"); + response.headers().set(CONTENT_LENGTH, buffer.readableBytes()); + ctx.sendDownstream( + new DownstreamMessageEvent( + ctx.getChannel(), + new FailedChannelFuture(ctx.getChannel(), ex.getCause()), + response, + ctx.getChannel().getRemoteAddress())); + } + }); + + return channelPipeline; + } +}