diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f271898cde..b93eec7dae 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -33,8 +33,10 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
[float]
===== Bug fixes
+* Fixed NPE in ApacheHttpClientApiAdapter#getHostName - {pull}3479[#3479]
* Fix span stack trace when combined with span compression - {pull}3474[#3474]
+
[[release-notes-1.x]]
=== Java Agent version 1.x
diff --git a/apm-agent-core/pom.xml b/apm-agent-core/pom.xml
index 89c7100310..b1a723fe9c 100644
--- a/apm-agent-core/pom.xml
+++ b/apm-agent-core/pom.xml
@@ -56,7 +56,8 @@
org.jctools
jctools-core
- 4.0.2
+
+ 4.0.1
com.googlecode.concurrentlinkedhashmap
diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java
index fce3555a6d..aa6c60f04d 100644
--- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java
+++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/AbstractApacheHttpClientAdvice.java
@@ -27,6 +27,7 @@
import co.elastic.apm.agent.tracer.dispatch.TextHeaderGetter;
import co.elastic.apm.agent.tracer.dispatch.TextHeaderSetter;
+import javax.annotation.Nullable;
import java.net.URISyntaxException;
public abstract class AbstractApacheHttpClientAdvice {
@@ -36,12 +37,12 @@ public abstract class AbstractApacheHttpClientAdvice {
TextHeaderGetter> Object startSpan(final Tracer tracer,
final ApacheHttpClientApiAdapter adapter,
final WRAPPER request,
- final HTTPHOST httpHost,
+ @Nullable final HTTPHOST httpHost,
final HeaderAccessor headerAccessor) throws URISyntaxException {
ElasticContext> elasticContext = tracer.currentContext();
Span> span = null;
if (elasticContext.getSpan() != null) {
- span = HttpClientHelper.startHttpClientSpan(elasticContext, adapter.getMethod(request), adapter.getUri(request), adapter.getHostName(httpHost));
+ span = HttpClientHelper.startHttpClientSpan(elasticContext, adapter.getMethod(request), adapter.getUri(request), adapter.getHostName(httpHost, request));
if (span != null) {
span.activate();
}
diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java
index 4363cc3e17..a3834035cd 100644
--- a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java
+++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient-common/src/main/java/co/elastic/apm/agent/httpclient/common/ApacheHttpClientApiAdapter.java
@@ -19,6 +19,7 @@
package co.elastic.apm.agent.httpclient.common;
+import javax.annotation.Nullable;
import java.net.URI;
import java.net.URISyntaxException;
@@ -27,7 +28,8 @@ public interface ApacheHttpClientApiAdapter {
private static final ApacheHttpClient5ApiAdapter INSTANCE = new ApacheHttpClient5ApiAdapter();
+ private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClient5ApiAdapter.class);
+
private ApacheHttpClient5ApiAdapter() {
}
@@ -49,8 +56,19 @@ public URI getUri(ClassicHttpRequest request) throws URISyntaxException {
}
@Override
- public CharSequence getHostName(HttpHost httpHost) {
- return httpHost.getHostName();
+ public CharSequence getHostName(@Nullable HttpHost httpHost, ClassicHttpRequest request) {
+ if (httpHost != null) {
+ return httpHost.getHostName();
+ }
+
+ try {
+ return RoutingSupport.determineHost(request)
+ .getHostName();
+ } catch (HttpException e) {
+ logger.error("Exception while determining HostName", e);
+
+ return null;
+ }
}
@Override
diff --git a/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java
new file mode 100644
index 0000000000..ef04c9f59e
--- /dev/null
+++ b/apm-agent-plugins/apm-apache-httpclient/apm-apache-httpclient5-plugin/src/test/java/co/elastic/apm/agent/httpclient/v5/ApacheHttpClientExecuteOpenInstrumentationTest.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License 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 co.elastic.apm.agent.httpclient.v5;
+
+
+import co.elastic.apm.agent.httpclient.AbstractHttpClientInstrumentationTest;
+import org.apache.hc.client5.http.ClientProtocolException;
+import org.apache.hc.client5.http.classic.methods.HttpGet;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.io.IOException;
+
+public class ApacheHttpClientExecuteOpenInstrumentationTest extends AbstractHttpClientInstrumentationTest {
+
+ private static CloseableHttpClient client;
+
+ @BeforeClass
+ public static void setUp() {
+ client = HttpClients.createDefault();
+ }
+
+ @AfterClass
+ public static void close() throws IOException {
+ client.close();
+ }
+
+ /**
+ * RFC 7230: treat presence of userinfo in authority component in request URI as an HTTP protocol violation.
+ *
+ * Uses {@link org.apache.hc.core5.http.message.BasicHttpRequest#setUri} to fill {@link org.apache.hc.core5.net.URIAuthority}
+ *
+ * Assertions on authority in {@link org.apache.hc.client5.http.impl.classic.ProtocolExec#execute}
+ */
+ @Override
+ public boolean isTestHttpCallWithUserInfoEnabled() {
+ return false;
+ }
+
+ @Override
+ protected void performGet(String path) throws Exception {
+ HttpClientResponseHandler responseHandler = response -> {
+ int status = response.getCode();
+ if (status >= 200 && status < 300) {
+ HttpEntity entity = response.getEntity();
+ String res = entity != null ? EntityUtils.toString(entity) : null;
+ return res;
+ } else {
+ throw new ClientProtocolException("Unexpected response status: " + status);
+ }
+ };
+
+ ClassicHttpResponse response = client.executeOpen(null, new HttpGet(path), null);
+ responseHandler.handleResponse(response);
+ }
+}