diff --git a/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtils.java b/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtils.java index f2346d32..085e1303 100644 --- a/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtils.java +++ b/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtils.java @@ -49,7 +49,6 @@ import com.google.common.net.InternetDomainName; import java.net.HttpCookie; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; @@ -119,8 +118,6 @@ public class HttpSemanticConventionUtils { private static final String SLASH = "/"; private static final String HTTP_REQUEST_FORWARDED_ATTRIBUTE = HTTP_REQUEST_FORWARDED.getValue(); - private static final String HTTP_REQUEST_ORIGIN = - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(); private static final String HTTP_RESPONSE_HEADER_PREFIX = RawSpanConstants.getValue(HTTP_RESPONSE_HEADER) + DOT; public static final String RESPONSE_COOKIE_HEADER_PREFIX = @@ -186,6 +183,7 @@ public class HttpSemanticConventionUtils { RawSpanConstants.getValue(OT_SPAN_TAG_HTTP_STATUS_CODE), RawSpanConstants.getValue(HTTP_RESPONSE_STATUS_CODE), OTelHttpSemanticConventions.HTTP_STATUS_CODE.getValue()); + public static final String HTTPS = "https"; /** * @return attribute keys for http method @@ -553,6 +551,24 @@ public static Optional getHttpMethod(Event event) { return Optional.empty(); } + /** + * Returns the HTTP scheme associated with the given {@link Event}. The scheme is determined as + * follows: + *
    + *
  1. If the event has an attribute like {@code http.url} that contains a full URL, the scheme + * from that URL is returned if it is https. + *
  2. If the scheme from the full URL is not 'https', then other attributes like + * {@code x-forwarded-proto}, {@code forwarded}, {@code http.scheme} are checked. If any of + * these has the value 'https', then that is returned. + *
  3. If none of the above steps yield a scheme of 'https', then the scheme from the full URL + * (which should be 'http') is returned. + *
  4. If the event does not have a full URL attribute, then the other attributes are checked for + * the scheme. + *
+ * + * @param event the event to extract the scheme from + * @return an {@link Optional} containing the scheme if it could be determined, empty otherwise + */ public static Optional getHttpScheme(Event event) { Optional scheme = Optional.empty(); @@ -573,10 +589,11 @@ public static Optional getHttpScheme(Event event) { if (scheme.get().equalsIgnoreCase("https")) { return scheme; } - // 3. else check if there are other attributes that like 'origin', 'x-forwarded-proto' etc + // 3. else check if there are other attributes that like 'x-forwarded-proto', forwarded, + // http.scheme etc. // that have the actual scheme - // this is for scenarios where a LB fronts the service and terminates SSL. - // If this scheme is 'https' then return this instead + // this is for scenarios where a LB fronts the service and terminates SSL. + // If this scheme is 'https' then return this instead else { schemeFromOtherRawAttributes = getHttpSchemeFromRawAttributes(event); if (schemeFromOtherRawAttributes.isPresent() @@ -602,46 +619,37 @@ private static Optional getHttpSchemeFromRawAttributes(Event event) { private static Optional getHttpSchemeFromRawAttributes( Map attributeValueMap) { + String scheme = null; // dealing with the Forwarded header separately as it may have // more info than just the protocol + // 1. Check 'forwarded' header AttributeValue httpRequestForwardedAttributeValue = attributeValueMap.get(HTTP_REQUEST_FORWARDED_ATTRIBUTE); if (httpRequestForwardedAttributeValue != null - && !StringUtils.isEmpty(httpRequestForwardedAttributeValue.getValue())) { - String schemeValue = httpRequestForwardedAttributeValue.getValue(); - Optional optionalExtractedProtoValue = getProtocolFromForwarded(schemeValue); + && StringUtils.isNotEmpty(httpRequestForwardedAttributeValue.getValue())) { + Optional optionalExtractedProtoValue = + getProtocolFromForwarded(httpRequestForwardedAttributeValue.getValue()); if (optionalExtractedProtoValue.isPresent()) { - return optionalExtractedProtoValue; + scheme = optionalExtractedProtoValue.get(); + if (HTTPS.equalsIgnoreCase(scheme)) { + return Optional.of(HTTPS); + } } } - AttributeValue httpRequestOriginValue = attributeValueMap.get(HTTP_REQUEST_ORIGIN); - if (httpRequestOriginValue != null && !StringUtils.isEmpty(httpRequestOriginValue.getValue())) { - String origin = httpRequestOriginValue.getValue(); - /** Syntax: Origin: null Origin: :// Origin: ://: */ - if (StringUtils.isNotEmpty(origin)) { - try { - String scheme = new URI(origin).getScheme(); - // handle the case where the value of origin is the "null" string - if (StringUtils.isNotEmpty(scheme)) { - return Optional.of(scheme); - } - } catch (Exception e) { - LOGGER.debug( - "On extracting scheme, received an invalid origin header: {}, {}", - origin, - e.getMessage()); + // 2. Check other scheme-related attributes like x-forwarded-proto, http.scheme, etc. + for (String schemeAttr : SCHEME_ATTRIBUTES) { + AttributeValue attrValue = attributeValueMap.get(schemeAttr); + if (attrValue != null && StringUtils.isNotEmpty(attrValue.getValue())) { + scheme = attrValue.getValue(); + if (HTTPS.equalsIgnoreCase(scheme)) { + return Optional.of(HTTPS); } } } - for (String scheme : SCHEME_ATTRIBUTES) { - if (attributeValueMap.get(scheme) != null - && !StringUtils.isEmpty(attributeValueMap.get(scheme).getValue())) { - return Optional.of(attributeValueMap.get(scheme).getValue()); - } - } - return Optional.empty(); + // 3. Return scheme if found + return Optional.ofNullable(scheme); } private static Optional getProtocolFromForwarded(String value) { diff --git a/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtilsTest.java b/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtilsTest.java index dc5facd6..3b39f066 100644 --- a/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtilsTest.java +++ b/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/http/HttpSemanticConventionUtilsTest.java @@ -368,8 +368,8 @@ public void testGetHttpScheme() { event = createMockEventWithAttribute( HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(), - "by= random ; proto = https; for=modnar"); - assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); + "by= random ; proto=http; for=modnar"); + assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event)); event = createMockEventWithAttribute( @@ -394,28 +394,31 @@ public void testGetHttpScheme() { when(e.getEnrichedAttributes()).thenReturn(null); assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(e)); - // when only origin header exists expect whatever is the scheme of the origin header - // when origin header has 'https' - event = - createMockEventWithAttribute( - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), "https://abc.xyz"); + // when http url and X-forwarded proto header exists with scheme https then expect https + event = mock(Event.class); + when(event.getAttributes()) + .thenReturn( + Attributes.newBuilder() + .setAttributeMap( + Map.of( + HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(), + AttributeValue.newBuilder().setValue("https").build(), + RawSpanConstants.getValue(Http.HTTP_URL), + AttributeValue.newBuilder() + .setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2") + .build())) + .build()); assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); - // when origin header has 'http' - event = - createMockEventWithAttribute( - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), "http://abc.xyz"); - assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event)); - - // when http url and origin header exists with scheme https then expect https + // when http url and forwarded header exists with scheme https then expect https event = mock(Event.class); when(event.getAttributes()) .thenReturn( Attributes.newBuilder() .setAttributeMap( Map.of( - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), - AttributeValue.newBuilder().setValue("https://abc.xyz.ai").build(), + HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(), + AttributeValue.newBuilder().setValue("proto=https").build(), RawSpanConstants.getValue(Http.HTTP_URL), AttributeValue.newBuilder() .setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2") @@ -423,15 +426,13 @@ public void testGetHttpScheme() { .build()); assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); - // when http url and origin header exists with scheme http then expect http + // when http url exists with scheme http then expect http event = mock(Event.class); when(event.getAttributes()) .thenReturn( Attributes.newBuilder() .setAttributeMap( Map.of( - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), - AttributeValue.newBuilder().setValue("http://abc.xyz.ai").build(), RawSpanConstants.getValue(Http.HTTP_URL), AttributeValue.newBuilder() .setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2") @@ -439,21 +440,60 @@ public void testGetHttpScheme() { .build()); assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event)); - // when http url and origin header is null string then expect scheme of the url + // when http url exists with scheme https but other attributes http only. event = mock(Event.class); when(event.getAttributes()) .thenReturn( Attributes.newBuilder() .setAttributeMap( Map.of( - HttpSemanticConventions.HTTP_REQUEST_ORIGIN.getValue(), - AttributeValue.newBuilder().setValue("null").build(), RawSpanConstants.getValue(Http.HTTP_URL), AttributeValue.newBuilder() .setValue("http://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2") + .build(), + HTTP_SCHEME.getValue(), + AttributeValue.newBuilder().setValue("https").build(), + HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(), + AttributeValue.newBuilder().setValue("http").build(), + HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(), + AttributeValue.newBuilder() + .setValue("by= random ;proto = ; for=modnar") .build())) .build()); - assertEquals(Optional.of("http"), HttpSemanticConventionUtils.getHttpScheme(event)); + assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); + + // when http url not present and forwarded header is https and others are http then scheme + // should be https. + event = mock(Event.class); + when(event.getAttributes()) + .thenReturn( + Attributes.newBuilder() + .setAttributeMap( + Map.of( + HTTP_SCHEME.getValue(), + AttributeValue.newBuilder().setValue("http").build(), + HttpSemanticConventions.HTTP_REQUEST_X_FORWARDED_PROTO.getValue(), + AttributeValue.newBuilder().setValue("http").build(), + HttpSemanticConventions.HTTP_REQUEST_FORWARDED.getValue(), + AttributeValue.newBuilder() + .setValue("by= random ;proto = https; for=modnar") + .build())) + .build()); + assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); + + // when http url is present with scheme https + event = mock(Event.class); + when(event.getAttributes()) + .thenReturn( + Attributes.newBuilder() + .setAttributeMap( + Map.of( + RawSpanConstants.getValue(Http.HTTP_URL), + AttributeValue.newBuilder() + .setValue("https://abc.xyz.ai/apis/5673/events?a1=v1&a2=v2") + .build())) + .build()); + assertEquals(Optional.of("https"), HttpSemanticConventionUtils.getHttpScheme(event)); } @Test