package com.github.kristofa.brave.servlet; import com.github.kristofa.brave.Brave; import com.github.kristofa.brave.KeyValueAnnotation; import com.github.kristofa.brave.ServerRequestInterceptor; import com.github.kristofa.brave.ServerResponseAdapter; import com.github.kristofa.brave.ServerResponseInterceptor; import com.github.kristofa.brave.ServerTracer; import com.github.kristofa.brave.http.DefaultSpanNameProvider; import com.github.kristofa.brave.http.HttpServerRequestAdapter; import com.github.kristofa.brave.http.HttpServerResponseAdapter; import com.github.kristofa.brave.http.SpanNameProvider; import com.github.kristofa.brave.internal.Nullable; import com.github.kristofa.brave.servlet.internal.MaybeAddClientAddressFromRequest; import java.util.Collection; import java.util.Collections; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import java.io.IOException; import zipkin.Constants; import zipkin.TraceKeys; import static com.github.kristofa.brave.internal.Util.checkNotNull; /** * Servlet filter that will extract trace headers from the request and send * sr (server received) and ss (server sent) annotations. * * @deprecated Replaced by {@code TracingFilter} from brave-instrumentation-servlet */ public class BraveServletFilter implements Filter { /** Creates a tracing filter with defaults. Use {@link #builder(Brave)} to customize. */ public static BraveServletFilter create(Brave brave) { return new Builder(brave).build(); } public static Builder builder(Brave brave) { return new Builder(brave); } public static final class Builder { final Brave brave; SpanNameProvider spanNameProvider = new DefaultSpanNameProvider(); Builder(Brave brave) { // intentionally hidden this.brave = checkNotNull(brave, "brave"); } public Builder spanNameProvider(SpanNameProvider spanNameProvider) { this.spanNameProvider = checkNotNull(spanNameProvider, "spanNameProvider"); return this; } public BraveServletFilter build() { return new BraveServletFilter(this); } } private final ServerRequestInterceptor requestInterceptor; private final ServerResponseInterceptor responseInterceptor; private final SpanNameProvider spanNameProvider; @Nullable // while deprecated constructor is in use private final ServerTracer serverTracer; private final MaybeAddClientAddressFromRequest maybeAddClientAddressFromRequest; private FilterConfig filterConfig; protected BraveServletFilter(Builder b) { // intentionally hidden this.requestInterceptor = b.brave.serverRequestInterceptor(); this.responseInterceptor = b.brave.serverResponseInterceptor(); this.spanNameProvider = b.spanNameProvider; this.serverTracer = b.brave.serverTracer(); this.maybeAddClientAddressFromRequest = MaybeAddClientAddressFromRequest.create(b.brave); } /** * @deprecated please use {@link #create(Brave)} or {@link #builder(Brave)} */ @Deprecated public BraveServletFilter(ServerRequestInterceptor requestInterceptor, ServerResponseInterceptor responseInterceptor, SpanNameProvider spanNameProvider) { this.requestInterceptor = requestInterceptor; this.responseInterceptor = responseInterceptor; this.spanNameProvider = spanNameProvider; this.serverTracer = null; this.maybeAddClientAddressFromRequest = null; } @Override public void init(FilterConfig filterConfig) throws ServletException { this.filterConfig = filterConfig; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null; if (hasAlreadyFilteredAttribute) { // Proceed without invoking this filter... filterChain.doFilter(request, response); } else { request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); HttpServletRequest httpRequest = (HttpServletRequest) request; final Servlet25ServerResponseAdapter servlet25ServerResponseAdapter = new Servlet25ServerResponseAdapter((HttpServletResponse) response); requestInterceptor.handle(new HttpServerRequestAdapter(new ServletHttpServerRequest(httpRequest), spanNameProvider)); if (maybeAddClientAddressFromRequest != null) { maybeAddClientAddressFromRequest.accept(httpRequest); } try { filterChain.doFilter(request, servlet25ServerResponseAdapter); } catch (IOException | ServletException| RuntimeException | Error e) { if (serverTracer != null) { // TODO: revisit https://github.com/openzipkin/openzipkin.github.io/issues/52 String message = e.getMessage(); if (message == null) message = e.getClass().getSimpleName(); serverTracer.submitBinaryAnnotation(Constants.ERROR, message); } throw e; } finally { responseInterceptor.handle(servlet25ServerResponseAdapter); } } } @Override public void destroy() { } private String getAlreadyFilteredAttributeName() { String name = getFilterName(); if (name == null) { name = getClass().getName(); } return name + ".FILTERED"; } private final String getFilterName() { return (this.filterConfig != null ? this.filterConfig.getFilterName() : null); } /** When deployed in Servlet 2.5 environment {@link #getStatus} is not available. */ static final class Servlet25ServerResponseAdapter extends HttpServletResponseWrapper implements ServerResponseAdapter { // The Servlet spec says: calling setStatus is optional, if no status is set, the default is OK. int httpStatus = HttpServletResponse.SC_OK; Servlet25ServerResponseAdapter(HttpServletResponse response) { super(response); } @Override public void setStatus(int sc, String sm) { httpStatus = sc; super.setStatus(sc, sm); } @Override public void sendError(int sc) throws IOException { httpStatus = sc; super.sendError(sc); } @Override public void sendError(int sc, String msg) throws IOException { httpStatus = sc; super.sendError(sc, msg); } @Override public void setStatus(int sc) { httpStatus = sc; super.setStatus(sc); } /** Alternative to {@link #getStatus}, but for Servlet 2.5+ */ @Override public Collection<KeyValueAnnotation> responseAnnotations() { return Collections.singleton( KeyValueAnnotation.create(TraceKeys.HTTP_STATUS_CODE, String.valueOf(httpStatus)) ); } } }