Skip to content

Commit

Permalink
Support AWS Lambda Function URLs (#598)
Browse files Browse the repository at this point in the history
* Support Lambda Http APIs Function URLS

* Refactoring

* Remove outPrintLn

* Remove unused imports

* - Local Session not working on AWS Lambda Http function.
-  Added UnitTests
- Bump SDK version

* Remove comment

* Remove unneeded code

* Address @iroqueta review
  • Loading branch information
ggallotti authored Sep 5, 2022
1 parent d42bbca commit 17492bb
Show file tree
Hide file tree
Showing 15 changed files with 501 additions and 81 deletions.
1 change: 1 addition & 0 deletions gxawsserverless/GXApplicationClasses.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
com.gx.serverless.test.test_services_rest
com.gx.serverless.test.receivenumber_services_rest
com.gx.serverless.test.sessionset_services_rest
com.genexus.serverless.proxy.test.jersey.EchoJerseyResource
2 changes: 1 addition & 1 deletion gxawsserverless/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<dependency>
<groupId>com.amazonaws.serverless</groupId>
<artifactId>aws-serverless-java-container-jersey</artifactId>
<version>1.6</version>
<version>1.8.2</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
package com.genexus.cloud.serverless.aws;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Application;

import com.amazonaws.serverless.proxy.RequestReader;
import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse;
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest;
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;
import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext;
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
import com.genexus.cloud.serverless.aws.handler.AwsGxServletResponse;
import com.genexus.specific.java.Connect;
import com.genexus.specific.java.LogManager;
import com.genexus.webpanels.GXWebObjectStub;
import org.glassfish.jersey.server.ResourceConfig;

import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.genexus.ApplicationContext;
import com.genexus.cloud.serverless.aws.handler.AwsGxServletResponse;
import com.genexus.cloud.serverless.aws.handler.LambdaApplicationHelper;
import com.genexus.diagnostics.core.ILogger;
import com.genexus.util.IniFile;
import com.genexus.webpanels.*;
import com.genexus.specific.java.LogManager;
import com.genexus.webpanels.GXOAuthAccessToken;
import com.genexus.webpanels.GXOAuthLogout;
import com.genexus.webpanels.GXOAuthUserInfo;
import com.genexus.webpanels.GXWebObjectStub;
import org.glassfish.jersey.server.ResourceConfig;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter;

public class LambdaHandler implements RequestHandler<AwsProxyRequest, AwsProxyResponse> {
private static ILogger logger = null;
public static JerseyLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler = null;
private static ResourceConfig jerseyApplication = null;
private static final String BASE_REST_PATH = "/rest/";
private static final String GX_APPLICATION_CLASS = "GXApplication";

public LambdaHandler() throws Exception {
if (LambdaHandler.jerseyApplication == null) {
JerseyLambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8");
LambdaHandler.jerseyApplication = ResourceConfig.forApplication(initialize());
logger = LogManager.initialize(".", LambdaHandler.class);
LambdaHandler.jerseyApplication = ResourceConfig.forApplication(LambdaApplicationHelper.initialize());
if (jerseyApplication.getClasses().size() == 0) {
String errMsg = "No endpoints found for this application";
logger.error(errMsg);
throw new Exception(errMsg);
logger.error("No HTTP endpoints found for this application");
}
LambdaHandler.handler = JerseyLambdaContainerHandler.getAwsProxyHandler(LambdaHandler.jerseyApplication);
}
Expand All @@ -61,7 +55,7 @@ public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context c
dumpRequest(awsProxyRequest);

logger.debug("Before handle Request");

awsProxyRequest.setPath(path.replace(BASE_REST_PATH, "/"));
AwsProxyResponse response = this.handler.proxy(awsProxyRequest, context);

Expand All @@ -70,7 +64,7 @@ public AwsProxyResponse handleRequest(AwsProxyRequest awsProxyRequest, Context c
awsProxyRequest.setPath(path);
logger.debug("Trying servlet request: " + path);
AwsGxServletResponse servletResponse = handleServletRequest(awsProxyRequest, context);
if (servletResponse.wasHandled()){
if (servletResponse.wasHandled()) {
response = servletResponse.getAwsProxyResponse();
}
}
Expand Down Expand Up @@ -166,27 +160,8 @@ private GXWebObjectStub resolveServlet(AwsProxyRequest awsProxyRequest) {
return handler;
}

private static Application initialize() throws Exception {
logger = LogManager.initialize(".", LambdaHandler.class);
Connect.init();
IniFile config = com.genexus.ConfigFileFinder.getConfigFile(null, "client.cfg", null);
String className = config.getProperty("Client", "PACKAGE", null);
Class<?> cls;
try {
cls = Class.forName(className.isEmpty() ? GX_APPLICATION_CLASS: String.format("%s.%s", className, GX_APPLICATION_CLASS));
Application app = (Application) cls.getDeclaredConstructor().newInstance();
ApplicationContext appContext = ApplicationContext.getInstance();
appContext.setServletEngine(true);
appContext.setServletEngineDefaultPath("");
com.genexus.Application.init(cls);
return app;
} catch (Exception e) {
logger.error("Failed to initialize App", e);
throw e;
}
}

private void dumpRequest(AwsProxyRequest awsProxyRequest){
private void dumpRequest(AwsProxyRequest awsProxyRequest) {
String lineSeparator = System.lineSeparator();
String reqData = String.format("Path: %s", awsProxyRequest.getPath()) + lineSeparator;
reqData += String.format("Method: %s", awsProxyRequest.getHttpMethod()) + lineSeparator;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.genexus.cloud.serverless.aws.handler;

import com.genexus.ApplicationContext;
import com.genexus.specific.java.Connect;
import com.genexus.util.IniFile;

import javax.ws.rs.core.Application;

public class LambdaApplicationHelper {
private static final String GX_APPLICATION_CLASS = "GXApplication";
private static final String GX_APPLICATION_CONFIG = "client.cfg";

public static Application initialize() throws Exception {
Connect.init();
IniFile config = com.genexus.ConfigFileFinder.getConfigFile(null, GX_APPLICATION_CONFIG, null);
String className = config.getProperty("Client", "PACKAGE", null);
Class<?> cls = Class.forName(className.isEmpty() ? GX_APPLICATION_CLASS : String.format("%s.%s", className, GX_APPLICATION_CLASS));
Application app = (Application) cls.getDeclaredConstructor().newInstance();
ApplicationContext appContext = ApplicationContext.getInstance();
appContext.setServletEngine(true);
appContext.setServletEngineDefaultPath("");
com.genexus.Application.init(cls);
return app;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.genexus.cloud.serverless.aws.handler;

import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.genexus.cloud.serverless.aws.LambdaHandler;
import com.genexus.cloud.serverless.aws.handler.internal.GxJerseyLambdaContainerHandlerFactory;
import com.genexus.diagnostics.core.ILogger;
import com.genexus.specific.java.LogManager;
import org.glassfish.jersey.server.ResourceConfig;

import java.util.Map;

public class LambdaHttpApiHandler implements RequestHandler<HttpApiV2ProxyRequest, AwsProxyResponse> {
private static final String BASE_REST_PATH = "/rest/";
public static JerseyLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> handler = null;
private static ILogger logger = null;
private static ResourceConfig jerseyApplication = null;

public LambdaHttpApiHandler() throws Exception {
if (LambdaHttpApiHandler.jerseyApplication == null) {
JerseyLambdaContainerHandler.getContainerConfig().setDefaultContentCharset("UTF-8");
logger = LogManager.initialize(".", LambdaHandler.class);
LambdaHttpApiHandler.jerseyApplication = ResourceConfig.forApplication(LambdaApplicationHelper.initialize());

if (jerseyApplication.getClasses().size() == 0) {
logger.error("No HTTP endpoints found for this application");
}

handler = GxJerseyLambdaContainerHandlerFactory.getHttpApiV2ProxyHandler(LambdaHttpApiHandler.jerseyApplication);
}
}

@Override
public AwsProxyResponse handleRequest(HttpApiV2ProxyRequest awsProxyRequest, Context context) {
if (logger.isDebugEnabled()) {
dumpRequest(awsProxyRequest);
}

String path = awsProxyRequest.getRawPath();
prepareRequest(awsProxyRequest);

logger.debug("Before handle Request");

awsProxyRequest.setRawPath(path.replace(BASE_REST_PATH, "/"));
AwsProxyResponse response = handler.proxy(awsProxyRequest, context);

int statusCode = response.getStatusCode();
logger.debug("After handle Request - Status Code: " + statusCode);

if (statusCode >= 404 && statusCode <= 599) {
logger.warn(String.format("Request could not be handled (%d): %s", response.getStatusCode(), path));
}
return response;
}

private void prepareRequest(HttpApiV2ProxyRequest awsProxyRequest) {
Map<String, String> headers = awsProxyRequest.getHeaders();

if (headers == null) {
return;
}

// In Jersey lambda context, the Referer Header has a special meaning. So we copy it to another Header.
String referer = headers.get("Referer");
if (referer != null && !referer.isEmpty()) {
headers.put("GX-Referer", referer);
}
}

private void dumpRequest(HttpApiV2ProxyRequest awsProxyRequest) {
String lineSeparator = System.lineSeparator();
String reqData = String.format("Path: %s", awsProxyRequest.getRawPath()) + lineSeparator;
reqData += String.format("Method: %s", awsProxyRequest.getRequestContext().getHttp().getMethod()) + lineSeparator;
reqData += String.format("QueryString: %s", awsProxyRequest.getRawQueryString()) + lineSeparator;
reqData += String.format("Body: %sn", awsProxyRequest.getBody()) + lineSeparator;
logger.debug(reqData);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.genexus.cloud.serverless.aws.handler.internal;

import com.amazonaws.serverless.exceptions.InvalidRequestEventException;
import com.amazonaws.serverless.proxy.RequestReader;
import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpApiV2ProxyHttpServletRequest;
import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext;
import com.amazonaws.serverless.proxy.model.ContainerConfig;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;
import com.amazonaws.services.lambda.runtime.Context;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.SecurityContext;

public class GxAwsHttpApiV2HttpServletRequestReader extends RequestReader<HttpApiV2ProxyRequest, HttpServletRequest> {
static final String INVALID_REQUEST_ERROR = "The incoming event is not a valid HTTP API v2 proxy request";

@Override
public HttpServletRequest readRequest(HttpApiV2ProxyRequest request, SecurityContext securityContext, Context lambdaContext, ContainerConfig config) throws InvalidRequestEventException {
if (request.getRequestContext() == null || request.getRequestContext().getHttp().getMethod() == null || request.getRequestContext().getHttp().getMethod().equals("")) {
throw new InvalidRequestEventException(INVALID_REQUEST_ERROR);
}

// clean out the request path based on the container config
request.setRawPath(stripBasePath(request.getRawPath(), config));

AwsHttpApiV2ProxyHttpServletRequest servletRequest = new AwsHttpApiV2ProxyHttpServletRequest(request, lambdaContext, securityContext, config);

AwsProxyRequestContext rContext = new AwsProxyRequestContext();
rContext.setRequestId(request.getRequestContext().getRequestId());
servletRequest.setAttribute(API_GATEWAY_CONTEXT_PROPERTY, rContext);
servletRequest.setAttribute(HTTP_API_CONTEXT_PROPERTY, request.getRequestContext());
servletRequest.setAttribute(HTTP_API_STAGE_VARS_PROPERTY, request.getStageVariables());
servletRequest.setAttribute(HTTP_API_EVENT_PROPERTY, request);
servletRequest.setAttribute(LAMBDA_CONTEXT_PROPERTY, lambdaContext);
servletRequest.setAttribute(JAX_SECURITY_CONTEXT_PROPERTY, securityContext);

return servletRequest;
}

@Override
protected Class<? extends HttpApiV2ProxyRequest> getRequestClass() {
return HttpApiV2ProxyRequest.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.genexus.cloud.serverless.aws.handler.internal;

import com.amazonaws.serverless.proxy.*;
import com.amazonaws.serverless.proxy.internal.servlet.*;
import com.amazonaws.serverless.proxy.jersey.JerseyLambdaContainerHandler;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest;

import javax.ws.rs.core.Application;

public class GxJerseyLambdaContainerHandlerFactory {
public static JerseyLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> getHttpApiV2ProxyHandler(Application jaxRsApplication) {
JerseyLambdaContainerHandler<HttpApiV2ProxyRequest, AwsProxyResponse> newHandler = new JerseyLambdaContainerHandler<>(
HttpApiV2ProxyRequest.class,
AwsProxyResponse.class,
new GxAwsHttpApiV2HttpServletRequestReader(),
new AwsProxyHttpServletResponseWriter(true),
new AwsHttpApiV2SecurityContextWriter(),
new AwsProxyExceptionHandler(),
jaxRsApplication);
newHandler.initialize();
return newHandler;
}
}
Loading

0 comments on commit 17492bb

Please sign in to comment.