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

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

package com.nike.wingtips.apache.httpclient;

import com.nike.internal.util.StringUtils;
import com.nike.wingtips.Span;
import com.nike.wingtips.Span.SpanPurpose;
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.HttpException;
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.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.protocol.HttpProcessor;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;

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

/**
 * (NOTE: This class is strongly recommended instead of {@link WingtipsApacheHttpClientInterceptor} 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 extension of {@link HttpClientBuilder}, where any {@link HttpClient}s that you {@link #build()} * will propagate Wingtips tracing on the {@link HttpClient} request headers and optionally surround the request in * a subspan. * *

If the subspan option is enabled but there's no current span on the current thread when a request 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 the subspan 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 the {@link Tracer#getCurrentSpan()}'s tracing info * will be propagated downstream if it's available, but if no current span exists on the current thread when the * request is executed then no tracing logic will occur 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, this class is the preferred way to automatically handle Wingtips * tracing propagation and subspans for {@link HttpClient} requests if you have control over the {@link * HttpClientBuilder} that gets used to generate {@link HttpClient}s. The other option is interceptors via {@link * WingtipsApacheHttpClientInterceptor} (when you don't have control over the {@link HttpClientBuilder}), however this * class is preferred instead of the interceptor where possible for the following reasons: *

    *
  • * There are certain types of exceptions that can occur that prevent the response interceptor side of {@link * WingtipsApacheHttpClientInterceptor} 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 this class instead of the interceptor. *
  • *
  • * There are several ways for interceptors to be accidentally wiped out, e.g. {@link * HttpClientBuilder#setHttpProcessor(HttpProcessor)}. *
  • *
  • * This class makes sure that any subspan *fully* surrounds the request, including all other interceptors that * are executed. *
  • *
  • * When using the interceptor instead of this class you have to remember to add the interceptor as both a * request interceptor ({@link HttpRequestInterceptor}) *and* response interceptor ({@link * HttpResponseInterceptor}) on the {@link HttpClientBuilder} you use, or tracing will be broken. *
  • *
* That said, the interceptors do work perfectly well when you aren't able to use this builder class 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 * this class instead of the interceptor if you can). * * @author Nic Munroe */ @SuppressWarnings("WeakerAccess") public class WingtipsHttpClientBuilder extends HttpClientBuilder { protected boolean surroundCallsWithSubspan; protected HttpTagAndSpanNamingStrategy tagAndNamingStrategy; protected 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 WingtipsHttpClientBuilder() { 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 WingtipsHttpClientBuilder(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 WingtipsHttpClientBuilder( 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; } /** * @return Static factory method for creating a new {@link WingtipsHttpClientBuilder} instance with the subspan * option turned on and the default {@link HttpTagAndSpanNamingStrategy} and {@link HttpTagAndSpanNamingAdapter} * ({@link ZipkinHttpTagStrategy} and {@link ApacheHttpClientTagAdapter}). */ public static WingtipsHttpClientBuilder create() { return new WingtipsHttpClientBuilder(); } /** * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. * @return Static factory method for creating a new {@link WingtipsHttpClientBuilder} 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}). */ public static WingtipsHttpClientBuilder create(boolean surroundCallsWithSubspan) { return new WingtipsHttpClientBuilder(surroundCallsWithSubspan); } /** * @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()}. * @return Static factory method for creating a new {@link WingtipsHttpClientBuilder} instance with the subspan * option set to the value of the {@code surroundCallsWithSubspan} argument, and the given * {@link HttpTagAndSpanNamingStrategy} and {@link HttpTagAndSpanNamingAdapter}. */ public static WingtipsHttpClientBuilder create( boolean surroundCallsWithSubspan, HttpTagAndSpanNamingStrategy tagAndNamingStrategy, HttpTagAndSpanNamingAdapter tagAndNamingAdapter ) { return new WingtipsHttpClientBuilder(surroundCallsWithSubspan, tagAndNamingStrategy, tagAndNamingAdapter); } @Override protected ClientExecChain decorateProtocolExec(final ClientExecChain protocolExec) { final boolean myHttpClientSurroundCallsWithSubspan = surroundCallsWithSubspan; return new ClientExecChain() { @Override @SuppressWarnings("TryFinallyCanBeTryWithResources") public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext, HttpExecutionAware execAware) throws IOException, HttpException { if(myHttpClientSurroundCallsWithSubspan) { return createNewSubSpanAndExecute(route, request, clientContext, execAware); } return propagateHeadersAndExecute(route, request, clientContext, execAware); } protected CloseableHttpResponse createNewSubSpanAndExecute(HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext, HttpExecutionAware execAware) throws IOException, HttpException { // Will start a new trace if necessary, or a subspan if a trace is already in progress. Span spanAroundCall = Tracer.getInstance().startSpanInCurrentContext( getSubspanSpanName(request, tagAndNamingStrategy, tagAndNamingAdapter), SpanPurpose.CLIENT ); CloseableHttpResponse response = null; Throwable errorForTagging = null; try { tagAndNamingStrategy.handleRequestTagging(spanAroundCall, request, tagAndNamingAdapter); response = propagateHeadersAndExecute(route, request, clientContext, execAware); return response; } catch(Throwable t) { errorForTagging = t; throw t; } finally { try { // Handle response/error tagging and final span name. tagAndNamingStrategy.handleResponseTaggingAndFinalSpanName( spanAroundCall, request, response, errorForTagging, tagAndNamingAdapter ); } finally { // Span.close() contains the logic we want - if the spanAroundCall was an overall span (new // trace) then tracer.completeRequestSpan() will be called, otherwise it's a subspan and // tracer.completeSubSpan() will be called. spanAroundCall.close(); } } } protected CloseableHttpResponse propagateHeadersAndExecute( HttpRoute route, HttpRequestWrapper request, HttpClientContext clientContext, HttpExecutionAware execAware ) throws IOException, HttpException { propagateTracingHeaders(request, Tracer.getInstance().getCurrentSpan()); return protocolExec.execute(route, request, clientContext, execAware); } }; } /** * 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); } /** * @return The current value of the subspan option. */ public boolean isSurroundCallsWithSubspan() { return surroundCallsWithSubspan; } /** * Sets the builder's subspan option value. New {@link HttpClient}s generated with {@link #build()} will use this * value when processing requests, but setting this here will not affect any {@link HttpClient}s that have already * been built - it only affects future-generated {@link HttpClient}s * * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. * @return This builder after setting the subspan option to the desired value. */ public WingtipsHttpClientBuilder setSurroundCallsWithSubspan(boolean surroundCallsWithSubspan) { this.surroundCallsWithSubspan = surroundCallsWithSubspan; return this; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy