blob: ad36124249f1e1a628048b5bce730c30b74120c5 [file] [log] [blame]
/*
* Copyright 2015-2016 IBM Corporation
*
* 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 openwhisk.java.action;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.HashMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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
}
public void start() {
server.start();
}
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();
}
private static void writeError(HttpExchange t, String errorMessage) throws IOException {
JsonObject message = new JsonObject();
message.addProperty("error", errorMessage);
writeResponse(t, 502, message.toString());
}
private class InitHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
if (loader != null) {
Proxy.writeError(t, "Cannot initialize the action more than once.");
return;
}
try {
InputStream is = t.getRequestBody();
JsonParser parser = new JsonParser();
JsonElement ie = parser.parse(new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)));
JsonObject inputObject = ie.getAsJsonObject();
JsonObject message = inputObject.getAsJsonObject("value");
String mainClass = message.getAsJsonPrimitive("main").getAsString();
String base64Jar = message.getAsJsonPrimitive("code").getAsString();
// FIXME: this is obviously not very useful. The idea is that we
// will implement/use
// a streaming parser for the incoming JSON object so that we
// can stream the contents
// of the jar straight to a file.
InputStream jarIs = new ByteArrayInputStream(base64Jar.getBytes(StandardCharsets.UTF_8));
// Save the bytes to a file.
Path jarPath = JarLoader.saveBase64EncodedFile(jarIs);
// Start up the custom classloader. This also checks that the
// main method exists.
loader = new JarLoader(jarPath, mainClass);
Proxy.writeResponse(t, 200, "OK");
return;
} catch (Exception e) {
e.printStackTrace(System.err);
Proxy.writeError(t, "An error has occurred (see logs for details): " + e);
return;
}
}
}
private class RunHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
if (loader == null) {
Proxy.writeError(t, "Cannot invoke an uninitialized action.");
return;
}
ClassLoader cl = Thread.currentThread().getContextClassLoader();
SecurityManager sm = System.getSecurityManager();
try {
InputStream is = t.getRequestBody();
JsonParser parser = new JsonParser();
JsonElement ie = parser.parse(new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)));
JsonObject inputObject = ie.getAsJsonObject().getAsJsonObject("value");
HashMap<String, String> env = new HashMap<String, String>();
for (String p : new String[] { "api_key", "namespace", "action_name", "activation_id", "deadline" }) {
try {
String val = ie.getAsJsonObject().getAsJsonPrimitive(p).getAsString();
env.put(String.format("__OW_%s", p.toUpperCase()), val);
} catch (Exception e) {}
}
Thread.currentThread().setContextClassLoader(loader);
System.setSecurityManager(new WhiskSecurityManager());
// User code starts running here.
JsonObject output = loader.invokeMain(inputObject, env);
// User code finished running here.
if(output == null) {
throw new NullPointerException("The action returned null");
}
Proxy.writeResponse(t, 200, output.toString());
return;
} catch (InvocationTargetException ite) {
// These are exceptions from the action, wrapped in ite because
// of reflection
Throwable underlying = ite.getCause();
underlying.printStackTrace(System.err);
Proxy.writeError(t,
"An error has occured while invoking the action (see logs for details): " + underlying);
} catch (Exception e) {
e.printStackTrace(System.err);
Proxy.writeError(t, "An error has occurred (see logs for details): " + e);
} finally {
System.setSecurityManager(sm);
Thread.currentThread().setContextClassLoader(cl);
}
}
}
public static void main(String args[]) throws Exception {
Proxy proxy = new Proxy(8080);
proxy.start();
}
}