Skip to content

Commit f7be3bd

Browse files
authored
Merge branch 'main' into maxday/fix-throttle
2 parents 0cb5e63 + d343d78 commit f7be3bd

10 files changed

Lines changed: 392 additions & 1 deletion

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: dockerized-test
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
push:
8+
branches: [main]
9+
pull_request:
10+
branches: ['*']
11+
paths:
12+
- 'aws-lambda-java-core/**'
13+
- 'aws-lambda-java-serialization/**'
14+
- 'aws-lambda-java-runtime-interface-client/**'
15+
- '.github/workflows/dockerized-test.yml'
16+
workflow_dispatch:
17+
18+
jobs:
19+
dockerized-test:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v5
23+
24+
- name: Set up JDK
25+
uses: actions/setup-java@v5
26+
with:
27+
# The core/serialization/RIC builds require a JDK 8 toolchain
28+
# (maven-toolchains-plugin: [1.8,9)); 25 is the JVM Maven runs on.
29+
java-version: |
30+
8
31+
25
32+
distribution: corretto
33+
cache: maven
34+
35+
- name: Build core, serialization and the RIC under test
36+
run: |
37+
mvn -B -q install -DskipTests --file aws-lambda-java-core/pom.xml
38+
mvn -B -q install -DskipTests --file aws-lambda-java-serialization/pom.xml
39+
mvn -B -q clean install -DskipTests -DmultiArch=false \
40+
--file aws-lambda-java-runtime-interface-client/pom.xml
41+
# Stage core + serialization next to the RIC jar so the image build
42+
# can pick all three up from a single ./target folder.
43+
cp aws-lambda-java-core/target/aws-lambda-java-core-*.jar \
44+
aws-lambda-java-runtime-interface-client/target/
45+
cp aws-lambda-java-serialization/target/aws-lambda-java-serialization-*.jar \
46+
aws-lambda-java-runtime-interface-client/target/
47+
48+
- name: Compile the W3C task handler
49+
working-directory: aws-lambda-java-runtime-interface-client
50+
run: |
51+
javac --release 8 -cp "$(ls target/aws-lambda-java-core-*.jar)" \
52+
-d test/dockerized/tasks \
53+
test/dockerized/handler/W3CHandler.java
54+
55+
- name: Build the test image
56+
working-directory: aws-lambda-java-runtime-interface-client
57+
run: |
58+
docker build . \
59+
-t local/test \
60+
-f Dockerfile.test \
61+
--build-arg BASE_IMAGE=public.ecr.aws/lambda/java:25
62+
63+
- name: Run dockerized suites
64+
uses: aws/containerized-test-runner-for-aws-lambda@0863dd17b5fc19585250a2405c0f939a77b4f397 # main
65+
with:
66+
suiteFileArray: '["./aws-lambda-java-runtime-interface-client/test/dockerized/suites/*.json"]'
67+
dockerImageName: 'local/test'
68+
taskFolder: './aws-lambda-java-runtime-interface-client/test/dockerized/tasks'

‎aws-lambda-java-core/src/main/java/com/amazonaws/services/lambda/runtime/Context.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
package com.amazonaws.services.lambda.runtime;
44

5+
import java.util.Collections;
6+
import java.util.Map;
7+
58
/**
69
*
710
* The context object allows you to access useful information available within
@@ -119,4 +122,20 @@ default String getTenantId() {
119122
default String getXrayTraceId() {
120123
return null;
121124
}
125+
126+
/**
127+
* Returns the W3C trace-context fields associated with the request at invoke
128+
* time.
129+
* <p>
130+
* The returned map only ever contains the allowlisted W3C trace-context
131+
* keys ({@code traceparent}, {@code tracestate}, {@code baggage}) that were
132+
* carried on the invoke's client context. Any other key, and any allowlisted
133+
* key whose value is not a string, is dropped.
134+
* </p>
135+
*
136+
* @return an unmodifiable map of W3C trace-context fields; empty by default
137+
*/
138+
default Map<String, String> w3c() {
139+
return Collections.emptyMap();
140+
}
122141
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
ARG BASE_IMAGE=public.ecr.aws/lambda/java:25
5+
FROM $BASE_IMAGE
6+
7+
COPY target/aws-lambda-java-runtime-interface-client-*.jar /opt/ric/
8+
COPY target/aws-lambda-java-core-*.jar /opt/ric/
9+
COPY target/aws-lambda-java-serialization-*.jar /opt/ric/
10+
11+
ENTRYPOINT ["/bin/sh", "-c", \
12+
"exec /usr/local/bin/aws-lambda-rie java \
13+
-cp \"/opt/ric/*:/var/task\" \
14+
com.amazonaws.services.lambda.runtime.api.client.AWSLambda \"$@\"", \
15+
"sh"]
16+
CMD ["W3CHandler::getW3c"]

‎aws-lambda-java-runtime-interface-client/pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
<dependency>
6666
<groupId>com.amazonaws</groupId>
6767
<artifactId>aws-lambda-java-core</artifactId>
68-
<version>1.4.0</version>
68+
<version>1.4.1-SNAPSHOT</version>
6969
</dependency>
7070
<dependency>
7171
<groupId>com.amazonaws</groupId>

