/* * Copyright 2016-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.glowroot.agent.plugin.httpclient; import java.net.URL; import javax.annotation.Nullable; import org.glowroot.agent.plugin.api.Agent; import org.glowroot.agent.plugin.api.AsyncTraceEntry; import org.glowroot.agent.plugin.api.AuxThreadContext; import org.glowroot.agent.plugin.api.MessageSupplier; import org.glowroot.agent.plugin.api.ThreadContext; import org.glowroot.agent.plugin.api.TimerName; import org.glowroot.agent.plugin.api.TraceEntry; import org.glowroot.agent.plugin.api.weaving.BindClassMeta; import org.glowroot.agent.plugin.api.weaving.BindParameter; import org.glowroot.agent.plugin.api.weaving.BindReceiver; import org.glowroot.agent.plugin.api.weaving.BindThrowable; import org.glowroot.agent.plugin.api.weaving.BindTraveler; import org.glowroot.agent.plugin.api.weaving.Mixin; import org.glowroot.agent.plugin.api.weaving.OnBefore; import org.glowroot.agent.plugin.api.weaving.OnReturn; import org.glowroot.agent.plugin.api.weaving.OnThrow; import org.glowroot.agent.plugin.api.weaving.Pointcut; import org.glowroot.agent.plugin.api.weaving.Shim; public class OkHttpClientAspect { @Shim("com.squareup.okhttp.Request") public interface Request { @Nullable String method(); @Nullable URL url(); } // the field and method names are verbose to avoid conflict since they will become fields // and methods in all classes that extend com.squareup.okhttp.Callback @Mixin("com.squareup.okhttp.Callback") public abstract static class CallbackImpl implements CallbackMixin { private volatile @Nullable AsyncTraceEntry glowroot$asyncTraceEntry; private volatile @Nullable AuxThreadContext glowroot$auxContext; @Override public @Nullable AsyncTraceEntry glowroot$getAsyncTraceEntry() { return glowroot$asyncTraceEntry; } @Override public void glowroot$setAsyncTraceEntry(@Nullable AsyncTraceEntry asyncTraceEntry) { this.glowroot$asyncTraceEntry = asyncTraceEntry; } @Override public @Nullable AuxThreadContext glowroot$getAuxContext() { return glowroot$auxContext; } @Override public void glowroot$setAuxContext(@Nullable AuxThreadContext auxContext) { this.glowroot$auxContext = auxContext; } } // the method names are verbose to avoid conflict since they will become methods in all classes // that extend com.squareup.okhttp.Callback public interface CallbackMixin { @Nullable AsyncTraceEntry glowroot$getAsyncTraceEntry(); void glowroot$setAsyncTraceEntry(@Nullable AsyncTraceEntry asyncTraceEntry); @Nullable AuxThreadContext glowroot$getAuxContext(); void glowroot$setAuxContext(@Nullable AuxThreadContext auxContext); } @Pointcut(className = "com.squareup.okhttp.Call", methodName = "execute", methodParameterTypes = {}, nestingGroup = "http-client", timerName = "http client request") public static class ExecuteAdvice { private static final TimerName timerName = Agent.getTimerName(ExecuteAdvice.class); @OnBefore public static @Nullable TraceEntry onBefore(ThreadContext context, @BindReceiver Object call, @BindClassMeta OkHttpClientCallInvoker callInvoker) { Request originalRequest = (Request) callInvoker.getOriginalRequest(call); if (originalRequest == null) { return null; } String method = originalRequest.method(); if (method == null) { method = ""; } else { method += " "; } URL urlObj = originalRequest.url(); String url; if (urlObj == null) { url = ""; } else { url = urlObj.toString(); } return context.startServiceCallEntry("HTTP", method + Uris.stripQueryString(url), MessageSupplier.create("http client request: {}{}", method, url), timerName); } @OnReturn public static void onReturn(@BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.end(); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.endWithError(t); } } } @Pointcut(className = "com.squareup.okhttp.Call", methodName = "enqueue", methodParameterTypes = {"com.squareup.okhttp.Callback"}, nestingGroup = "http-client", timerName = "http client request") public static class EnqueueAdvice { private static final TimerName timerName = Agent.getTimerName(EnqueueAdvice.class); @OnBefore public static @Nullable AsyncTraceEntry onBefore(ThreadContext context, @BindReceiver Object call, @BindParameter @Nullable CallbackMixin callback, @BindClassMeta OkHttpClientCallInvoker callInvoker) { Request originalRequest = (Request) callInvoker.getOriginalRequest(call); if (originalRequest == null) { return null; } if (callback == null) { return null; } String method = originalRequest.method(); if (method == null) { method = ""; } else { method += " "; } URL urlObj = originalRequest.url(); String url; if (urlObj == null) { url = ""; } else { url = urlObj.toString(); } AsyncTraceEntry asyncTraceEntry = context.startAsyncServiceCallEntry("HTTP", method + Uris.stripQueryString(url), MessageSupplier.create("http client request: {}{}", method, url), timerName); // important to inject values into callback in @OnBefore since it's possible for // callback to be invoked prior to @OnReturn callback.glowroot$setAsyncTraceEntry(asyncTraceEntry); callback.glowroot$setAuxContext(context.createAuxThreadContext()); return asyncTraceEntry; } @OnReturn public static void onReturn(@BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable AsyncTraceEntry asyncTraceEntry) { if (asyncTraceEntry != null) { asyncTraceEntry.stopSyncTimer(); asyncTraceEntry.endWithError(t); } } } @Pointcut(className = "com.squareup.okhttp.Callback", methodName = "onResponse|onFailure", methodParameterTypes = {".."}) public static class CallbackAdvice { @OnBefore public static @Nullable TraceEntry onBefore(@BindReceiver CallbackMixin callback) { AsyncTraceEntry asyncTraceEntry = callback.glowroot$getAsyncTraceEntry(); if (asyncTraceEntry != null) { asyncTraceEntry.end(); callback.glowroot$setAsyncTraceEntry(null); } AuxThreadContext auxContext = callback.glowroot$getAuxContext(); if (auxContext != null) { callback.glowroot$setAuxContext(null); return auxContext.start(); } return null; } @OnReturn public static void onReturn(@BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.end(); } } @OnThrow public static void onThrow(@BindThrowable Throwable t, @BindTraveler @Nullable TraceEntry traceEntry) { if (traceEntry != null) { traceEntry.endWithError(t); } } } }