You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/07/04 14:39:12 UTC
[GitHub] marcinczeczko commented on a change in pull request #40: WIP:
Remove com.sun.* dependency
marcinczeczko commented on a change in pull request #40: WIP: Remove com.sun.* dependency
URL: https://github.com/apache/incubator-openwhisk-runtime-java/pull/40#discussion_r200146999
##########
File path: java8/proxy/src/main/java/openwhisk/java/action/Proxy.java
##########
@@ -14,156 +14,198 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package openwhisk.java.action;
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-import java.net.InetSocketAddress;
-import java.nio.charset.StandardCharsets;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.HashMap;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
+import java.util.Map;
+import java.util.jar.Attributes;
import com.google.gson.JsonParser;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-
-public class Proxy {
- private HttpServer server;
-
- private JarLoader loader = null;
-
- public Proxy(int port) throws IOException {
- this.server = HttpServer.create(new InetSocketAddress(port), -1);
-
- this.server.createContext("/init", new InitHandler());
- this.server.createContext("/run", new RunHandler());
- this.server.setExecutor(null); // creates a default executor
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.AsyncResult;
+import io.vertx.core.Future;
+import io.vertx.core.Handler;
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpHeaders;
+import io.vertx.core.http.HttpServer;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.BodyHandler;
+import io.vertx.ext.web.handler.ErrorHandler;
+
+public class Proxy extends AbstractVerticle {
+
+ private static final int HTTP_INTERNAL_SERVER_ERROR = 500;
+ // Return 502 if the function is not found
+ private static final int ERROR_FUNCTION_NOT_FOUND = 502;
+ private static final int HTTP_OK = 200;
+
+ private static final String[] OW_ENV_KEYS = { "api_key", "namespace", "action_name", "activation_id", "deadline" };
+ private static final int PORT = 8080;
+
+ private URLClassLoader urlClassLoader;
+ private Method mainMethod;
+
+ @Override
+ public void start() throws InterruptedException {
+ HttpServer httpServer = vertx.createHttpServer();
+ Router router = Router.router(vertx);
+
+ router.route().handler(BodyHandler.create());
+ router.route().failureHandler(ErrorHandler.create(true));
+
+ router.route("/init").handler(this::initHandler);
+ router.route("/run").handler(this::runHandler);
+
+ httpServer.requestHandler(router::accept).listen(PORT);
+ }
+
+ private void initHandler(RoutingContext rc) {
+ if (urlClassLoader != null || mainMethod != null) {
+ rc.response().setStatusCode(HTTP_INTERNAL_SERVER_ERROR).end("Cannot initialize the action more than once.");
+ return;
}
- public void start() {
- server.start();
- }
+ final JsonObject request = rc.getBodyAsJson();
+ final JsonObject value = request.getJsonObject("value");
+ final byte[] jarBinary = value.getBinary("code");
- private static void writeResponse(HttpExchange t, int code, String content) throws IOException {
- byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
- t.sendResponseHeaders(code, bytes.length);
- OutputStream os = t.getResponseBody();
- os.write(bytes);
- os.close();
+ Proxy.writeCodeOnFileSystem(vertx, jarBinary, ar -> {
+ if (ar.failed()) {
+ rc.fail(ar.cause());
+ } else {
+ if (!ar.result().toFile().isFile()) {
+ rc.response().setStatusCode(HTTP_INTERNAL_SERVER_ERROR).end("Error invoking function");
+ }
+ try {
+ final URL url = ar.result().toUri().toURL();
+ urlClassLoader = new URLClassLoader(new URL[] { url});
+ String clazzName = value.getString("main");
+ String methodName = "main";
+ if(clazzName == null || clazzName.isEmpty()){
+ clazzName = getClassNameFromManifest(url);
+ }
+ if(clazzName.indexOf('#')>-1){
+ final String[] splitted = clazzName.split("#");
+ clazzName = splitted[0];
+ methodName= splitted[1];
+ }
+ if(clazzName == null ){
+ rc.response().setStatusCode(ERROR_FUNCTION_NOT_FOUND).end("Main class is empty, can not determine the function to invoke");
+ return;
+ }
+ final Class<?> mainClass = urlClassLoader.loadClass(clazzName);
+
+ final Method m = mainClass.getMethod(methodName, new Class[] { com.google.gson.JsonObject.class });
+ m.setAccessible(true);
+ final int modifiers = m.getModifiers();
+ if (m.getReturnType() != com.google.gson.JsonObject.class || !Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
+ throw new NoSuchMethodException(methodName);
+ }
+ this.mainMethod = m;
+
+ rc.response().setStatusCode(HTTP_OK).end("OK");
+
+ }catch(NoSuchMethodException e){
+ rc.response().setStatusCode(ERROR_FUNCTION_NOT_FOUND).end(e);
+ }
+ catch (ClassNotFoundException | IOException e) {
+ rc.response().setStatusCode(HTTP_INTERNAL_SERVER_ERROR).end("An error has occurred (see logs for details): " + e);
+ }
+ }
+ });
+ }
+
+ private String getClassNameFromManifest(URL url ) throws IOException{
+ final URL jarUrl = new URL("jar", "", url + "!/");
+ final JarURLConnection connection = (JarURLConnection)jarUrl.openConnection();
+ final Attributes attributes = connection.getMainAttributes();
+ return attributes != null
+ ? attributes.getValue(Attributes.Name.MAIN_CLASS)
+ : null;
+ }
+
+ private void runHandler(RoutingContext rc) {
+ final JsonObject request = rc.getBodyAsJson();
+ final JsonObject value = request.getJsonObject("value");
+ final JsonParser parser = new JsonParser();
+ final com.google.gson.JsonObject req = parser.parse(value.toString()).getAsJsonObject();
+
+ final HashMap<String, String> env = new HashMap<>();
+ for (String envKey : OW_ENV_KEYS) {
+ env.put(String.format("__OW_%s", envKey.toUpperCase()), value.getString(envKey));
}
-
- private static void writeError(HttpExchange t, String errorMessage) throws IOException {
- JsonObject message = new JsonObject();
- message.addProperty("error", errorMessage);
- writeResponse(t, 502, message.toString());
+ augmentEnv(env);
+
+ Thread.currentThread().setContextClassLoader(urlClassLoader);
+ System.setSecurityManager(new WhiskSecurityManager());
+
+ try {
+ final com.google.gson.JsonObject out = (com.google.gson.JsonObject) this.mainMethod.invoke(null, req);
Review comment:
i think if you leveraged the vertx features like eventbus the function invocation would be much more reactive.
But, i think new action "kind" would be necessary as the method invocation should make the things a different way,....
what if you invoked function using event bus, e.g.:
```
vertx.eventBus().<JsonObject>send("activation", <inputJsonObject>, reply - > {
if (reply.failed()) {
rc.response().setStatusCode(500);
} else {
rc.response.end(...);
}
}
```
Then the function could be just a class extending `AbstractVerticle` and in `start()` method simply consume eventBus instead of class with `main` method.
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
With regards,
Apache Git Services