From 90a3af70ddf872a5a9b6370e0c759b786626d55a Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Wed, 17 Apr 2024 05:18:10 +0900 Subject: [PATCH] Handle disconnect route of Websocket (#548) * Handle disconnect route of Websocket * Revert MessageID type change * Revert field type change * revert MessageID to interface{} --------- Co-authored-by: Bryan Moffatt --- events/apigw.go | 88 ++++++++++--------- events/apigw_test.go | 46 ++++++++++ .../apigw-websocket-request-disconnect.json | 35 ++++++++ .../apigw-websocket-request-send-message.json | 23 +++++ events/testdata/apigw-websocket-request.json | 4 +- 5 files changed, 151 insertions(+), 45 deletions(-) create mode 100644 events/testdata/apigw-websocket-request-disconnect.json create mode 100644 events/testdata/apigw-websocket-request-send-message.json diff --git a/events/apigw.go b/events/apigw.go index 04ac73f6..7999876c 100644 --- a/events/apigw.go +++ b/events/apigw.go @@ -133,63 +133,65 @@ type APIGatewayV2HTTPResponse struct { // APIGatewayRequestIdentity contains identity information for the request caller. type APIGatewayRequestIdentity struct { - CognitoIdentityPoolID string `json:"cognitoIdentityPoolId"` - AccountID string `json:"accountId"` - CognitoIdentityID string `json:"cognitoIdentityId"` - Caller string `json:"caller"` - APIKey string `json:"apiKey"` - APIKeyID string `json:"apiKeyId"` - AccessKey string `json:"accessKey"` + CognitoIdentityPoolID string `json:"cognitoIdentityPoolId,omitempty"` + AccountID string `json:"accountId,omitempty"` + CognitoIdentityID string `json:"cognitoIdentityId,omitempty"` + Caller string `json:"caller,omitempty"` + APIKey string `json:"apiKey,omitempty"` + APIKeyID string `json:"apiKeyId,omitempty"` + AccessKey string `json:"accessKey,omitempty"` SourceIP string `json:"sourceIp"` - CognitoAuthenticationType string `json:"cognitoAuthenticationType"` - CognitoAuthenticationProvider string `json:"cognitoAuthenticationProvider"` - UserArn string `json:"userArn"` //nolint: stylecheck + CognitoAuthenticationType string `json:"cognitoAuthenticationType,omitempty"` + CognitoAuthenticationProvider string `json:"cognitoAuthenticationProvider,omitempty"` + UserArn string `json:"userArn,omitempty"` //nolint: stylecheck UserAgent string `json:"userAgent"` - User string `json:"user"` + User string `json:"user,omitempty"` } // APIGatewayWebsocketProxyRequest contains data coming from the API Gateway proxy type APIGatewayWebsocketProxyRequest struct { - Resource string `json:"resource"` // The resource path defined in API Gateway - Path string `json:"path"` // The url path for the caller + Resource string `json:"resource,omitempty"` // The resource path defined in API Gateway + Path string `json:"path,omitempty"` // The url path for the caller HTTPMethod string `json:"httpMethod,omitempty"` - Headers map[string]string `json:"headers"` - MultiValueHeaders map[string][]string `json:"multiValueHeaders"` - QueryStringParameters map[string]string `json:"queryStringParameters"` - MultiValueQueryStringParameters map[string][]string `json:"multiValueQueryStringParameters"` - PathParameters map[string]string `json:"pathParameters"` - StageVariables map[string]string `json:"stageVariables"` + Headers map[string]string `json:"headers,omitempty"` + MultiValueHeaders map[string][]string `json:"multiValueHeaders,omitempty"` + QueryStringParameters map[string]string `json:"queryStringParameters,omitempty"` + MultiValueQueryStringParameters map[string][]string `json:"multiValueQueryStringParameters,omitempty"` + PathParameters map[string]string `json:"pathParameters,omitempty"` + StageVariables map[string]string `json:"stageVariables,omitempty"` RequestContext APIGatewayWebsocketProxyRequestContext `json:"requestContext"` - Body string `json:"body"` - IsBase64Encoded bool `json:"isBase64Encoded,omitempty"` + Body string `json:"body,omitempty"` + IsBase64Encoded bool `json:"isBase64Encoded"` } // APIGatewayWebsocketProxyRequestContext contains the information to identify // the AWS account and resources invoking the Lambda function. It also includes // Cognito identity information for the caller. type APIGatewayWebsocketProxyRequestContext struct { - AccountID string `json:"accountId"` - ResourceID string `json:"resourceId"` - Stage string `json:"stage"` - RequestID string `json:"requestId"` - Identity APIGatewayRequestIdentity `json:"identity"` - ResourcePath string `json:"resourcePath"` - Authorizer interface{} `json:"authorizer"` - HTTPMethod string `json:"httpMethod"` - APIID string `json:"apiId"` // The API Gateway rest API Id - ConnectedAt int64 `json:"connectedAt"` - ConnectionID string `json:"connectionId"` - DomainName string `json:"domainName"` - Error string `json:"error"` - EventType string `json:"eventType"` - ExtendedRequestID string `json:"extendedRequestId"` - IntegrationLatency string `json:"integrationLatency"` - MessageDirection string `json:"messageDirection"` - MessageID interface{} `json:"messageId"` - RequestTime string `json:"requestTime"` - RequestTimeEpoch int64 `json:"requestTimeEpoch"` - RouteKey string `json:"routeKey"` - Status string `json:"status"` + AccountID string `json:"accountId,omitempty"` + ResourceID string `json:"resourceId,omitempty"` + Stage string `json:"stage"` + RequestID string `json:"requestId"` + Identity APIGatewayRequestIdentity `json:"identity"` + ResourcePath string `json:"resourcePath,omitempty"` + Authorizer interface{} `json:"authorizer,omitempty"` + HTTPMethod string `json:"httpMethod,omitempty"` + APIID string `json:"apiId"` // The API Gateway rest API Id + ConnectedAt int64 `json:"connectedAt"` + ConnectionID string `json:"connectionId"` + DomainName string `json:"domainName"` + Error string `json:"error,omitempty"` + EventType string `json:"eventType"` + ExtendedRequestID string `json:"extendedRequestId"` + IntegrationLatency string `json:"integrationLatency,omitempty"` + MessageDirection string `json:"messageDirection"` + MessageID interface{} `json:"messageId,omitempty"` + RequestTime string `json:"requestTime"` + RequestTimeEpoch int64 `json:"requestTimeEpoch"` + RouteKey string `json:"routeKey"` + Status string `json:"status,omitempty"` + DisconnectStatusCode int64 `json:"disconnectStatusCode,omitempty"` + DisconnectReason *string `json:"disconnectReason,omitempty"` } // APIGatewayCustomAuthorizerRequestTypeRequestIdentity contains identity information for the request caller including certificate information if using mTLS. diff --git a/events/apigw_test.go b/events/apigw_test.go index 5957e96a..6aceda3c 100644 --- a/events/apigw_test.go +++ b/events/apigw_test.go @@ -150,6 +150,52 @@ func TestApiGatewayWebsocketRequestMarshaling(t *testing.T) { assert.JSONEq(t, string(inputJSON), string(outputJSON)) } +func TestApiGatewayWebsocketRequestSendMessageMarshaling(t *testing.T) { + + // read json from file + inputJSON, err := ioutil.ReadFile("./testdata/apigw-websocket-request-send-message.json") + if err != nil { + t.Errorf("could not open test file. details: %v", err) + } + + // de-serialize into Go object + var inputEvent APIGatewayWebsocketProxyRequest + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // serialize to json + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + +func TestApiGatewayWebsocketRequestDisconnectMarshaling(t *testing.T) { + + // read json from file + inputJSON, err := ioutil.ReadFile("./testdata/apigw-websocket-request-disconnect.json") + if err != nil { + t.Errorf("could not open test file. details: %v", err) + } + + // de-serialize into Go object + var inputEvent APIGatewayWebsocketProxyRequest + if err := json.Unmarshal(inputJSON, &inputEvent); err != nil { + t.Errorf("could not unmarshal event. details: %v", err) + } + + // serialize to json + outputJSON, err := json.Marshal(inputEvent) + if err != nil { + t.Errorf("could not marshal event. details: %v", err) + } + + assert.JSONEq(t, string(inputJSON), string(outputJSON)) +} + func TestApiGatewayWebsocketRequestMalformedJson(t *testing.T) { test.TestMalformedJson(t, APIGatewayWebsocketProxyRequest{}) } diff --git a/events/testdata/apigw-websocket-request-disconnect.json b/events/testdata/apigw-websocket-request-disconnect.json new file mode 100644 index 00000000..bbb1e859 --- /dev/null +++ b/events/testdata/apigw-websocket-request-disconnect.json @@ -0,0 +1,35 @@ +{ + "headers": { + "Host": "dl7ptocha9.execute-api.ap-northeast-1.amazonaws.com", + "x-api-key": "", + "X-Forwarded-For": "", + "x-restapi": "" + }, + "multiValueHeaders": { + "Host": [ "dl7ptocha9.execute-api.ap-northeast-1.amazonaws.com" ], + "x-api-key": [ "" ], + "X-Forwarded-For": [ "" ], + "x-restapi": [ "" ] + }, + "requestContext": { + "routeKey": "$disconnect", + "disconnectStatusCode": 1001, + "eventType": "DISCONNECT", + "extendedRequestId": "R1koeHsUtjMFbRw=", + "requestTime": "20/Jan/2024:11:55:08 +0000", + "messageDirection": "IN", + "disconnectReason": "", + "stage": "prod", + "connectedAt": 1705751697419, + "requestTimeEpoch": 1705751708326, + "identity": { + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", + "sourceIp": "49.105.91.154" + }, + "requestId": "R1koeHsUtjMFbRw=", + "domainName": "dl7ptocha9.execute-api.ap-northeast-1.amazonaws.com", + "connectionId": "R1kmxc2VNjMCFIA=", + "apiId": "dl7ptocha9" + }, + "isBase64Encoded": false +} diff --git a/events/testdata/apigw-websocket-request-send-message.json b/events/testdata/apigw-websocket-request-send-message.json new file mode 100644 index 00000000..13e95a7e --- /dev/null +++ b/events/testdata/apigw-websocket-request-send-message.json @@ -0,0 +1,23 @@ +{ + "requestContext": { + "routeKey": "$default", + "messageId": "R1knPc2ntjMCFIA=", + "eventType": "MESSAGE", + "extendedRequestId": "R1knPH17NjMFftw=", + "requestTime": "20/Jan/2024:11:55:00 +0000", + "messageDirection": "IN", + "stage": "prod", + "connectedAt": 1705751697419, + "requestTimeEpoch": 1705751700453, + "identity": { + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36", + "sourceIp": "49.105.91.154" + }, + "requestId": "R1knPH17NjMFftw=", + "domainName": "dl7ptocha9.execute-api.ap-northeast-1.amazonaws.com", + "connectionId": "R1kmxc2VNjMCFIA=", + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} diff --git a/events/testdata/apigw-websocket-request.json b/events/testdata/apigw-websocket-request.json index 21217a57..041d90e9 100644 --- a/events/testdata/apigw-websocket-request.json +++ b/events/testdata/apigw-websocket-request.json @@ -97,11 +97,11 @@ "extendedRequestId": "TWegAcC4EowCHnA=", "integrationLatency": "123", "messageDirection": "IN", - "messageId": null, "requestTime": "07/Jan/2019:09:20:57 +0000", "requestTimeEpoch": 0, "routeKey": "$connect", "status": "*" }, - "body": "{\r\n\t\"a\": 1\r\n}" + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false }