Skip to content

Commit c163bfa

Browse files
feat: add WebSocketConnectionContext to expose connection metadata in APIGatewayV2WebSocketEvent
1 parent 319b771 commit c163bfa

4 files changed

Lines changed: 184 additions & 0 deletions

File tree

aws-lambda-java-events/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@
6060
* `SQSBatchResponse`
6161
* `SQSEvent`
6262

63+
### API Gateway WebSocket connection context
64+
65+
API Gateway manages the WebSocket connection. Lambda receives event payloads instead of a native Java WebSocket session.
66+
67+
For `APIGatewayV2WebSocketEvent`, use the request context's connection metadata:
68+
69+
```java
70+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext connection = event.getConnectionContext();
71+
if (connection != null) {
72+
String connectionId = connection.getConnectionId();
73+
String endpoint = connection.getManagementApiEndpoint(); // https://{domainName}/{stage}
74+
// endpoint is null when domainName/stage is missing or empty
75+
}
76+
```
77+
78+
You can pass this object through your handlers as a lightweight session-like context and use it with the API Gateway Management API to send messages.
79+
6380

6481
### Usage
6582

aws-lambda-java-events/RELEASE.CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### Unreleased
2+
- Add `WebSocketConnectionContext` helper accessors on `APIGatewayV2WebSocketEvent` to expose connection metadata as a session-like object
3+
14
### June 17, 2025
25
`3.16.0`:
36
- Add Schema metadata related attributes in KafkaEvent ([#548](https://github.com/aws/aws-lambda-java-libs/pull/548))

aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayV2WebSocketEvent.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,81 @@ public class APIGatewayV2WebSocketEvent implements Serializable, Cloneable {
1212

1313
private static final long serialVersionUID = 5695319264103347099L;
1414

15+
/**
16+
* Represents the API Gateway-managed WebSocket connection for a single client.
17+
* This is not a native server-side socket session.
18+
*/
19+
public static class WebSocketConnectionContext implements Serializable {
20+
21+
private static final long serialVersionUID = 9166276112534784030L;
22+
23+
private final String connectionId;
24+
private final String domainName;
25+
private final String stage;
26+
27+
public WebSocketConnectionContext(String connectionId, String domainName, String stage) {
28+
this.connectionId = connectionId;
29+
this.domainName = domainName;
30+
this.stage = stage;
31+
}
32+
33+
public String getConnectionId() {
34+
return connectionId;
35+
}
36+
37+
public String getDomainName() {
38+
return domainName;
39+
}
40+
41+
public String getStage() {
42+
return stage;
43+
}
44+
45+
/**
46+
* Builds the API Gateway Management API endpoint used for postToConnection calls.
47+
*/
48+
public String getManagementApiEndpoint() {
49+
if (isNullOrEmpty(domainName) || isNullOrEmpty(stage)) {
50+
return null;
51+
}
52+
return "https://" + domainName + "/" + stage;
53+
}
54+
55+
private static boolean isNullOrEmpty(String value) {
56+
return value == null || value.isEmpty();
57+
}
58+
59+
@Override
60+
public boolean equals(Object o) {
61+
if (this == o) return true;
62+
if (o == null || getClass() != o.getClass()) return false;
63+
64+
WebSocketConnectionContext that = (WebSocketConnectionContext) o;
65+
66+
if (!Objects.equals(connectionId, that.connectionId)) return false;
67+
if (!Objects.equals(domainName, that.domainName)) return false;
68+
return Objects.equals(stage, that.stage);
69+
}
70+
71+
@Override
72+
public int hashCode() {
73+
int result = connectionId != null ? connectionId.hashCode() : 0;
74+
result = 31 * result + (domainName != null ? domainName.hashCode() : 0);
75+
result = 31 * result + (stage != null ? stage.hashCode() : 0);
76+
return result;
77+
}
78+
79+
@Override
80+
public String toString() {
81+
final StringBuilder sb = new StringBuilder("WebSocketConnectionContext{");
82+
sb.append("connectionId='").append(connectionId).append('\'');
83+
sb.append(", domainName='").append(domainName).append('\'');
84+
sb.append(", stage='").append(stage).append('\'');
85+
sb.append('}');
86+
return sb.toString();
87+
}
88+
}
89+
1590
public static class RequestIdentity implements Serializable, Cloneable {
1691

1792
private static final long serialVersionUID = -3276649362684921217L;
@@ -415,6 +490,13 @@ public void setStatus(String status) {
415490
this.status = status;
416491
}
417492

493+
public WebSocketConnectionContext getConnectionContext() {
494+
if (connectionId == null) {
495+
return null;
496+
}
497+
return new WebSocketConnectionContext(connectionId, domainName, stage);
498+
}
499+
418500
@Override
419501
public int hashCode() {
420502
int hash = 3;
@@ -646,6 +728,13 @@ public void setRequestContext(RequestContext requestContext) {
646728
this.requestContext = requestContext;
647729
}
648730

731+
public WebSocketConnectionContext getConnectionContext() {
732+
if (requestContext == null) {
733+
return null;
734+
}
735+
return requestContext.getConnectionContext();
736+
}
737+
649738
public String getBody() {
650739
return body;
651740
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.amazonaws.services.lambda.runtime.events;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
import static org.junit.jupiter.api.Assertions.assertNotNull;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
9+
class APIGatewayV2WebSocketEventTest {
10+
11+
@Test
12+
void requestContextBuildsConnectionContext() {
13+
APIGatewayV2WebSocketEvent.RequestContext requestContext = new APIGatewayV2WebSocketEvent.RequestContext();
14+
requestContext.setConnectionId("conn-123");
15+
requestContext.setDomainName("abc.execute-api.us-east-1.amazonaws.com");
16+
requestContext.setStage("prod");
17+
18+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext connectionContext = requestContext.getConnectionContext();
19+
20+
assertNotNull(connectionContext);
21+
assertEquals("conn-123", connectionContext.getConnectionId());
22+
assertEquals("abc.execute-api.us-east-1.amazonaws.com", connectionContext.getDomainName());
23+
assertEquals("prod", connectionContext.getStage());
24+
assertEquals("https://abc.execute-api.us-east-1.amazonaws.com/prod", connectionContext.getManagementApiEndpoint());
25+
}
26+
27+
@Test
28+
void eventExposesConnectionContextFromRequestContext() {
29+
APIGatewayV2WebSocketEvent.RequestContext requestContext = new APIGatewayV2WebSocketEvent.RequestContext();
30+
requestContext.setConnectionId("conn-456");
31+
requestContext.setDomainName("xyz.execute-api.us-east-1.amazonaws.com");
32+
requestContext.setStage("dev");
33+
34+
APIGatewayV2WebSocketEvent event = new APIGatewayV2WebSocketEvent();
35+
event.setRequestContext(requestContext);
36+
37+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext connectionContext = event.getConnectionContext();
38+
39+
assertNotNull(connectionContext);
40+
assertEquals("conn-456", connectionContext.getConnectionId());
41+
assertEquals("https://xyz.execute-api.us-east-1.amazonaws.com/dev", connectionContext.getManagementApiEndpoint());
42+
}
43+
44+
@Test
45+
void connectionContextRequiresConnectionId() {
46+
APIGatewayV2WebSocketEvent.RequestContext requestContext = new APIGatewayV2WebSocketEvent.RequestContext();
47+
requestContext.setDomainName("abc.execute-api.us-east-1.amazonaws.com");
48+
requestContext.setStage("prod");
49+
50+
assertNull(requestContext.getConnectionContext());
51+
52+
APIGatewayV2WebSocketEvent event = new APIGatewayV2WebSocketEvent();
53+
assertNull(event.getConnectionContext());
54+
}
55+
56+
@Test
57+
void managementEndpointRequiresDomainAndStage() {
58+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext connectionContext =
59+
new APIGatewayV2WebSocketEvent.WebSocketConnectionContext("conn-789", null, "prod");
60+
61+
assertNull(connectionContext.getManagementApiEndpoint());
62+
}
63+
64+
@Test
65+
void managementEndpointRequiresNonEmptyDomainAndStage() {
66+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext emptyDomain =
67+
new APIGatewayV2WebSocketEvent.WebSocketConnectionContext("conn-111", "", "prod");
68+
APIGatewayV2WebSocketEvent.WebSocketConnectionContext emptyStage =
69+
new APIGatewayV2WebSocketEvent.WebSocketConnectionContext("conn-222", "abc.execute-api.us-east-1.amazonaws.com", "");
70+
71+
assertNull(emptyDomain.getManagementApiEndpoint());
72+
assertNull(emptyStage.getManagementApiEndpoint());
73+
}
74+
}
75+

0 commit comments

Comments
 (0)