Integrate trace api with AdvancedHttpClient

This commit is contained in:
Sebastian Sdorra
2020-10-26 16:54:05 +01:00
parent 09d85f6dbb
commit eb7a7837d7
4 changed files with 126 additions and 49 deletions

View File

@@ -21,30 +21,28 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.net.ahc; package sonia.scm.net.ahc;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import org.apache.shiro.codec.Base64; import org.apache.shiro.codec.Base64;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
//~--- JDK imports ------------------------------------------------------------
/** /**
* Base class for http requests. * Base class for http requests.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @param <T> request implementation * @param <T> request implementation
* *
* @since 1.46 * @since 1.46
*/ */
public abstract class BaseHttpRequest<T extends BaseHttpRequest> public abstract class BaseHttpRequest<T extends BaseHttpRequest>
@@ -75,7 +73,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
* *
* @throws IOException * @throws IOException
*/ */
public AdvancedHttpResponse request() throws IOException public AdvancedHttpResponse request() throws IOException
{ {
return client.request(this); return client.request(this);
} }
@@ -102,7 +100,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
String auth = Strings.nullToEmpty(username).concat(":").concat( String auth = Strings.nullToEmpty(username).concat(":").concat(
Strings.nullToEmpty(password)); Strings.nullToEmpty(password));
auth = Base64.encodeToString(auth.getBytes(Charsets.ISO_8859_1)); auth = Base64.encodeToString(auth.getBytes(StandardCharsets.ISO_8859_1));
headers.put("Authorization", "Basic ".concat(auth)); headers.put("Authorization", "Basic ".concat(auth));
return self(); return self();
@@ -129,7 +127,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
* *
* *
* @param disableCertificateValidation true to disable certificate validation * @param disableCertificateValidation true to disable certificate validation
* *
* @return request instance * @return request instance
*/ */
public T disableCertificateValidation(boolean disableCertificateValidation) public T disableCertificateValidation(boolean disableCertificateValidation)
@@ -246,6 +244,19 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
return self(); return self();
} }
/**
* Sets the kind of span for tracing api.
*
* @param spanKind kind of span
* @return request instance
*
* @since 2.9.0
*/
public T spanKind(String spanKind) {
this.spanKind = spanKind;
return self();
}
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**
@@ -281,6 +292,17 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
return url; return url;
} }
/**
* Returns the kind of span which is used for the trace api.
*
* @return kind of span
*
* @since 2.9.0
*/
public String getSpanKind() {
return spanKind;
}
/** /**
* Returns true if the request decodes gzip compression. * Returns true if the request decodes gzip compression.
* *
@@ -317,7 +339,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
/** /**
* Returns true if the proxy settings are ignored. * Returns true if the proxy settings are ignored.
* *
* *
* @return true if the proxy settings are ignored * @return true if the proxy settings are ignored
*/ */
public boolean isIgnoreProxySettings() public boolean isIgnoreProxySettings()
@@ -341,7 +363,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
} }
/** /**
* Returns string representation of the given object or {@code null}, if the * Returns string representation of the given object or {@code null}, if the
* object is {@code null}. * object is {@code null}.
* *
* *
@@ -398,4 +420,7 @@ public abstract class BaseHttpRequest<T extends BaseHttpRequest>
/** url of request */ /** url of request */
private String url; private String url;
/** kind of span for trace api */
private String spanKind = "http-request";
} }

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.net.ahc; package sonia.scm.net.ahc;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -38,8 +38,11 @@ import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.Proxies; import sonia.scm.net.Proxies;
import sonia.scm.net.TrustAllHostnameVerifier; import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.net.TrustAllTrustManager; import sonia.scm.net.TrustAllTrustManager;
import sonia.scm.trace.Span;
import sonia.scm.trace.Tracer;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
import javax.annotation.Nonnull;
import javax.inject.Provider; import javax.inject.Provider;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@@ -99,9 +102,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
*/ */
@Inject @Inject
public DefaultAdvancedHttpClient(ScmConfiguration configuration, public DefaultAdvancedHttpClient(ScmConfiguration configuration,
Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider) Tracer tracer, Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider)
{ {
this.configuration = configuration; this.configuration = configuration;
this.tracer = tracer;
this.contentTransformers = contentTransformers; this.contentTransformers = contentTransformers;
this.sslContextProvider = sslContextProvider; this.sslContextProvider = sslContextProvider;
} }
@@ -185,45 +189,48 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
* @throws IOException * @throws IOException
*/ */
@Override @Override
protected AdvancedHttpResponse request(BaseHttpRequest<?> request) protected AdvancedHttpResponse request(BaseHttpRequest<?> request) throws IOException {
throws IOException try (Span span = tracer.span(request.getSpanKind())) {
{ span.label("url", request.getUrl());
HttpURLConnection connection = openConnection(request, span.label("method", request.getMethod());
new URL(request.getUrl())); DefaultAdvancedHttpResponse response = doRequest(request);
span.label("status", response.getStatus());
if (!response.isSuccessful()) {
span.failed();
}
return response;
}
}
@Nonnull
private DefaultAdvancedHttpResponse doRequest(BaseHttpRequest<?> request) throws IOException {
HttpURLConnection connection = openConnection(request, new URL(request.getUrl()));
applyBaseSettings(request, connection); applyBaseSettings(request, connection);
if (connection instanceof HttpsURLConnection) if (connection instanceof HttpsURLConnection) {
{
applySSLSettings(request, (HttpsURLConnection) connection); applySSLSettings(request, (HttpsURLConnection) connection);
} }
Content content = null; Content content = null;
if (request instanceof AdvancedHttpRequestWithBody) if (request instanceof AdvancedHttpRequestWithBody) {
{
AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request; AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request;
content = ahrwb.getContent(); content = ahrwb.getContent();
if (content != null) if (content != null) {
{
content.prepare(ahrwb); content.prepare(ahrwb);
} } else {
else
{
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
} }
} } else {
else
{
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
} }
applyHeaders(request, connection); applyHeaders(request, connection);
if (content != null) if (content != null) {
{
applyContent(connection, content); applyContent(connection, content);
} }
@@ -309,8 +316,8 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
{ {
logger.error("could not disable certificate validation", ex); logger.error("could not disable certificate validation", ex);
} }
} }
else else
{ {
logger.trace("set ssl socker factory from provider"); logger.trace("set ssl socker factory from provider");
connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory()); connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory());
@@ -380,7 +387,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient
/** set of content transformers */ /** set of content transformers */
private final Set<ContentTransformer> contentTransformers; private final Set<ContentTransformer> contentTransformers;
/** ssl context provider */ /** ssl context provider */
private final Provider<SSLContext> sslContextProvider; private final Provider<SSLContext> sslContextProvider;
/** tracer used for request tracing */
private Tracer tracer;
} }

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.net.ahc; package sonia.scm.net.ahc;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -30,11 +30,14 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.net.TrustAllHostnameVerifier; import sonia.scm.net.TrustAllHostnameVerifier;
import sonia.scm.trace.Span;
import sonia.scm.trace.Tracer;
import sonia.scm.util.HttpUtil; import sonia.scm.util.HttpUtil;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -82,12 +85,12 @@ public class DefaultAdvancedHttpClientTest
DefaultAdvancedHttpClient.TIMEOUT_CONNECTION); DefaultAdvancedHttpClient.TIMEOUT_CONNECTION);
verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0"); verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0");
} }
@Test(expected = ContentTransformerNotFoundException.class) @Test(expected = ContentTransformerNotFoundException.class)
public void testContentTransformerNotFound(){ public void testContentTransformerNotFound(){
client.createTransformer(String.class, "text/plain"); client.createTransformer(String.class, "text/plain");
} }
@Test @Test
public void testContentTransformer(){ public void testContentTransformer(){
ContentTransformer transformer = mock(ContentTransformer.class); ContentTransformer transformer = mock(ContentTransformer.class);
@@ -265,6 +268,32 @@ public class DefaultAdvancedHttpClientTest
"Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0"); "Basic dHJpY2lhOnRyaWNpYXMgc2VjcmV0");
} }
@Test
public void shouldCreateTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(200);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").spanKind("spaceships").request();
verify(tracer).span("spaceships");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
verify(span).label("status", 200);
verify(span, never()).failed();
verify(span).close();
}
@Test
public void shouldCreateFailedTracingSpan() throws IOException {
when(connection.getResponseCode()).thenReturn(500);
new AdvancedHttpRequest(client, HttpMethod.GET, "https://www.scm-manager.org").request();
verify(tracer).span("http-request");
verify(span).label("url", "https://www.scm-manager.org");
verify(span).label("method", "GET");
verify(span).label("status", 500);
verify(span).failed();
verify(span).close();
}
//~--- set methods ---------------------------------------------------------- //~--- set methods ----------------------------------------------------------
/** /**
@@ -277,6 +306,7 @@ public class DefaultAdvancedHttpClientTest
configuration = new ScmConfiguration(); configuration = new ScmConfiguration();
transformers = new HashSet<ContentTransformer>(); transformers = new HashSet<ContentTransformer>();
client = new TestingAdvacedHttpClient(configuration, transformers); client = new TestingAdvacedHttpClient(configuration, transformers);
when(tracer.span(anyString())).thenReturn(span);
} }
//~--- inner classes -------------------------------------------------------- //~--- inner classes --------------------------------------------------------
@@ -298,10 +328,9 @@ public class DefaultAdvancedHttpClientTest
* @param configuration * @param configuration
* @param transformers * @param transformers
*/ */
public TestingAdvacedHttpClient(ScmConfiguration configuration, public TestingAdvacedHttpClient(ScmConfiguration configuration, Set<ContentTransformer> transformers)
Set<ContentTransformer> transformers)
{ {
super(configuration, transformers, new SSLContextProvider()); super(configuration, tracer, transformers, new SSLContextProvider());
} }
//~--- methods ------------------------------------------------------------ //~--- methods ------------------------------------------------------------
@@ -364,4 +393,10 @@ public class DefaultAdvancedHttpClientTest
/** Field description */ /** Field description */
private Set<ContentTransformer> transformers; private Set<ContentTransformer> transformers;
@Mock
private Tracer tracer;
@Mock
private Span span;
} }

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. * SOFTWARE.
*/ */
package sonia.scm.net.ahc; package sonia.scm.net.ahc;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
@@ -33,9 +33,11 @@ import com.google.common.collect.Multimap;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
@@ -56,6 +58,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import sonia.scm.net.SSLContextProvider; import sonia.scm.net.SSLContextProvider;
import sonia.scm.trace.Tracer;
/** /**
* *
@@ -65,6 +68,13 @@ import sonia.scm.net.SSLContextProvider;
public class DefaultAdvancedHttpResponseTest public class DefaultAdvancedHttpResponseTest
{ {
private DefaultAdvancedHttpClient client;
@Before
public void setUpClient() {
client = new DefaultAdvancedHttpClient(new ScmConfiguration(), tracer, new HashSet<>(), new SSLContextProvider());
}
/** /**
* Method description * Method description
* *
@@ -130,13 +140,10 @@ public class DefaultAdvancedHttpResponseTest
assertTrue(headers.get("Test-2").isEmpty()); assertTrue(headers.get("Test-2").isEmpty());
} }
//~--- fields ---------------------------------------------------------------
/** Field description */
private final DefaultAdvancedHttpClient client =
new DefaultAdvancedHttpClient(new ScmConfiguration(), new HashSet<>(), new SSLContextProvider());
/** Field description */ /** Field description */
@Mock @Mock
private HttpURLConnection connection; private HttpURLConnection connection;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Tracer tracer;
} }