diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java index d30ca4ca6b..bab0b4e0a8 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/BaseHttpRequest.java @@ -21,30 +21,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; - import org.apache.shiro.codec.Base64; - import sonia.scm.util.HttpUtil; -//~--- JDK imports ------------------------------------------------------------ - import java.io.IOException; +import java.nio.charset.StandardCharsets; + +//~--- JDK imports ------------------------------------------------------------ /** * Base class for http requests. * * @author Sebastian Sdorra * @param request implementation - * + * * @since 1.46 */ public abstract class BaseHttpRequest @@ -75,7 +73,7 @@ public abstract class BaseHttpRequest * * @throws IOException */ - public AdvancedHttpResponse request() throws IOException + public AdvancedHttpResponse request() throws IOException { return client.request(this); } @@ -102,7 +100,7 @@ public abstract class BaseHttpRequest String auth = Strings.nullToEmpty(username).concat(":").concat( 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)); return self(); @@ -129,7 +127,7 @@ public abstract class BaseHttpRequest * * * @param disableCertificateValidation true to disable certificate validation - * + * * @return request instance */ public T disableCertificateValidation(boolean disableCertificateValidation) @@ -246,6 +244,19 @@ public abstract class BaseHttpRequest 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 ---------------------------------------------------------- /** @@ -281,6 +292,17 @@ public abstract class BaseHttpRequest 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. * @@ -317,7 +339,7 @@ public abstract class BaseHttpRequest /** * Returns true if the proxy settings are ignored. * - * + * * @return true if the proxy settings are ignored */ public boolean isIgnoreProxySettings() @@ -341,7 +363,7 @@ public abstract class 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}. * * @@ -398,4 +420,7 @@ public abstract class BaseHttpRequest /** url of request */ private String url; + + /** kind of span for trace api */ + private String spanKind = "http-request"; } diff --git a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java index 610ec39533..62db41ad38 100644 --- a/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java +++ b/scm-webapp/src/main/java/sonia/scm/net/ahc/DefaultAdvancedHttpClient.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- @@ -38,8 +38,11 @@ import sonia.scm.config.ScmConfiguration; import sonia.scm.net.Proxies; import sonia.scm.net.TrustAllHostnameVerifier; import sonia.scm.net.TrustAllTrustManager; +import sonia.scm.trace.Span; +import sonia.scm.trace.Tracer; import sonia.scm.util.HttpUtil; +import javax.annotation.Nonnull; import javax.inject.Provider; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -99,9 +102,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient */ @Inject public DefaultAdvancedHttpClient(ScmConfiguration configuration, - Set contentTransformers, Provider sslContextProvider) + Tracer tracer, Set contentTransformers, Provider sslContextProvider) { this.configuration = configuration; + this.tracer = tracer; this.contentTransformers = contentTransformers; this.sslContextProvider = sslContextProvider; } @@ -185,45 +189,48 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient * @throws IOException */ @Override - protected AdvancedHttpResponse request(BaseHttpRequest request) - throws IOException - { - HttpURLConnection connection = openConnection(request, - new URL(request.getUrl())); + protected AdvancedHttpResponse request(BaseHttpRequest request) throws IOException { + try (Span span = tracer.span(request.getSpanKind())) { + span.label("url", request.getUrl()); + span.label("method", request.getMethod()); + 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); - if (connection instanceof HttpsURLConnection) - { + if (connection instanceof HttpsURLConnection) { applySSLSettings(request, (HttpsURLConnection) connection); } Content content = null; - if (request instanceof AdvancedHttpRequestWithBody) - { + if (request instanceof AdvancedHttpRequestWithBody) { AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request; content = ahrwb.getContent(); - if (content != null) - { + if (content != null) { content.prepare(ahrwb); - } - else - { + } else { request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); } - } - else - { + } else { request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0"); } applyHeaders(request, connection); - if (content != null) - { + if (content != null) { applyContent(connection, content); } @@ -309,8 +316,8 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient { logger.error("could not disable certificate validation", ex); } - } - else + } + else { logger.trace("set ssl socker factory from provider"); connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory()); @@ -380,7 +387,10 @@ public class DefaultAdvancedHttpClient extends AdvancedHttpClient /** set of content transformers */ private final Set contentTransformers; - + /** ssl context provider */ private final Provider sslContextProvider; + + /** tracer used for request tracing */ + private Tracer tracer; } diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java index 7da5ddfb8a..4ca513b4e5 100644 --- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpClientTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- @@ -30,11 +30,14 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.config.ScmConfiguration; import sonia.scm.net.TrustAllHostnameVerifier; +import sonia.scm.trace.Span; +import sonia.scm.trace.Tracer; import sonia.scm.util.HttpUtil; import static org.junit.Assert.*; @@ -82,12 +85,12 @@ public class DefaultAdvancedHttpClientTest DefaultAdvancedHttpClient.TIMEOUT_CONNECTION); verify(connection).addRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, "0"); } - + @Test(expected = ContentTransformerNotFoundException.class) public void testContentTransformerNotFound(){ client.createTransformer(String.class, "text/plain"); } - + @Test public void testContentTransformer(){ ContentTransformer transformer = mock(ContentTransformer.class); @@ -265,6 +268,32 @@ public class DefaultAdvancedHttpClientTest "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 ---------------------------------------------------------- /** @@ -277,6 +306,7 @@ public class DefaultAdvancedHttpClientTest configuration = new ScmConfiguration(); transformers = new HashSet(); client = new TestingAdvacedHttpClient(configuration, transformers); + when(tracer.span(anyString())).thenReturn(span); } //~--- inner classes -------------------------------------------------------- @@ -298,10 +328,9 @@ public class DefaultAdvancedHttpClientTest * @param configuration * @param transformers */ - public TestingAdvacedHttpClient(ScmConfiguration configuration, - Set transformers) + public TestingAdvacedHttpClient(ScmConfiguration configuration, Set transformers) { - super(configuration, transformers, new SSLContextProvider()); + super(configuration, tracer, transformers, new SSLContextProvider()); } //~--- methods ------------------------------------------------------------ @@ -364,4 +393,10 @@ public class DefaultAdvancedHttpClientTest /** Field description */ private Set transformers; + + @Mock + private Tracer tracer; + + @Mock + private Span span; } diff --git a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java index 0496b318e6..e358820a2e 100644 --- a/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java +++ b/scm-webapp/src/test/java/sonia/scm/net/ahc/DefaultAdvancedHttpResponseTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.net.ahc; //~--- non-JDK imports -------------------------------------------------------- @@ -33,9 +33,11 @@ import com.google.common.collect.Multimap; import com.google.common.io.ByteSource; import org.hamcrest.Matchers; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -56,6 +58,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import sonia.scm.net.SSLContextProvider; +import sonia.scm.trace.Tracer; /** * @@ -65,6 +68,13 @@ import sonia.scm.net.SSLContextProvider; public class DefaultAdvancedHttpResponseTest { + private DefaultAdvancedHttpClient client; + + @Before + public void setUpClient() { + client = new DefaultAdvancedHttpClient(new ScmConfiguration(), tracer, new HashSet<>(), new SSLContextProvider()); + } + /** * Method description * @@ -130,13 +140,10 @@ public class DefaultAdvancedHttpResponseTest assertTrue(headers.get("Test-2").isEmpty()); } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final DefaultAdvancedHttpClient client = - new DefaultAdvancedHttpClient(new ScmConfiguration(), new HashSet<>(), new SSLContextProvider()); - /** Field description */ @Mock private HttpURLConnection connection; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Tracer tracer; }