Websocket Chat with Lambda (but all in Java)
I don't know if I am not able to perform search on Internet (I doubt it) but every examples of websocket chat created with Lambda was all created using Node.js. And I can cheat on Java, that's my love.
So I decided to transform the usual chat into a Java project, by of course performing some changes in order to be easy to check if it works.
THE IDEA
The idea behind the project is simple: a simple WebSocket API exposed with api gateway with 4 different route and a unique Lambda who is in charge to serve each request.
The change I made is about the name registration: instead of just propagate the message to other connected users, I want to have the way to register a name for chat users so every time a message is published, the receiver can see who sent the message.
As I said in other project, this is to be intended for testing purpose, so there are really infinite ways to do have the same behavior maybe in a better way.
THE PROJECT
The entire project is based on cloud formation, where I have declared:
- the gateway
- the stage
- and all the routes
SimpleWebSocket:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: !Sub ${AWS::StackName}-SimpleWebSocket
ProtocolType: WEBSOCKET
RouteSelectionExpression: "$request.body.action"
ApiKeySelectionExpression: $request.header.x-api-key
SimpleWebSocketStage:
Type: AWS::ApiGatewayV2::Stage
Properties:
ApiId: !Ref SimpleWebSocket
AutoDeploy: true
StageName: production
ConnectRoute:
Type: AWS::ApiGatewayV2::Route
Properties:
ApiId: !Ref SimpleWebSocket
RouteKey: $connect
AuthorizationType: NONE
OperationName: ConnectRoute
Target: !Join
- '/'
- - 'integrations'
- !Ref ConnectInteg
ConnectInteg:
Type: AWS::ApiGatewayV2::Integration
Properties:
ApiId: !Ref SimpleWebSocket
Description: Connect Integration
IntegrationType: AWS_PROXY
IntegrationUri:
Fn::Sub:
arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SimpleWebSocketFunction.Arn}/invocations
SimpleWebSocketFunctionPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt SimpleWebSocketFunction.Arn
Principal: apigateway.amazonaws.com
THE TABLE
- the key is the connectionId received during the connect routing
- the second attribute is the name
- the action, a string representation. In this case the value is of course sendmessage. This is the field that drives the routing
- the detail, which is an element of type Action composed of
- the type, which is an ActionType element, an enum that can have two value: ADD_MESSAGE and SET_NAME
- the value which is a string
- the content which is a string representing the message
- the sender which is a SendInfo element made by
- connectionId - the receiver
- name - the name of the sender
else if (routeKey.equals(ROUTE_KEY_SEND_MESSAGE)){
try {
Action detail = new ObjectMapper().readValue(event.getBody(), MessageReceived.class).detail();
log.debug("Received send message with type {}", detail.type());
if (detail.type() == ActionType.SET_NAME){
log.debug("Updating {} with name {}", connectionId, detail.value());
dynamoDbClient.updateItem(new UpdateItem(connectionId, detail.value()).buildUpdateItem());
}
else if (detail.type() == ActionType.ADD_MESSAGE){
log.debug("Send message {} to others", detail.value());
SendInfo sender = this.fromMapAttributeValue(dynamoDbClient
.getItem(new ItemKeyBuilder(connectionId).buildGetItem()).item());
log.debug("Message is sent by {}", sender.name());
dynamoDbClient.scan(buildScanRequest()).items().stream()
.map(this::fromMapAttributeValue).filter(s -> !s.connectionId().equals(connectionId))
.map(senderFromDb -> new MessageToSend(detail.value(),
new SendInfo(senderFromDb.connectionId(), sender.name())))
.map(messageSent -> buildPostToConnectionRequest(messageSent))
.forEach(apiGatewayManagementApiClient::postToConnection);
}
} catch(Exception e){
log.error("Unable to send messages " + e.getMessage(), e);
sendMessageToMyself(connectionId, apiGatewayManagementApiClient, "Error sending message");
}
Commenti
Posta un commento