com.amazonaws.services.lambda.runtime.api.client.AWSLambda Maven / Gradle / Ivy
Show all versions of aws-lambda-java-runtime-interface-client Show documentation
//
// AWSLambda.java
//
// Copyright (c) 2013 Amazon. All rights reserved.
//
package com.amazonaws.services.lambda.runtime.api.client;
import com.amazonaws.services.lambda.crac.Core;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.api.client.LambdaRequestHandler.UserFaultHandler;
import com.amazonaws.services.lambda.runtime.api.client.logging.FramedTelemetryLogSink;
import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
import com.amazonaws.services.lambda.runtime.api.client.logging.LogSink;
import com.amazonaws.services.lambda.runtime.api.client.logging.StdOutLogSink;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeClient;
import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream;
import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil;
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
import com.amazonaws.services.lambda.runtime.logging.LogLevel;
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory;
import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory;
import com.amazonaws.services.lambda.runtime.serialization.util.ReflectUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.net.URLClassLoader;
import java.security.Security;
import java.util.Properties;
/**
* The entrypoint of this class is {@link AWSLambda#startRuntime}. It performs two main tasks:
*
*
* 1. loads the user's handler.
*
* 2. enters the Lambda runtime loop which handles function invocations as defined in the Lambda Custom Runtime API.
*
*
* Once initialized, {@link AWSLambda#startRuntime} will halt only if an irrecoverable error occurs.
*/
public class AWSLambda {
private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore";
private static final String JAVA_SECURITY_PROPERTIES = "java.security.properties";
private static final String NETWORKADDRESS_CACHE_NEGATIVE_TTL_ENV_VAR = "AWS_LAMBDA_JAVA_NETWORKADDRESS_CACHE_NEGATIVE_TTL";
private static final String NETWORKADDRESS_CACHE_NEGATIVE_TTL_PROPERTY = "networkaddress.cache.negative.ttl";
private static final String DEFAULT_NEGATIVE_CACHE_TTL = "1";
// System property for Lambda tracing, see aws-xray-sdk-java/LambdaSegmentContext
// https://github.com/aws/aws-xray-sdk-java/blob/2f467e50db61abb2ed2bd630efc21bddeabd64d9/aws-xray-recorder-sdk-core/src/main/java/com/amazonaws/xray/contexts/LambdaSegmentContext.java#L39-L40
private static final String LAMBDA_TRACE_HEADER_PROP = "com.amazonaws.xray.traceHeader";
private static final String INIT_TYPE_SNAP_START = "snap-start";
private static final String AWS_LAMBDA_INITIALIZATION_TYPE = System.getenv(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_INITIALIZATION_TYPE);
static {
// Override the disabledAlgorithms setting to match configuration for openjdk8-u181.
// This is to keep DES ciphers around while we deploying security updates.
Security.setProperty(
"jdk.tls.disabledAlgorithms",
"SSLv3, RC4, MD5withRSA, DH keySize < 1024, EC keySize < 224, DES40_CBC, RC4_40, 3DES_EDE_CBC"
);
// Override the location of the trusted certificate authorities to be provided by the system.
// The ca-certificates package provides /etc/pki/java/cacerts which becomes the symlink destination
// of $java_home/lib/security/cacerts when java is installed in the chroot. Given that java is provided
// in /var/lang as opposed to installed in the chroot, this brings it closer.
if (System.getProperty(TRUST_STORE_PROPERTY) == null) {
final File systemCacerts = new File("/etc/pki/java/cacerts");
if (systemCacerts.exists() && systemCacerts.isFile()) {
System.setProperty(TRUST_STORE_PROPERTY, systemCacerts.getPath());
}
}
if (isNegativeCacheOverridable()) {
String ttlFromEnv = System.getenv(NETWORKADDRESS_CACHE_NEGATIVE_TTL_ENV_VAR);
String negativeCacheTtl = ttlFromEnv == null ? DEFAULT_NEGATIVE_CACHE_TTL : ttlFromEnv;
Security.setProperty(NETWORKADDRESS_CACHE_NEGATIVE_TTL_PROPERTY, negativeCacheTtl);
}
}
private static boolean isNegativeCacheOverridable() {
String securityPropertiesPath = System.getProperty(JAVA_SECURITY_PROPERTIES);
if (securityPropertiesPath == null) {
return true;
}
try (FileInputStream inputStream = new FileInputStream(securityPropertiesPath)) {
Properties secProps = new Properties();
secProps.load(inputStream);
return !secProps.containsKey(NETWORKADDRESS_CACHE_NEGATIVE_TTL_PROPERTY);
} catch (IOException e) {
return true;
}
}
private static LambdaRequestHandler findRequestHandler(final String handlerString, ClassLoader customerClassLoader) {
final HandlerInfo handlerInfo;
try {
handlerInfo = HandlerInfo.fromString(handlerString, customerClassLoader);
} catch (HandlerInfo.InvalidHandlerException e) {
UserFault userFault = UserFault.makeUserFault("Invalid handler: `" + handlerString + "'");
return new UserFaultHandler(userFault);
} catch (ClassNotFoundException e) {
return LambdaRequestHandler.classNotFound(e, HandlerInfo.className(handlerString));
} catch (NoClassDefFoundError e) {
return LambdaRequestHandler.initErrorHandler(e, HandlerInfo.className(handlerString));
} catch (Throwable t) {
throw UserFault.makeInitErrorUserFault(t, HandlerInfo.className(handlerString));
}
final LambdaRequestHandler requestHandler = EventHandlerLoader.loadEventHandler(handlerInfo);
// if loading the handler failed and the failure is fatal (for e.g. the constructor threw an exception)
// we want to report this as an init error rather than deferring to the first invoke.
if (requestHandler instanceof UserFaultHandler) {
UserFault userFault = ((UserFaultHandler) requestHandler).fault;
if (userFault.fatal) {
throw userFault;
}
}
return requestHandler;
}
public static void setupRuntimeLogger(LambdaLogger lambdaLogger)
throws ClassNotFoundException {
ReflectUtil.setStaticField(
Class.forName("com.amazonaws.services.lambda.runtime.LambdaRuntime"),
"logger",
true,
lambdaLogger
);
}
public static String getEnvOrExit(String envVariableName) {
String value = System.getenv(envVariableName);
if (value == null) {
System.err.println("Could not get environment variable " + envVariableName);
System.exit(-1);
}
return value;
}
protected static URLClassLoader customerClassLoader;
/**
* convert an integer into a FileDescriptor object using reflection to access private members.
*/
private static FileDescriptor intToFd(int fd) throws RuntimeException {
try {
Class clazz = FileDescriptor.class;
Constructor c = clazz.getDeclaredConstructor(Integer.TYPE);
c.setAccessible(true);
return c.newInstance(fd);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static LogSink createLogSink() {
final String fdStr = System.getenv("_LAMBDA_TELEMETRY_LOG_FD");
if (fdStr == null) {
return new StdOutLogSink();
}
try {
int fdInt = Integer.parseInt(fdStr);
FileDescriptor fd = intToFd(fdInt);
return new FramedTelemetryLogSink(fd);
} catch (Exception e) {
return new StdOutLogSink();
}
}
public static void main(String[] args) {
startRuntime(args[0]);
}
private static void startRuntime(String handler) {
try (LogSink logSink = createLogSink()) {
LambdaLogger logger = new LambdaContextLogger(
logSink,
LogLevel.fromString(LambdaEnvironment.LAMBDA_LOG_LEVEL),
LogFormat.fromString(LambdaEnvironment.LAMBDA_LOG_FORMAT)
);
startRuntime(handler, logger);
} catch (Throwable t) {
throw new Error(t);
}
}
private static void startRuntime(String handler, LambdaLogger lambdaLogger) throws Throwable {
UnsafeUtil.disableIllegalAccessWarning();
System.setOut(new PrintStream(new LambdaOutputStream(System.out), false, "UTF-8"));
System.setErr(new PrintStream(new LambdaOutputStream(System.err), false, "UTF-8"));
setupRuntimeLogger(lambdaLogger);
String runtimeApi = getEnvOrExit(ReservedRuntimeEnvironmentVariables.AWS_LAMBDA_RUNTIME_API);
LambdaRuntimeClient runtimeClient = new LambdaRuntimeClient(runtimeApi);
String taskRoot = System.getProperty("user.dir");
String libRoot = "/opt/java";
// Make system classloader the customer classloader's parent to ensure any aws-lambda-java-core classes
// are loaded from the system classloader.
customerClassLoader = new CustomerClassLoader(taskRoot, libRoot, ClassLoader.getSystemClassLoader());
Thread.currentThread().setContextClassLoader(customerClassLoader);
// Load the user's handler
LambdaRequestHandler requestHandler;
try {
requestHandler = findRequestHandler(handler, customerClassLoader);
} catch (UserFault userFault) {
lambdaLogger.log(userFault.reportableError());
reportInitError(new Failure(userFault), runtimeClient);
System.exit(1);
return;
}
if (INIT_TYPE_SNAP_START.equals(AWS_LAMBDA_INITIALIZATION_TYPE)) {
onInitComplete(runtimeClient, lambdaLogger);
}
boolean shouldExit = false;
while (!shouldExit) {
UserFault userFault = null;
InvocationRequest request = runtimeClient.waitForNextInvocation();
if (request.getXrayTraceId() != null) {
System.setProperty(LAMBDA_TRACE_HEADER_PROP, request.getXrayTraceId());
} else {
System.clearProperty(LAMBDA_TRACE_HEADER_PROP);
}
ByteArrayOutputStream payload;
try {
payload = requestHandler.call(request);
runtimeClient.postInvocationResponse(request.getId(), payload.toByteArray());
boolean ignored = Thread.interrupted(); // clear interrupted flag in case if it was set by user's code
} catch (UserFault f) {
userFault = f;
UserFault.filterStackTrace(f);
payload = new ByteArrayOutputStream(1024);
Failure failure = new Failure(f);
GsonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload);
shouldExit = f.fatal;
runtimeClient.postInvocationError(request.getId(), payload.toByteArray(), failure.getErrorType());
} catch (Throwable t) {
UserFault.filterStackTrace(t);
userFault = UserFault.makeUserFault(t);
payload = new ByteArrayOutputStream(1024);
Failure failure = new Failure(t);
GsonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload);
// These two categories of errors are considered fatal.
shouldExit = Failure.isInvokeFailureFatal(t);
runtimeClient.postInvocationError(request.getId(), payload.toByteArray(), failure.getErrorType(),
serializeAsXRayJson(t));
} finally {
if (userFault != null) {
lambdaLogger.log(userFault.reportableError());
}
}
}
}
static void onInitComplete(final LambdaRuntimeClient runtimeClient, final LambdaLogger lambdaLogger) throws IOException {
try {
Core.getGlobalContext().beforeCheckpoint(null);
// Blocking call to RAPID /restore/next API, will return after taking snapshot.
// This will also be the 'entrypoint' when resuming from snapshots.
runtimeClient.getRestoreNext();
} catch (Exception e1) {
logExceptionCloudWatch(lambdaLogger, e1);
reportInitError(new Failure(e1), runtimeClient);
System.exit(64);
}
try {
Core.getGlobalContext().afterRestore(null);
} catch (Exception restoreExc) {
logExceptionCloudWatch(lambdaLogger, restoreExc);
Failure errorPayload = new Failure(restoreExc);
reportRestoreError(errorPayload, runtimeClient);
System.exit(64);
}
}
private static void logExceptionCloudWatch(LambdaLogger lambdaLogger, Exception exc) {
UserFault.filterStackTrace(exc);
UserFault userFault = UserFault.makeUserFault(exc, true);
lambdaLogger.log(userFault.reportableError());
}
static void reportInitError(final Failure failure,
final LambdaRuntimeClient runtimeClient) throws IOException {
ByteArrayOutputStream payload = new ByteArrayOutputStream(1024);
JacksonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload);
runtimeClient.postInitError(payload.toByteArray(), failure.getErrorType());
}
static int reportRestoreError(final Failure failure,
final LambdaRuntimeClient runtimeClient) throws IOException {
ByteArrayOutputStream payload = new ByteArrayOutputStream(1024);
JacksonFactory.getInstance().getSerializer(Failure.class).toJson(failure, payload);
return runtimeClient.postRestoreError(payload.toByteArray(), failure.getErrorType());
}
private static PojoSerializer xRayErrorCauseSerializer;
/**
* @param throwable throwable to convert
* @return json as string expected by XRay's web console. On conversion failure, returns null.
*/
private static String serializeAsXRayJson(Throwable throwable) {
try {
final OutputStream outputStream = new ByteArrayOutputStream();
final XRayErrorCause cause = new XRayErrorCause(throwable);
if (xRayErrorCauseSerializer == null) {
xRayErrorCauseSerializer = JacksonFactory.getInstance().getSerializer(XRayErrorCause.class);
}
xRayErrorCauseSerializer.toJson(cause, outputStream);
return outputStream.toString();
} catch (Exception e) {
return null;
}
}
}