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

com.appland.appmap.process.hooks.Spark Maven / Gradle / Ivy

There is a newer version: 1.27.1
Show newest version
package com.appland.appmap.process.hooks;

import static com.appland.appmap.util.ClassUtil.safeClassForName;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.tinylog.TaggedLogger;

import com.appland.appmap.config.AppMapConfig;
import com.appland.appmap.output.v1.Event;
import com.appland.appmap.process.ExitEarly;
import com.appland.appmap.process.hooks.http.HttpServerRequest;
import com.appland.appmap.process.hooks.remoterecording.RemoteRecordingManager;
import com.appland.appmap.process.hooks.remoterecording.ServletRequest;
import com.appland.appmap.reflect.DynamicReflectiveType;
import com.appland.appmap.reflect.HttpServletRequest;
import com.appland.appmap.reflect.HttpServletResponse;
import com.appland.appmap.reflect.ReflectiveType;
import com.appland.appmap.transform.annotations.ArgumentArray;
import com.appland.appmap.transform.annotations.HookClass;

public class Spark {
  private static final TaggedLogger logger = AppMapConfig.getLogger(null);

  private static class HandlerWrapper extends ReflectiveType {
    private static String SET_HANDLER = "setHandler";

    public HandlerWrapper(Object self) {
      super(self);
      addMethod(SET_HANDLER, "org.eclipse.jetty.server.Handler");
    }

    public void setHandler(Object handler) {
      invokeVoidMethod(SET_HANDLER, handler);
    }

  }

  private static class JettyRequest extends ReflectiveType {
    private static String SET_HANDLED = "setHandled";

    public JettyRequest(Object self) {
      super(self);
      addMethod(SET_HANDLED, Boolean.TYPE);
    }

    public void setHandled(boolean handled) {
      invokeVoidMethod(SET_HANDLED, handled);
    }
  }

  private static class Handler extends DynamicReflectiveType implements InvocationHandler {
    private Object wrapper;

    private Handler(Object wrapper) {
      this.wrapper = wrapper;
    }

    static Object build(Object handler) {
      ClassLoader cl = handler.getClass().getClassLoader();
      try {
        Object wrapper = safeClassForName(cl, "org.eclipse.jetty.server.handler.HandlerWrapper")
            .getConstructor()
            .newInstance();
        new HandlerWrapper(wrapper)
            .setHandler(handler);
        return DynamicReflectiveType.build(new Handler(wrapper), cl, "org.eclipse.jetty.server.Handler");
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
          | InvocationTargetException | NoSuchMethodException | SecurityException e) {
        // Should never happen
        logger.error(e);
        throw new InternalError(e);
      }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String name = method.getName();
      if (name.equals("handle")) {
        JettyRequest jettyReq = new JettyRequest(args[1]);
        HttpServletRequest req = new HttpServletRequest(args[2]);
        HttpServletResponse resp = new HttpServletResponse(args[3]);

        String requestURI = req.getRequestURI();
        logger.trace("handling {}", requestURI);
        if (requestURI.equals("/_appmap/record")) {
          if (RemoteRecordingManager.service(new ServletRequest(req, resp))) {
            jettyReq.setHandled(true);
            return null;
          }
        }

        RequestRecording.start(req);
        Event http_server_request = Event.functionCallEvent();
        HttpServerRequest.recordHttpServerRequest(http_server_request, req);

        try {
          method.invoke(wrapper, args);
          req.setAttribute(HttpServerRequest.STATUS_ATTRIBUTE, Integer.valueOf(resp.getStatus()));
          Event http_server_response = Event.functionReturnEvent();
          HttpServerRequest.recordHttpServerResponse(http_server_response, req, resp);
          return null;
        } finally {
          RequestRecording.stop(req);
        }
      }
      logger.trace("wrapper: {}, invoking {}", wrapper, method);
      return method.invoke(wrapper, args);
    }
  }

  private static class HandlerList extends ReflectiveType {
    private static String GET_HANDLERS = "getHandlers";

    public HandlerList(Object self) {
      super(self);
      addMethods(GET_HANDLERS);
    }

    public Object[] getHandlers() {
      return (Object[]) invokeObjectMethod(GET_HANDLERS);
    }
  }

  @HookClass(value = "org.eclipse.jetty.server.handler.HandlerWrapper")
  @ArgumentArray
  public static void setHandler(Event event, Object receiver, Object[] args) {
    // TODO: consider all inherited methods in ClassFileTransformer.transform?

    // If we looked at all the methods returned by CtClass.getMethods, in
    // addition to those returned by CtClass.getDeclaredBehaviors, we wouldn't
    // have to do things like this:
    if (!receiver.getClass().getName().equals("org.eclipse.jetty.server.Server")) {
      return;
    }

    Object handler = args[0];
    if (!isSparkHandler(handler)) {
      return;
    }

    HandlerWrapper server = new HandlerWrapper(receiver);
    logger.trace("handler: {}", handler);
    server.setHandler(Handler.build(handler));

    // We just set the handler, don't continue with the method
    throw new ExitEarly();
  }

  private static boolean isSparkHandler(Object handler) {
    // Spark will set the server's handler to be either a Spark JettyHandler, or a
    // HandlerList that contains a JettyHandler.
    String argClass = handler.getClass().getName();
    String jettyHandlerClass = "spark.embeddedserver.jetty.JettyHandler";
    if (argClass.equals(jettyHandlerClass)) {
      return true;
    }

    if (argClass.equals("org.eclipse.jetty.server.handler.HandlerList")) {
      HandlerList hl = new HandlerList(handler);
      return Arrays.stream(hl.getHandlers())
          .anyMatch(h -> h.getClass().getName().equals(jettyHandlerClass));
    }

    // Otherwise, we're not in a Spark server
    return false;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy