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.wingtips.Span;
import com.nike.wingtips.Tracer;
import com.nike.wingtips.apache.httpclient.util.WingtipsApacheHttpClientUtil;

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.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;

import java.io.IOException;

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

/**
 * (NOTE: {@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 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)} - override that method 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 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. * * @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; /** * Creates a new instance with the subspan option turned on. */ public WingtipsApacheHttpClientInterceptor() { this(true); } /** * Creates a new instance with the subspan option set to the value of the {@code surroundCallsWithSubspan} argument. * * @param surroundCallsWithSubspan Pass in true to have requests surrounded in a subspan, false to disable the * subspan option. */ public WingtipsApacheHttpClientInterceptor(boolean surroundCallsWithSubspan) { this.surroundCallsWithSubspan = surroundCallsWithSubspan; } @Override public void process(HttpRequest request, HttpContext context) throws HttpException, IOException { 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), Span.SpanPurpose.CLIENT); // 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) throws HttpException, IOException { // 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. Close it. // 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 {@code * apachehttpclient_downstream_call-[HTTP_METHOD]_[REQUEST_URI]} with any query string stripped, e.g. for a GET * call to https://foo.bar/baz?stuff=things, this would return {@code * "apachehttpclient_downstream_call-GET_https://foo.bar/baz"}. You can override this * method to return something else if you want a different subspan name format. * * @param request The request that is about to be executed. * @return The name that should be used for the subspan surrounding the call. */ protected String getSubspanSpanName(HttpRequest request) { return WingtipsApacheHttpClientUtil.getSubspanSpanName(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