Skip to content

Commit

Permalink
Load Callable classes without reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
Mourjo Sen committed Apr 25, 2020
1 parent e48306e commit 6670bbf
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 8 deletions.
28 changes: 25 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# lestrange

A playground for dark magic with Java class loaders. This is a work in progress. Currently uses reflection to invoke methods.
A playground for dark magic with Java class loaders. This is a work in
progress. There are two implmentations to call methods on dynamically
loaded classes currently:
- Using reflection to call a method by name on a class file passed in
an multi-part HTTP POST request
- Using the interface `Callable` and invoking the `call` method on the
object created from the class file passed

This library is an attempt to understand how dynamic class loaders work and how in languages like Clojure, code is evaluated.
This library is an attempt to understand how dynamic class loaders
work and how in languages like Clojure, code is evaluated.

![Bellatrix](/utils/bella.png)

Expand All @@ -14,19 +21,33 @@ Start the server with:
mvn clean install exec:java
```

Send a class file to be executed:
Send a class file to be executed via reflection:
```bash
curl -XPOST "localhost:8080/loadclass/?classname=SystemInfo&methodname=info" -F "file=@SystemInfo.class"
curl -XPOST "localhost:8080/loadclass/?classname=RandomNumber&methodname=sayHello" -F "file=@RandomNumber.class"
```

To run the `call` method on a `Callable` class:
```bash
curl -XPOST "localhost:8080/callable/?classname=CallableAPI" -F "file=@CallableAPI.class"
_ _
| | | |
| | ___ ___| |_ _ __ __ _ _ __ __ _ ___
| | / _ \ __| __| '__/ _` | '_ \ / _` |/ _ \
| |____ __\__ \ |_| | | (_| | | | | (_| | __/
|______\___|___/\__|_| \__,_|_| |_|\__, |\___|
__/ |
|___/
```
# Generating class files
Ensure that clases are public and the method being accessed is public.
```bash
cd utils
javac RandomNumber.java
javac HelloClass.java
javac SystemInfo.java
javac CallableAPI.java
```
# Notes
Expand Down Expand Up @@ -55,6 +76,7 @@ while true;
curl -XPOST "localhost:8080/loadclass/?classname=SystemInfo&methodname=info" -F "file=@SystemInfo.class";
curl -XPOST "localhost:8080/loadclass/?classname=HelloClass&methodname=sayHello" -F "file=@HelloClass.class";
curl -XPOST "localhost:8080/loadclass/?classname=RandomNumber&methodname=sayHello" -F "file=@RandomNumber.class";
curl -XPOST "localhost:8080/callable/?classname=CallableAPI" -F "file=@CallableAPI.class";
done
```
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/lestrange/CallableLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lestrange;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpStatus;

public class CallableLoader extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(HttpStatus.OK_200);
resp.getWriter().println("Use the POST method.");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String cl = req.getParameter("classname");

InputStream x = req.getPart("file").getInputStream();
SimpleClassLoader hc = new SimpleClassLoader(x);

@SuppressWarnings("unchecked")
Class<Callable<Object>> callable = (Class<Callable<Object>>) hc.loadClass(cl);

Object result = callable.newInstance().call();

resp.setStatus(HttpStatus.OK_200);
resp.getWriter().println(result == null ? "Result is a null" : result.toString());

} catch (Exception e) {
e.printStackTrace();
resp.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
resp.getWriter().println("That's too bad... " + e.getMessage());
}
}

}
13 changes: 9 additions & 4 deletions src/main/java/lestrange/Main.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package lestrange;

import javax.servlet.MultipartConfigElement;
import javax.servlet.annotation.MultipartConfig;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

public class Main {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);

QueuedThreadPool tp = (QueuedThreadPool) server.getThreadPool();
tp.setLowThreadsThreshold(0);
tp.setReservedThreads(-1);
tp.setMinThreads(1);
tp.setMaxThreads(5);

ServletContextHandler handler = new ServletContextHandler(server, "/loadclass");
ServletContextHandler handler = new ServletContextHandler(server, "/");

MultipartConfigElement mpc = new MultipartConfigElement("");

handler.addServlet(ReflectiveLoader.class, "/loadclass/").getRegistration().setMultipartConfig(mpc);

handler.addServlet(LoaderServlet.class, "/").getRegistration()
.setMultipartConfig(new MultipartConfigElement(""));
handler.addServlet(CallableLoader.class, "/callable/").getRegistration().setMultipartConfig(mpc);

server.start();
server.join();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import org.eclipse.jetty.http.HttpStatus;

public class LoaderServlet extends HttpServlet {
public class ReflectiveLoader extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Expand Down
30 changes: 30 additions & 0 deletions utils/CallableAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Callable;

public class CallableAPI implements Callable<String> {

public String call() throws Exception {
URL url = new URL("http://artii.herokuapp.com/make?text=Lestrange");
HttpURLConnection con = (HttpURLConnection) url.openConnection();

con.setRequestMethod("GET");
con.setConnectTimeout(5000);
con.setReadTimeout(5000);
con.setDoOutput(true);

BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine+"\n");
}

in.close();
con.disconnect();
return content.toString();
}
}

0 comments on commit 6670bbf

Please sign in to comment.