All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nike.wingtips.apache.httpclient.WingtipsApacheHttpClientInterceptor Maven / Gradle / Ivy

package com.nike.wingtips.apache.httpclient;

import com.nike.internal.util.StringUtils;
import com.nike.wingtips.Span;
import com.nike.wingtips.Tracer;
import com.nike.wingtips.apache.httpclient.tag.ApacheHttpClientTagAdapter;
import com.nike.wingtips.apache.httpclient.util.WingtipsApacheHttpClientUtil;
import com.nike.wingtips.tags.HttpTagAndSpanNamingAdapter;
import com.nike.wingtips.tags.HttpTagAndSpanNamingStrategy;
import com.nike.wingtips.tags.NoOpHttpTagAdapter;
import com.nike.wingtips.tags.NoOpHttpTagStrategy;
import com.nike.wingtips.tags.ZipkinHttpTagStrategy;

import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpProcessor;
import org.jetbrains.annotations.NotNull;

import static com.nike.wingtips.apache.httpclient.util.WingtipsApacheHttpClientUtil.propagateTracingHeaders;

/**
 * (NOTE: {@link WingtipsHttpClientBuilder} is strongly recommended instead of these interceptors if you have control
 * over which {@link HttpClientBuilder} is used to create your {@link HttpClient}s. Reasons for this are described at
 * the bottom of this class javadoc.)
 *
 * 

This class is an implementation of both {@link HttpRequestInterceptor} and {@link HttpResponseInterceptor} for * propagating Wingtips tracing information on a {@link HttpClient} call's request headers, with an option to surround * requests in a subspan. The subspan option defaults to on and is highly recommended since the subspans will * provide you with timing info for your downstream calls separate from any parent span that may be active at the time * this interceptor executes. * *

In order for tracing to be propagated and any subspan to be completed correctly you need to add this as *both* * a request and response interceptor. Forgetting to add this as both a request and response interceptor could leave * your tracing in a broken state. Ideally this is added as the first {@link * HttpClientBuilder#addInterceptorFirst(HttpRequestInterceptor)} and the last {@link * HttpClientBuilder#addInterceptorLast(HttpResponseInterceptor)} so that any subspan surrounds as much of the request * as possible, including other interceptors. You can use the {@link #addTracingInterceptors(HttpClientBuilder)} * helper method to guarantee this interceptor gets added to both the request and response sides, although there's no * way to enforce the ideal request-interceptor-first and response-interceptor-last scenario with a helper method * when you have any other interceptors (you would have to add it as a request and response interceptor yourself at * the appropriate times). * *

If the subspan option is enabled but there's no current span on the current thread when this interceptor executes, * then a new root span (new trace) will be created rather than a subspan. In either case the newly created span will * have a {@link Span#getSpanPurpose()} of {@link Span.SpanPurpose#CLIENT} since this interceptor is for a client call. * The {@link Span#getSpanName()} for the newly created span will be generated by {@link * #getSubspanSpanName(HttpRequest, HttpTagAndSpanNamingStrategy, HttpTagAndSpanNamingAdapter)}. Instantiate this * class with a custom {@link HttpTagAndSpanNamingStrategy} and/or {@link HttpTagAndSpanNamingAdapter} (preferred), * or override that method (last resort) if you want a different span naming format. * *

Note that if you have the subspan option turned off then this interceptor will propagate the {@link * Tracer#getCurrentSpan()}'s tracing info downstream if it's available, but will do nothing if no current span exists * on the current thread when this interceptor executes as there's no tracing info to propagate. Turning on the * subspan option mitigates this as it guarantees there will be a span to propagate. * *

As mentioned at the top of this class' javadocs, {@link WingtipsHttpClientBuilder} is recommended instead of * these interceptors if you have control over which {@link HttpClientBuilder} is used to create your {@link * HttpClient}s. Reasons for this: *

    *
  • * There are certain types of exceptions that can occur that prevent the response interceptor side of this * class from executing, thus preventing the subspan around the request from completing. This is a consequence * of how the Apache HttpClient interceptors work. The only way to guarantee subspan completion is to use * {@link WingtipsHttpClientBuilder} instead of this interceptor. *
  • *
  • * There are several ways for interceptors to be accidentally wiped out, e.g. {@link * HttpClientBuilder#setHttpProcessor(HttpProcessor)}. *
  • *
  • * {@link WingtipsHttpClientBuilder} makes sure that any subspan *fully* surrounds the request, including all * other interceptors that are executed. *
  • *
  • * You have to remember to add this interceptor as both a request interceptor ({@link HttpRequestInterceptor}) * *and* response interceptor ({@link HttpResponseInterceptor}) on {@link HttpClientBuilder}, or tracing will * be broken. *
  • *
* That said, these interceptors do work perfectly well as long as they are setup correctly *and* you never experience * any of the exceptions that cause the response interceptor to be ignored (this is usually impossible to guarantee, * making it a major issue for most use cases - again, please use {@link WingtipsHttpClientBuilder} if you can). * * @author Nic Munroe */ @SuppressWarnings("WeakerAccess") public class WingtipsApacheHttpClientInterceptor implements HttpRequestInterceptor, HttpResponseInterceptor { /** * Static default instance of this class. This class is thread-safe so you can reuse this default instance instead * of creating new objects. */ public static final WingtipsApacheHttpClientInterceptor DEFAULT_IMPL = new WingtipsApacheHttpClientInterceptor(); /** * This is just {@link #DEFAULT_IMPL} explicitly typed to {@link HttpRequestInterceptor} so that you can call * {@link HttpClientBuilder#addInterceptorFirst(HttpRequestInterceptor)} or {@link * HttpClientBuilder#addInterceptorLast(HttpRequestInterceptor)} without having to explicitly cast it to * {@link HttpRequestInterceptor}. */ public static final HttpRequestInterceptor DEFAULT_REQUEST_IMPL = DEFAULT_IMPL; /** * This is just {@link #DEFAULT_IMPL} explicitly typed to {@link HttpResponseInterceptor} so that you can call * {@link HttpClientBuilder#addInterceptorFirst(HttpResponseInterceptor)} or {@link * HttpClientBuilder#addInterceptorLast(HttpResponseInterceptor)} without having to explicitly cast it to * {@link HttpResponseInterceptor}. */ public static final HttpResponseInterceptor DEFAULT_RESPONSE_IMPL = DEFAULT_IMPL; protected static final String SPAN_TO_CLOSE_HTTP_CONTEXT_ATTR_KEY = WingtipsApacheHttpClientInterceptor.class.getSimpleName() + "-span_to_close"; protected final boolean surroundCallsWithSubspan; protected final HttpTagAndSpanNamingStrategy tagAndNamingStrategy; protected final HttpTagAndSpanNamingAdapter tagAndNamingAdapter; /** * Creates a new instance with the subspan option turned on and the default {@link HttpTagAndSpanNamingStrategy} * and {@link HttpTagAndSpanNamingAdapter} ({@link ZipkinHttpTagStrategy} and {@link ApacheHttpClientTagAdapter}). */ public WingtipsApacheHttpClientInterceptor() { this(true); } /** * Creates a new instance with the subspan option set to the value of the {@code surroundCallsWithSubspan} * argument, and the default {@link HttpTagAndSpanNamingStrategy} and {@link HttpTagAndSpanNamingAdapter} * ({@link ZipkinHttpTagStrategy} and {@link ApacheHttpClientTagAdapter}). * * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. */ public WingtipsApacheHttpClientInterceptor(boolean surroundCallsWithSubspan) { this( surroundCallsWithSubspan, ZipkinHttpTagStrategy.getDefaultInstance(), ApacheHttpClientTagAdapter.getDefaultInstance() ); } /** * Creates a new instance with the subspan option set to the value of the {@code surroundCallsWithSubspan} * argument, and the given {@link HttpTagAndSpanNamingStrategy} and {@link HttpTagAndSpanNamingAdapter}. * * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. * @param tagAndNamingStrategy The span tag and naming strategy to use - cannot be null. If you really want no * tag and naming strategy, then pass in {@link NoOpHttpTagStrategy#getDefaultInstance()}. * @param tagAndNamingAdapter The tag and naming adapter to use - cannot be null. If you really want no tag and * naming adapter, then pass in {@link NoOpHttpTagAdapter#getDefaultInstance()}. */ public WingtipsApacheHttpClientInterceptor( boolean surroundCallsWithSubspan, HttpTagAndSpanNamingStrategy tagAndNamingStrategy, HttpTagAndSpanNamingAdapter tagAndNamingAdapter ) { if (tagAndNamingStrategy == null) { throw new IllegalArgumentException( "tagAndNamingStrategy cannot be null - if you really want no strategy, use NoOpHttpTagStrategy" ); } if (tagAndNamingAdapter == null) { throw new IllegalArgumentException( "tagAndNamingAdapter cannot be null - if you really want no adapter, use NoOpHttpTagAdapter" ); } this.surroundCallsWithSubspan = surroundCallsWithSubspan; this.tagAndNamingStrategy = tagAndNamingStrategy; this.tagAndNamingAdapter = tagAndNamingAdapter; } @Override public void process(HttpRequest request, HttpContext context) { Tracer tracer = Tracer.getInstance(); if (surroundCallsWithSubspan) { // Will start a new trace if necessary, or a subspan if a trace is already in progress. Span spanToClose = tracer.startSpanInCurrentContext( getSubspanSpanName(request, tagAndNamingStrategy, tagAndNamingAdapter), Span.SpanPurpose.CLIENT ); tagAndNamingStrategy.handleRequestTagging(spanToClose, request, tagAndNamingAdapter); // Add the subspan to the HttpContext so that the response interceptor can retrieve and close it. context.setAttribute(SPAN_TO_CLOSE_HTTP_CONTEXT_ATTR_KEY, spanToClose); } propagateTracingHeaders(request, tracer.getCurrentSpan()); } @Override public void process(HttpResponse response, HttpContext context) { // See if there's a subspan passed to us from the request interceptor. Span spanToClose = (Span) context.getAttribute(SPAN_TO_CLOSE_HTTP_CONTEXT_ATTR_KEY); if (spanToClose != null) { // There was a subspan. Finalize and close it. try { // Handle response/error tagging and final span name. // The request should be found in the context attributes - try to extract it from there. // We have no access to any error, so we pass null for the error arg. HttpRequest request = null; Object requestRawObj = context.getAttribute(HttpCoreContext.HTTP_REQUEST); if (requestRawObj instanceof HttpRequest) { request = (HttpRequest) requestRawObj; } tagAndNamingStrategy.handleResponseTaggingAndFinalSpanName( spanToClose, request, response, null, tagAndNamingAdapter ); } finally { // Span.close() contains the logic we want - if the spanToClose was an overall span (new trace) // then tracer.completeRequestSpan() will be called, otherwise it's a subspan and // tracer.completeSubSpan() will be called. spanToClose.close(); } } } /** * Returns the name that should be used for the subspan surrounding the call. Defaults to whatever {@link * HttpTagAndSpanNamingStrategy#getInitialSpanName(Object, HttpTagAndSpanNamingAdapter)} returns, with a fallback * of {@link WingtipsApacheHttpClientUtil#getFallbackSubspanSpanName(HttpRequest)} if the naming strategy returned * null or blank string. You can override this method to return something else if you want different behavior and * you don't want to adjust the naming strategy or adapter. * * @param request The request that is about to be executed. * @param namingStrategy The {@link HttpTagAndSpanNamingStrategy} being used. * @param adapter The {@link HttpTagAndSpanNamingAdapter} being used. * @return The name that should be used for the subspan surrounding the call. */ protected @NotNull String getSubspanSpanName( @NotNull HttpRequest request, @NotNull HttpTagAndSpanNamingStrategy namingStrategy, @NotNull HttpTagAndSpanNamingAdapter adapter ) { // Try the naming strategy first. String subspanNameFromStrategy = namingStrategy.getInitialSpanName(request, adapter); if (StringUtils.isNotBlank(subspanNameFromStrategy)) { return subspanNameFromStrategy; } // The naming strategy didn't have anything for us. Fall back to something reasonable. return WingtipsApacheHttpClientUtil.getFallbackSubspanSpanName(request); } /** * Helper method for adding a default instance of this interceptor to the given builder's request *and* response * interceptors. The interceptors will have their subspan option turned on. * * @param builder The builder to add the tracing interceptors to. * @param The type of the builder. * @return The same builder passed in, but with tracing interceptors added. */ public static T addTracingInterceptors(T builder) { return addTracingInterceptors(builder, true); } /** * Helper method for adding a default instance of this interceptor to the given builder's request *and* response * interceptors. The interceptors will have their subspan option set to the value of the given * {@code surroundCallsWithSubspan} argument. * * @param builder The builder to add the tracing interceptors to. * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. * @param The type of the builder. * @return The same builder passed in, but with tracing interceptors added. */ public static T addTracingInterceptors(T builder, boolean surroundCallsWithSubspan) { WingtipsApacheHttpClientInterceptor interceptor = (surroundCallsWithSubspan) ? DEFAULT_IMPL : new WingtipsApacheHttpClientInterceptor(false); builder.addInterceptorFirst((HttpRequestInterceptor)interceptor); builder.addInterceptorLast((HttpResponseInterceptor)interceptor); return builder; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy