io.inversion.lambda.AwsApiGatewayLambdaRequestStreamHandler Maven / Gradle / Ivy
/*
* Copyright (c) 2015-2019 Rocket Partners, LLC
* https://github.com/inversion-api
*
* 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 io.inversion.lambda;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import io.inversion.Api;
import io.inversion.Engine;
import io.inversion.Request;
import io.inversion.Response;
import io.inversion.json.JSMap;
import io.inversion.json.JSNode;
import io.inversion.json.JSParser;
import io.inversion.Url;
import io.inversion.utils.Utils;
import io.inversion.utils.ListMap;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* Adapter to run an Inversion API as an AWS Lambda behind an ApiGateway
*/
public class AwsApiGatewayLambdaRequestStreamHandler implements RequestStreamHandler {
protected Engine engine = null;
protected Api api = null;
boolean debug = false;
@SuppressWarnings("unchecked")
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
String input = Utils.read(new BufferedInputStream(inputStream));
JSNode responseBody = new JSMap();
JSNode config = null;
Exception ex = null;
try {
JSNode json = JSParser.asJSNode(input);
debug("Request Event");
debug(json.toString(false));
String method = json.getString("httpMethod");
String host = (String) json.find("headers.Host");
String path = (String) json.find("requestContext.path");
Url url = new Url("http://" + host + path);
String profile = path != null ? Utils.explode("/", path).get(0) : "";
String proxyPath = (String) json.find("pathParameters.proxy");
proxyPath = proxyPath != null ? proxyPath : "";
String pathStr = Utils.implode("/", path);
String proxyStr = Utils.implode("/", proxyPath);
String servletPath = "";
if (pathStr.length() > proxyStr.length()) {
servletPath = pathStr.substring(0, pathStr.length() - proxyStr.length());
}
config = new JSMap("method", method, "host", host, "path", path, "url", url.toString(), "profile", profile, "proxyPath", proxyPath, "servletPath", servletPath);
if (engine == null) {
synchronized (this) {
if (engine == null) {
engine = buildEngine(profile, servletPath);
engine.startup();
}
}
}
ListMap headers = new ListMap<>();
JSMap jsonHeaders = json.getMap("headers");
if(jsonHeaders != null)
headers.putAll((Map)headers);
JSNode jsonParams = json.getNode("queryStringParameters");
Map params = jsonParams == null ? new HashMap<>() : (Map) jsonParams;
String body = json.getString("body");
if (method.equals("POST") && body != null) {
Map postParams = Utils.parseQueryString(body);
params.putAll(postParams);
}
Request req = new Request(method, url.toString(), body, params, headers);
Response res = new Response();
engine.service(req, res);
if (outputStream != null) {
writeResponse(res, outputStream);
}
} catch (Exception e1) {
ex = e1;
} finally {
if (ex != null) {
if (config != null)
responseBody.put("config", config);
responseBody.put("error", Utils.getShortCause(ex));
responseBody.put("request", JSParser.asJSNode(input));
JSNode responseJson = new JSMap();
responseJson.put("isBase64Encoded", false);
responseJson.put("statusCode", "500");
responseJson.put("headers", new JSMap("Access-Control-Allow-Origin", "*"));
responseJson.put("body", responseBody.toString());
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
writer.write(responseJson.toString());
writer.close();
}
}
}
/**
* Optional subclass override hook to allow for advanced Engine configuration.
*
* Simple embeddings can leave this alone. Complex embeddings can either set the engine via setEngine() or override this method to construct/configure as needed.
*
* This default implementation constructs an Engine with the supplied configProfile and adds api
to it if api
is not null.
*
* If api
is null, it calls buildApi() which by default does nothing and is itself designed as an override hook.
*
* @param configProfile the configuration runtime profile
* @param servletPath the servlet path
* @return an Engine with an Api already set if one was supplied otherwise an empty Engine that will be configured via via Config/Wirer.
* @see #buildApi
*/
protected Engine buildEngine(String configProfile, String servletPath) {
System.out.println("TODO FIX ME: " + servletPath);
System.out.println("TODO FIX ME: " + configProfile);
Engine engine = new Engine();
engine.withConfigProfile(configProfile);
if (api == null)
api = buildApi(engine);
if (api != null) {
engine.withApi(api);
}
return engine;
}
/**
* Optional subclass override hook to supply your own custom wired up Api.
*
* If you don't set your api
via setApi()
and you don't override buildApi()
to supply an Api
* or otherwise wire your custom Api and Engine in an overridden buildEngine() method, you will need to define your Api in inversion.properties files for autowiring via Config/Wirer.
*
* @param engine the engine that will host the Api
* @return null unless you override this method to construct an Api.
* @see #buildEngine
*/
protected Api buildApi(Engine engine) {
System.out.println("You can override buildApi(engine) to wire up your api in code: " + engine);
return null;
}
protected void writeResponse(Response res, OutputStream outputStream) throws IOException {
// JSNode responseJson = new JSMap();
//
// responseJson.put("isBase64Encoded", false);
// responseJson.put("statusCode", res.getStatusCode());
// JSNode headers = new JSMap();
// responseJson.put("headers", headers);
//
// asdfasdf
// for (String key : res.getHeaders().keySet()) {
// List values = res.getHeaders().get(key);
// StringBuilder buff = new StringBuilder();
// for (int i = 0; i < values.size(); i++) {
// buff.append(values.get(i));
// if (i < values.size() - 1)
// buff.append(",");
// }
// headers.put(key, buff.toString());
// }
//
// String output = res.getOutput();
//
// responseJson.put("body", output);
// OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
// writer.write(responseJson.toString());
// writer.close();
}
public void debug(String msg) {
if (isDebug()) {
System.out.println(msg);
}
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public Engine getEngine() {
return engine;
}
public void setEngine(Engine engine) {
this.engine = engine;
}
public Api getApi() {
return api;
}
public void setApi(Api api) {
this.api = api;
}
}