‎aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaClientContext.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class LambdaClientContext implements ClientContext {
1414
private LambdaClientContextClient client;
1515
private Map<String, String> custom;
1616
private Map<String, String> env;
17+
private Object w3c;
1718

1819
public Client getClient() {
1920
return client;
@@ -26,4 +27,10 @@ public Map<String, String> getCustom() {
2627
public Map<String, String> getEnvironment() {
2728
return env;
2829
}
30+
31+
Object readAndStripW3c() {
32+
Object raw = this.w3c;
33+
this.w3c = null;
34+
return raw;
35+
}
2936
}

‎aws-lambda-java-runtime-interface-client/src/main/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContext.java‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@
99
import com.amazonaws.services.lambda.runtime.CognitoIdentity;
1010
import com.amazonaws.services.lambda.runtime.Context;
1111
import com.amazonaws.services.lambda.runtime.LambdaLogger;
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.LinkedHashMap;
15+
import java.util.List;
16+
import java.util.Map;
1217

1318
public class LambdaContext implements Context {
1419

20+
/**
21+
* Allowlist of W3C trace-context fields that may be surfaced through
22+
* {@link #w3c()}. Any other key carried on {@code clientContext.w3c} is
23+
* ignored, and any allowlisted key whose value is not a string is dropped.
24+
*/
25+
public static final List<String> W3C_ALLOWED_FIELDS =
26+
Collections.unmodifiableList(Arrays.asList("traceparent", "tracestate", "baggage"));
27+
1528
private int memoryLimit;
1629
private final String awsRequestId;
1730
private final String logGroupName;
@@ -25,6 +38,7 @@ public class LambdaContext implements Context {
2538
private final String tenantId;
2639
private final String xrayTraceId;
2740
private final LambdaLogger logger;
41+
private final Map<String, String> w3cFields;
2842

2943
public LambdaContext(
3044
int memoryLimit,
@@ -52,6 +66,7 @@ public LambdaContext(
5266
this.invokedFunctionArn = invokedFunctionArn;
5367
this.tenantId = tenantId;
5468
this.xrayTraceId = xrayTraceId;
69+
this.w3cFields = extractAndStripW3c(clientContext);
5570
this.logger = com.amazonaws.services.lambda.runtime.LambdaRuntime.getLogger();
5671
}
5772

@@ -105,6 +120,32 @@ public String getXrayTraceId() {
105120
return xrayTraceId;
106121
}
107122

123+
@Override
124+
public Map<String, String> w3c() {
125+
return w3cFields;
126+
}
127+
128+
private static Map<String, String> extractAndStripW3c(ClientContext clientContext) {
129+
if (!(clientContext instanceof LambdaClientContext)) {
130+
return Collections.emptyMap();
131+
}
132+
133+
Object rawW3c = ((LambdaClientContext) clientContext).readAndStripW3c();
134+
if (!(rawW3c instanceof Map)) {
135+
return Collections.emptyMap();
136+
}
137+
138+
Map<?, ?> source = (Map<?, ?>) rawW3c;
139+
Map<String, String> fields = new LinkedHashMap<>();
140+
for (String key : W3C_ALLOWED_FIELDS) {
141+
Object value = source.get(key);
142+
if (value instanceof String) {
143+
fields.put(key, (String) value);
144+
}
145+
}
146+
return Collections.unmodifiableMap(fields);
147+
}
148+
108149
public LambdaLogger getLogger() {
109150
return logger;
110151
}

‎aws-lambda-java-runtime-interface-client/src/test/java/com/amazonaws/services/lambda/runtime/api/client/api/LambdaContextTest.java‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33
package com.amazonaws.services.lambda.runtime.api.client.api;
44

5+
import com.amazonaws.services.lambda.runtime.ClientContext;
6+
import com.amazonaws.services.lambda.runtime.Context;
7+
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
8+
import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory;
59
import org.junit.jupiter.api.Test;
610

11+
import java.util.Map;
12+
713
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertFalse;
15+
import static org.junit.jupiter.api.Assertions.assertThrows;
816
import static org.junit.jupiter.api.Assertions.assertTrue;
917

1018
public class LambdaContextTest {
@@ -58,4 +66,97 @@ private LambdaContext createContextWithDeadline(long deadlineTimeInMs) {
5866
return new LambdaContext(MEMORY_LIMIT, deadlineTimeInMs, REQUEST_ID, LOG_GROUP_NAME, LOG_STREAM_NAME,
5967
FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID, X_RAY_TRACE_ID, CLIENT_CONTEXT);
6068
}
69+
70+
private LambdaContext w3cContext(ClientContext clientContext) {
71+
return new LambdaContext(MEMORY_LIMIT, System.currentTimeMillis() + 1000, REQUEST_ID, LOG_GROUP_NAME,
72+
LOG_STREAM_NAME, FUNCTION_NAME, IDENTITY, FUNCTION_VERSION, INVOKED_FUNCTION_ARN, TENANT_ID,
73+
X_RAY_TRACE_ID, clientContext);
74+
}
75+
76+
private LambdaClientContext clientContextFromJson(String json) {
77+
PojoSerializer<LambdaClientContext> serializer =
78+
GsonFactory.getInstance().getSerializer(LambdaClientContext.class);
79+
return serializer.fromJson(json);
80+
}
81+
82+
@Test
83+
public void w3c_returnsEmptyWhenClientContextIsNull() {
84+
assertTrue(w3cContext(null).w3c().isEmpty());
85+
}
86+
87+
@Test
88+
public void w3c_returnsEmptyWhenNoW3cKey() {
89+
LambdaClientContext cc = clientContextFromJson("{\"custom\": {\"value\": \"test\"}}");
90+
assertTrue(w3cContext(cc).w3c().isEmpty());
91+
}
92+
93+
@Test
94+
public void w3c_returnsAllAllowlistedFields() {
95+
LambdaClientContext cc = clientContextFromJson(
96+
"{\"w3c\": {" +
97+
"\"traceparent\": \"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\"," +
98+
"\"tracestate\": \"rojo=00f067aa0ba902b7\"," +
99+
"\"baggage\": \"userId=alice\"}}");
100+
101+
Map<String, String> w3c = w3cContext(cc).w3c();
102+
103+
assertEquals(3, w3c.size());
104+
assertEquals("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", w3c.get("traceparent"));
105+
assertEquals("rojo=00f067aa0ba902b7", w3c.get("tracestate"));
106+
assertEquals("userId=alice", w3c.get("baggage"));
107+
}
108+
109+
@Test
110+
public void w3c_dropsNonAllowlistedKeysAndNonStringValues() {
111+
LambdaClientContext cc = clientContextFromJson(
112+
"{\"w3c\": {" +
113+
"\"baggage\": \"keep=me\"," +
114+
"\"unknownField\": \"drop-me\"," +
115+
"\"traceparent\": 42," +
116+
"\"tracestate\": null}}");
117+
118+
Map<String, String> w3c = w3cContext(cc).w3c();
119+
120+
assertEquals(1, w3c.size());
121+
assertEquals("keep=me", w3c.get("baggage"));
122+
}
123+
124+
@Test
125+
public void w3c_treatsNonObjectPayloadAsEmpty() {
126+
assertTrue(w3cContext(clientContextFromJson("{\"w3c\": \"not-an-object\"}")).w3c().isEmpty());
127+
assertTrue(w3cContext(clientContextFromJson("{\"w3c\": [\"baggage=abc\"]}")).w3c().isEmpty());
128+
}
129+
130+
@Test
131+
public void w3c_isStrippedFromClientContextAndIsUnmodifiable() {
132+
LambdaClientContext cc = clientContextFromJson(
133+
"{\"custom\": {\"value\": \"test\"}, \"w3c\": {\"baggage\": \"userId=alice\"}}");
134+
135+
LambdaContext context = w3cContext(cc);
136+
137+
assertEquals("userId=alice", context.w3c().get("baggage"));
138+
assertThrows(UnsupportedOperationException.class, () -> context.w3c().put("x", "y"));
139+
assertEquals("test", cc.getCustom().get("value"));
140+
assertTrue(w3cContext(cc).w3c().isEmpty());
141+
}
142+
143+
@Test
144+
public void w3c_defaultsToEmptyOnBareContextInterface() {
145+
Context bare = new Context() {
146+
public String getAwsRequestId() { return null; }
147+
public String getLogGroupName() { return null; }
148+
public String getLogStreamName() { return null; }
149+
public String getFunctionName() { return null; }
150+
public String getFunctionVersion() { return null; }
151+
public String getInvokedFunctionArn() { return null; }
152+
public com.amazonaws.services.lambda.runtime.CognitoIdentity getIdentity() { return null; }
153+
public ClientContext getClientContext() { return null; }
154+
public int getRemainingTimeInMillis() { return 0; }
155+
public int getMemoryLimitInMB() { return 0; }
156+
public com.amazonaws.services.lambda.runtime.LambdaLogger getLogger() { return null; }
157+
};
158+
159+
assertTrue(bare.w3c().isEmpty());
160+
assertFalse(bare.w3c() == null);
161+
}
61162
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import com.amazonaws.services.lambda.runtime.ClientContext;
7+
import com.amazonaws.services.lambda.runtime.Context;
8+
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
11+
12+
13+
public class W3CHandler {
14+
15+
public Map<String, String> getW3c(Map<String, Object> event, Context context) {
16+
return context.w3c();
17+
}
18+
19+
public Map<String, Object> getW3cAndCustom(Map<String, Object> event, Context context) {
20+
Map<String, Object> result = new LinkedHashMap<>();
21+
result.put("w3c", context.w3c());
22+
ClientContext clientContext = context.getClientContext();
23+
result.put("custom", clientContext == null ? null : clientContext.getCustom());
24+
return result;
25+
}
26+
}

0 commit comments

Comments
 (0)