├── CODE_OF_CONDUCT.md
├── .vscode
└── settings.json
├── go.mod
├── LICENSE
├── connect
├── Makefile
└── connect.go
├── publish
├── Makefile
└── publish.go
├── disconnect
├── Makefile
└── disconnect.go
├── .editorconfig
├── .gitignore
├── lib
├── logger
│ └── instance.go
├── apigw
│ ├── response.go
│ ├── client.go
│ └── ws
│ │ └── envelop.go
└── redis
│ └── client.go
├── Makefile
├── README.md
├── CONTRIBUTING.md
├── go.sum
└── template.yml
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "CNAME",
4 | "CPUs",
5 | "Datapoints",
6 | "Hostnames",
7 | "SADD",
8 | "SMEMBERS",
9 | "SREM",
10 | "aerr",
11 | "apigw",
12 | "cerr",
13 | "servicelookup",
14 | "wscat"
15 | ]
16 | }
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module com.aws-samples/apigateway.websockets.golang
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/aws/aws-lambda-go v1.18.0
7 | github.com/aws/aws-sdk-go-v2 v0.24.0
8 | github.com/jmespath/go-jmespath v0.3.0 // indirect
9 | github.com/mediocregopher/radix/v3 v3.5.2
10 | github.com/pkg/errors v0.9.1 // indirect
11 | go.uber.org/zap v1.15.0
12 | )
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/connect/Makefile:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | .PHONY: clean build
24 |
25 | clean:
26 | rm -rfv bin
27 |
28 | build:
29 | GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(ARTIFACTS_DIR)/bootstrap
30 |
--------------------------------------------------------------------------------
/publish/Makefile:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | .PHONY: clean build
24 |
25 | clean:
26 | rm -rfv bin
27 |
28 | build:
29 | GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(ARTIFACTS_DIR)/bootstrap
30 |
--------------------------------------------------------------------------------
/disconnect/Makefile:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | .PHONY: clean build
24 |
25 | clean:
26 | rm -rfv bin
27 |
28 | build:
29 | GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(ARTIFACTS_DIR)/bootstrap
30 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 | root = true
23 |
24 | [*]
25 | charset = utf-8
26 | indent_size = 2
27 | end_of_line = lf
28 | indent_style = space
29 | insert_final_newline = true
30 | trim_trailing_whitespace = true
31 |
32 | [*.go]
33 | indent_size = 4
34 |
35 | [Makefile]
36 | indent_size = 4
37 | indent_style = tab
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 | *.exe
23 | *.exe~
24 | *.dll
25 | *.so
26 | *.dylib
27 |
28 | # Test binary, build with `go test -c` and test data
29 | *.test
30 | **/_test
31 |
32 | # Output of the go coverage tool, specifically when used with LiteIDE
33 | *.out
34 |
35 | # SAM generated directories and files
36 | .aws-sam
37 | packaged.yml
38 |
39 | # AWS Lambda invoke response
40 | out.json
41 |
42 | # All build directories and log files
43 | **/bin
44 | **/*.log
45 | **/*.DS_Store
46 |
--------------------------------------------------------------------------------
/lib/logger/instance.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package logger provides a singleton logging instance which is used across the same AWS Lambda execution contexts.
24 | // Reusing the client across execution contexts provides some performance enhancements and ensures the logger
25 | // configuration is consistent across all handlers.
26 | package logger
27 |
28 | import "go.uber.org/zap"
29 |
30 | // Instance used across Lambda invocations for this execution context.
31 | var Instance *zap.Logger
32 |
33 | // initialize the logger used across Lambda invocations for the same execution context.
34 | func init() {
35 | Instance, _ = zap.NewProduction()
36 | }
37 |
--------------------------------------------------------------------------------
/lib/apigw/response.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package apigw provides common resources for working with Amazon API Gateway
24 | package apigw
25 |
26 | import (
27 | "net/http"
28 |
29 | "github.com/aws/aws-lambda-go/events"
30 | )
31 |
32 | // Response is a typedef for the response type provided by the SDK.
33 | type Response = events.APIGatewayProxyResponse
34 |
35 | // InternalServerErrorResponse returns an Amazon API Gateway Proxy Response configured with the correct HTTP status
36 | // code.
37 | func InternalServerErrorResponse() Response {
38 | return Response{StatusCode: http.StatusInternalServerError}
39 | }
40 |
41 | // BadRequestResponse returns an Amazon API Gateway Proxy Response configured with the correct HTTP status code.
42 | func BadRequestResponse() Response {
43 | return Response{StatusCode: http.StatusBadRequest}
44 | }
45 |
46 | // OkResponse returns an Amazon API Gateway Proxy Response configured with the correct HTTP status code.
47 | func OkResponse() Response {
48 | return Response{StatusCode: http.StatusOK}
49 | }
50 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | .PHONY: check clean test build deploy
24 |
25 | test:
26 | go test -v ./...
27 |
28 | clean:
29 | $(MAKE) -C publish clean
30 | $(MAKE) -C connect clean
31 | $(MAKE) -C disconnect clean
32 |
33 | build: clean
34 | @echo "building handlers for aws lambda"
35 | sam build
36 |
37 | build-ConnectFunction:
38 | @echo "building handler for aws lambda"
39 | $(MAKE) -C connect build
40 |
41 | build-DisconnectFunction:
42 | @echo "building handler for aws lambda"
43 | $(MAKE) -C disconnect build
44 |
45 | build-PublishFunction:
46 | @echo "building handler for aws lambda"
47 | $(MAKE) -C publish build
48 |
49 | deploy: check
50 | @echo "deploying infrastructure and code"
51 | sam package --output-template-file packaged.yml --s3-bucket "${bucket}"
52 | sam deploy --no-fail-on-empty-changeset \
53 | --stack-name "${stack}" \
54 | --template-file packaged.yml \
55 | --capabilities CAPABILITY_IAM
56 |
57 | check:
58 | ifndef bucket
59 | $(error bucket was not provided)
60 | endif
61 | ifndef stack
62 | $(error stack was not provided)
63 | endif
64 |
--------------------------------------------------------------------------------
/lib/apigw/client.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package apigw provides common resources for working with Amazon API Gateway
24 | package apigw
25 |
26 | import (
27 | "net/url"
28 |
29 | "github.com/aws/aws-sdk-go-v2/aws"
30 | "github.com/aws/aws-sdk-go-v2/service/apigatewaymanagementapi"
31 | )
32 |
33 | // NewAPIGatewayManagementClient creates a new API Gateway Management Client instance from the provided parameters. The
34 | // new client will have a custom endpoint that resolves to the application's deployed API.
35 | func NewAPIGatewayManagementClient(cfg *aws.Config, domain, stage string) *apigatewaymanagementapi.Client {
36 | cp := cfg.Copy()
37 | cp.EndpointResolver = aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
38 | if service != "execute-api" {
39 | return cfg.EndpointResolver.ResolveEndpoint(service, region)
40 | }
41 |
42 | var endpoint url.URL
43 | endpoint.Path = stage
44 | endpoint.Host = domain
45 | endpoint.Scheme = "https"
46 | return aws.Endpoint{
47 | SigningRegion: region,
48 | URL: endpoint.String(),
49 | }, nil
50 | })
51 |
52 | return apigatewaymanagementapi.New(cp)
53 | }
54 |
--------------------------------------------------------------------------------
/lib/apigw/ws/envelop.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package ws provides common resources for working with Amazon API Gateway WebSockets
24 | package ws
25 |
26 | import "encoding/json"
27 |
28 | // InputEnvelop defines the expected structure for incoming messages sent over the WebSocket connection. The envelop
29 | // provides additional metadata in addition to the message data.
30 | type InputEnvelop struct {
31 | Echo bool `json:"echo"`
32 | Type int `json:"type"`
33 | Data json.RawMessage `json:"data"`
34 | }
35 |
36 | // Decode decodes and populates the InputEnvelop from the provided bytes.
37 | func (e *InputEnvelop) Decode(data []byte) (*InputEnvelop, error) {
38 | err := json.Unmarshal(data, e)
39 | return e, err
40 | }
41 |
42 | // OutputEnvelop defines the structure for messages sent over the WebSocket connection from the backend service. The
43 | // envelop provides additional metadata in addition to the message data.
44 | type OutputEnvelop struct {
45 | Type int `json:"type"`
46 | Data json.RawMessage `json:"data"`
47 | Received int64 `json:"received"`
48 | }
49 |
50 | // Encode encodes the OutputEnvelop as JSON. The output is suitable for sending over the wire.
51 | func (e *OutputEnvelop) Encode() ([]byte, error) {
52 | return json.Marshal(e)
53 | }
54 |
--------------------------------------------------------------------------------
/lib/redis/client.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | // Package redis provides a singleton Redis client instance which is used across the same AWS Lambda execution contexts.
24 | // Reusing the client across execution contexts provides some performance enhancements due to reusing the underlying
25 | // connection to the Redis cluster.
26 | package redis
27 |
28 | import (
29 | "fmt"
30 | "net"
31 |
32 | "com.aws-samples/apigateway.websockets.golang/lib/logger"
33 | "github.com/mediocregopher/radix/v3"
34 | "go.uber.org/zap"
35 | )
36 |
37 | // Client is the single client instance shared across the same Lambda execution contexts.
38 | var Client *radix.Pool
39 |
40 | func init() {
41 | cname, servers, err := net.LookupSRV("redis", "tcp", "service.internal")
42 | if err != nil {
43 | logger.Instance.Panic("unable to resolve redis srv record", zap.Error(err))
44 | }
45 |
46 | if len(servers) == 0 {
47 | logger.Instance.Panic("unable to resolve redis srv record")
48 | }
49 |
50 | logger.Instance.Info("redis srv record",
51 | zap.String("cname", cname),
52 | zap.Uint16("port", servers[0].Port),
53 | zap.String("target", servers[0].Target),
54 | zap.Uint16("weight", servers[0].Weight),
55 | zap.Uint16("priority", servers[0].Priority))
56 |
57 | addr := net.JoinHostPort(servers[0].Target, fmt.Sprintf("%d", servers[0].Port))
58 | Client, err = radix.NewPool("tcp", addr, 1)
59 | if err != nil {
60 | logger.Instance.Panic("unable to create redis connection pool", zap.Error(err))
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/connect/connect.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package main
24 |
25 | import (
26 | "context"
27 |
28 | "com.aws-samples/apigateway.websockets.golang/lib/apigw"
29 | "com.aws-samples/apigateway.websockets.golang/lib/logger"
30 | "com.aws-samples/apigateway.websockets.golang/lib/redis"
31 | "github.com/aws/aws-lambda-go/events"
32 |
33 | "github.com/aws/aws-lambda-go/lambda"
34 | radix "github.com/mediocregopher/radix/v3"
35 | "go.uber.org/zap"
36 | )
37 |
38 | func main() {
39 | lambda.Start(handler)
40 | }
41 |
42 | // handler receives a synchronous invocation from API Gateway when a new WebSocket connection is created for the
43 | // application's API. The connection details are cached in the application's Redis cache which makes the connection
44 | // available to the other application components.
45 | func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
46 | defer func() {
47 | _ = logger.Instance.Sync()
48 | }()
49 |
50 | logger.Instance.Info("websocket connect",
51 | zap.String("requestId", req.RequestContext.RequestID),
52 | zap.String("connectionId", req.RequestContext.ConnectionID))
53 |
54 | var result string
55 | err := redis.Client.Do(radix.Cmd(&result, "SADD", "connections", req.RequestContext.ConnectionID))
56 | if err != nil {
57 | logger.Instance.Error("failed to cache connection details",
58 | zap.String("requestId", req.RequestContext.RequestID),
59 | zap.String("connectionId", req.RequestContext.ConnectionID),
60 | zap.Error(err))
61 |
62 | return apigw.InternalServerErrorResponse(), err
63 | }
64 |
65 | logger.Instance.Info("websocket connection cached",
66 | zap.String("result", result),
67 | zap.String("requestId", req.RequestContext.RequestID),
68 | zap.String("connectionId", req.RequestContext.ConnectionID))
69 |
70 | return apigw.OkResponse(), nil
71 | }
72 |
--------------------------------------------------------------------------------
/disconnect/disconnect.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package main
24 |
25 | import (
26 | "context"
27 |
28 | "com.aws-samples/apigateway.websockets.golang/lib/apigw"
29 | "com.aws-samples/apigateway.websockets.golang/lib/logger"
30 | "com.aws-samples/apigateway.websockets.golang/lib/redis"
31 | "github.com/aws/aws-lambda-go/events"
32 |
33 | "github.com/aws/aws-lambda-go/lambda"
34 | radix "github.com/mediocregopher/radix/v3"
35 | "go.uber.org/zap"
36 | )
37 |
38 | func main() {
39 | lambda.Start(handler)
40 | }
41 |
42 | // handler receives a synchronous invocation from API Gateway when a new connection has been disconnected from the
43 | // application's API. The connection details are removed in the application's Redis cache which cleans up the connection
44 | // details. This handler is not guaranteed to be called when the WebSocket connection is closed.
45 | func handler(_ context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
46 | defer func() {
47 | _ = logger.Instance.Sync()
48 | }()
49 |
50 | logger.Instance.Info("websocket disconnect",
51 | zap.String("requestId", req.RequestContext.RequestID),
52 | zap.String("connectionId", req.RequestContext.ConnectionID))
53 |
54 | var result string
55 | err := redis.Client.Do(radix.Cmd(&result, "SREM", "connections", req.RequestContext.ConnectionID))
56 | if err != nil {
57 | logger.Instance.Error("failed to delete connection details from cache",
58 | zap.String("requestId", req.RequestContext.RequestID),
59 | zap.String("connectionId", req.RequestContext.ConnectionID),
60 | zap.Error(err))
61 |
62 | return apigw.InternalServerErrorResponse(), err
63 | }
64 |
65 | logger.Instance.Info("websocket connection deleted from cache",
66 | zap.String("result", result),
67 | zap.String("requestId", req.RequestContext.RequestID),
68 | zap.String("connectionId", req.RequestContext.ConnectionID))
69 |
70 | return apigw.OkResponse(), nil
71 | }
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Amazon API Gateway WebSockets with Amazon ElastiCache for Redis
2 |
3 | This project contains a reference implementation for using AWS VPC, Amazon API Gateway WebSockets, AWS Lambda, and Amazon ElastiCache for Redis.
4 |
5 | Three AWS Lambda handlers are included in the project:
6 |
7 | - **ConnectFunction**: Invoked by API Gateway when a new WebSocket connection is established. The connection information is cached in the ElastiCache for Redis instance.
8 |
9 | - **DisconnectFunction**: Invoked by API Gateway when a WebSocket connection is terminated. The connection information is removed from the ElastiCache for Redis instance.
10 |
11 | - **PublishFunction**: Invoked by API Gateway when data is sent from the client over the WebSocket connection. The data is "published" to all connected clients.
12 |
13 | ## Building and Deploying
14 |
15 | ### Compilation
16 |
17 | The AWS Lambda handlers are written in Go. See the following link for more information about the Go programming language including installation instructions.
18 |
19 | Go >= 1.13 is required.
20 |
21 |
22 |
23 | Compilation of the AWS Lambda handlers is managed with the included Makefile. The Makefile ensures the binaries are cross-compiled to run in AWS Lambda. Run the following command to build the binaries for deployment.
24 |
25 | ```bash
26 | make clean build
27 | ```
28 |
29 | ### Deploying
30 |
31 | Deployments are managed using the AWS Serverless Application Model. See the following links for more information about AWS SAM including installation instructions.
32 |
33 |
34 |
35 |
36 |
37 | The included Makefile includes a target for deploying the infrastructure and code using AWS SAM. The following example demonstrates how to deploy the infrastructure and code.
38 |
39 | ```bash
40 | AWS_PROFILE={profile} AWS_DEFAULT_REGION={region} make bucket={bucket} stack={stack name} deploy
41 | ```
42 |
43 | ## Using wscat for Testing
44 |
45 |
46 |
47 | Query the deployed WebSocket endpoint by running the following command:
48 |
49 | ```bash
50 | aws cloudformation describe-stacks --stack-name {stack name} --query "Stacks[0].Outputs[?OutputKey=='WebSocketEndpoint'].OutputValue" --output text
51 | ```
52 |
53 | Now knowing the WebSocket endpoint, wscat can be used to test the deployment. Establish a WebSocket connection with the following command, and the endpoint value from the previous step:
54 |
55 | ```bash
56 | wscat -c {endpoint}
57 | ```
58 |
59 | The "backend service" only accepts messages which adhere to the below schema. Use the following format to publish a message to all connected clients:
60 |
61 | ```json
62 | { "echo": false, "type": 99, "data": "data to publish" }
63 | ```
64 |
65 | ## Security
66 |
67 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
68 |
69 | ## License
70 |
71 | This library is licensed under the MIT-0 License. See the LICENSE file.
72 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *master* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes.
62 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/aws/aws-lambda-go v1.18.0 h1:13AfxzFoPlFjOzXHbRnKuTbteCzHbu4YQgKONNhWcmo=
4 | github.com/aws/aws-lambda-go v1.18.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw=
5 | github.com/aws/aws-sdk-go-v2 v0.24.0 h1:R0lL0krk9EyTI1vmO1ycoeceGZotSzCKO51LbPGq3rU=
6 | github.com/aws/aws-sdk-go-v2 v0.24.0/go.mod h1:2LhT7UgHOXK3UXONKI5OMgIyoQL6zTAw/jwIeX6yqzw=
7 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
12 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
13 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
14 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
15 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
16 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
17 | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
18 | github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
19 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
20 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
21 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
22 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
23 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
25 | github.com/mediocregopher/radix/v3 v3.5.2 h1:A9u3G7n4+fWmDZ2ZDHtlK+cZl4q55T+7RjKjR0/MAdk=
26 | github.com/mediocregopher/radix/v3 v3.5.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
27 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
28 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
29 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
30 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
34 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
35 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
37 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
38 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
39 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
40 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
41 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
42 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
43 | github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
44 | go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
45 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
46 | go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
47 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
48 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
49 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
50 | go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
51 | go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
52 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
53 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
54 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
55 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
56 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
57 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
58 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
59 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
60 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
61 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
62 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
63 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
64 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
65 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
66 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
67 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
68 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
69 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
70 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
71 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
72 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
73 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
74 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
75 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
76 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
77 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
78 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
79 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
80 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
81 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
82 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
83 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
84 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
85 |
--------------------------------------------------------------------------------
/publish/publish.go:
--------------------------------------------------------------------------------
1 | // MIT No Attribution
2 |
3 | // Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy
6 | // of this software and associated documentation files (the "Software"), to deal
7 | // in the Software without restriction, including without limitation the rights
8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | // copies of the Software, and to permit persons to whom the Software is
10 | // furnished to do so, subject to the following conditions:
11 |
12 | // The above copyright notice and this permission notice shall be included in all
13 | // copies or substantial portions of the Software.
14 |
15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | // SOFTWARE.
22 |
23 | package main
24 |
25 | import (
26 | "context"
27 | "errors"
28 | "runtime"
29 | "sync"
30 | "time"
31 |
32 | "com.aws-samples/apigateway.websockets.golang/lib/apigw"
33 | "com.aws-samples/apigateway.websockets.golang/lib/apigw/ws"
34 | "com.aws-samples/apigateway.websockets.golang/lib/logger"
35 | "com.aws-samples/apigateway.websockets.golang/lib/redis"
36 | "github.com/aws/aws-lambda-go/events"
37 | "github.com/aws/aws-sdk-go-v2/aws"
38 | "github.com/aws/aws-sdk-go-v2/aws/awserr"
39 | "github.com/aws/aws-sdk-go-v2/aws/external"
40 |
41 | "github.com/aws/aws-lambda-go/lambda"
42 | "github.com/aws/aws-sdk-go-v2/service/apigatewaymanagementapi"
43 | radix "github.com/mediocregopher/radix/v3"
44 | "go.uber.org/zap"
45 | )
46 |
47 | // Stack is a simple thread-safe Pop only stack implementation which allows workers to pull work from the stack.
48 | type Stack struct {
49 | mu sync.Mutex
50 | elements []string
51 | }
52 |
53 | // Pop pops the next item from the stack and returns it or returns an error if the stack is empty.
54 | func (s *Stack) Pop() (string, error) {
55 | s.mu.Lock()
56 | defer s.mu.Unlock()
57 |
58 | c := len(s.elements)
59 | if c == 0 {
60 | return "", errors.New("no more elements")
61 | }
62 |
63 | v := s.elements[c-1]
64 | s.elements = s.elements[:c-1]
65 | return v, nil
66 | }
67 |
68 | // Len returns the length of the stack.
69 | func (s *Stack) Len() int {
70 | s.mu.Lock()
71 | defer s.mu.Unlock()
72 | return len(s.elements)
73 | }
74 |
75 | // cfg is the base or parent AWS configuration for this lambda.
76 | var cfg aws.Config
77 |
78 | // apiClient provides access to the Amazon API Gateway management functions. Once initialized, the instance is reused
79 | // across subsequent AWS Lambda invocations. This potentially amortizes the instance creation over multiple executions
80 | // of the AWS Lambda instance.
81 | var apiClient *apigatewaymanagementapi.Client
82 |
83 | // Use the SDK default configuration, loading additional config and credentials values from the environment variables,
84 | // shared credentials, and shared configuration files.
85 | func init() {
86 | var err error
87 | cfg, err = external.LoadDefaultAWSConfig()
88 | if err != nil {
89 | logger.Instance.Panic("unable to load SDK config", zap.Error(err))
90 | }
91 | }
92 |
93 | func main() {
94 | lambda.Start(handler)
95 | }
96 |
97 | // handler is the hook AWS Lambda calls to invoke the function as an Amazon API Gateway Proxy. This handlers reads the
98 | // request and echos the request back out to all connected clients. This demonstrates looking up connected clients from
99 | // the Redis cache and calling the Amazon API Gateway Management API to send data to the connected clients.
100 | func handler(ctx context.Context, req *events.APIGatewayWebsocketProxyRequest) (apigw.Response, error) {
101 | defer func() {
102 | _ = logger.Instance.Sync()
103 | }()
104 |
105 | // Lazily initialize the API Gateway Management client. This enables setting the service's endpoint to our API
106 | // endpoint. These values are provided from the synchronous request, thus the client can only be created upon the
107 | // first invocation.
108 | if apiClient == nil {
109 | apiClient = apigw.NewAPIGatewayManagementClient(&cfg, req.RequestContext.DomainName, req.RequestContext.Stage)
110 | }
111 |
112 | logger.Instance.Info("websocket publish",
113 | zap.String("requestId", req.RequestContext.RequestID),
114 | zap.String("connectionId", req.RequestContext.ConnectionID))
115 |
116 | input, err := new(ws.InputEnvelop).Decode([]byte(req.Body))
117 | if err != nil {
118 | logger.Instance.Error("failed to parse client input",
119 | zap.String("requestId", req.RequestContext.RequestID),
120 | zap.String("connectionId", req.RequestContext.ConnectionID),
121 | zap.Error(err))
122 |
123 | return apigw.BadRequestResponse(), err
124 | }
125 |
126 | output := &ws.OutputEnvelop{
127 | Data: input.Data,
128 | Type: input.Type,
129 | Received: time.Now().Unix(),
130 | }
131 |
132 | data, err := output.Encode()
133 | if err != nil {
134 | logger.Instance.Error("failed to encode output",
135 | zap.String("requestId", req.RequestContext.RequestID),
136 | zap.String("connectionId", req.RequestContext.ConnectionID),
137 | zap.Error(err))
138 |
139 | return apigw.InternalServerErrorResponse(), err
140 | }
141 |
142 | stack := new(Stack)
143 | err = redis.Client.Do(radix.Cmd(&(stack.elements), "SMEMBERS", "connections"))
144 | if err != nil {
145 | logger.Instance.Error("failed to read connections from cache",
146 | zap.String("requestId", req.RequestContext.RequestID),
147 | zap.String("connectionId", req.RequestContext.ConnectionID),
148 | zap.Error(err))
149 |
150 | return apigw.InternalServerErrorResponse(), err
151 | }
152 |
153 | logger.Instance.Info("websocket connections read from cache",
154 | zap.Int("connections", stack.Len()),
155 | zap.String("requestId", req.RequestContext.RequestID),
156 | zap.String("connectionId", req.RequestContext.ConnectionID))
157 |
158 | // Calculate how many go routines should be created to handle the work. Taking the number of logical CPUs times a
159 | // factor of 4 enables processing outgoing messages concurrently while limiting the amount of context switching.
160 | var wg sync.WaitGroup
161 | for i := 0; i < runtime.NumCPU()*4; i++ {
162 | wg.Add(1)
163 |
164 | // Run the go routine until the context is canceled or there is no more work to process.
165 | go func(sender string, echo bool) {
166 | defer wg.Done()
167 | for {
168 | select {
169 | case <-ctx.Done():
170 | return
171 | default:
172 | // Pull the next connection id from the stack. If the stack is empty, a non-nil error is returned
173 | // and the go routine exits cleanly. Otherwise, a nil error and the next connection id is returned.
174 | id, err := stack.Pop()
175 | if err != nil {
176 | return
177 | }
178 |
179 | // Do not send data to the connection if the connection represents the sender and the message was
180 | // configured to not echo back the message.
181 | if id == sender && !echo {
182 | continue
183 | }
184 |
185 | // Publish the data to the connected client via Amazon API Gateway's Management API. If publishing
186 | // the data results in an error, the error is passed to a convenience function which attempts to
187 | // resolve the issue which caused the error. The convenience function may return the same error if
188 | // it can not be handled or may return a different error if attempting the resolution results in an
189 | // error. Regardless, if an error is returned the only course of action is to log it.
190 | err = handleError(publish(ctx, id, data), id)
191 | if err != nil {
192 | logger.Instance.Error("failed to publish to connection",
193 | zap.String("receiver", id),
194 | zap.String("requestId", req.RequestContext.RequestID),
195 | zap.String("sender", req.RequestContext.ConnectionID),
196 | zap.Error(err))
197 | }
198 | }
199 | }
200 | }(req.RequestContext.ConnectionID, input.Echo)
201 | }
202 |
203 | wg.Wait()
204 | return apigw.OkResponse(), nil
205 | }
206 |
207 | // publish publishes the provided data to the provided Amazon API Gateway connection ID. A common failure scenario which
208 | // results in an error is if the connection ID is no longer valid. This can occur when a client disconnected from the
209 | // Amazon API Gateway endpoint but the disconnect AWS Lambda was not invoked as it is not guaranteed to be invoked when
210 | // clients disconnect.
211 | func publish(ctx context.Context, id string, data []byte) error {
212 | _, err := apiClient.PostToConnectionRequest(&apigatewaymanagementapi.PostToConnectionInput{
213 | Data: data,
214 | ConnectionId: aws.String(id),
215 | }).Send(ctx)
216 |
217 | return err
218 | }
219 |
220 | // handleError is a convenience function for taking action for a given error value. The function handles nil errors as a
221 | // convenience to the caller. If a nil error is provided, the error is immediately returned. The function may return an
222 | // error from the handling action, such as deleting the id from the cache, if that action results in an error.
223 | func handleError(err error, id string) error {
224 | if err == nil {
225 | return err
226 | }
227 |
228 | // Casting to the awserr.Error type will allow you to inspect the error code returned by the service in code. The
229 | // error code can be used to switch on context specific functionality.
230 | if aerr, ok := err.(awserr.Error); ok {
231 | switch aerr.Code() {
232 | case aws.ErrCodeSerialization:
233 | logger.Instance.Info("delete stale connection details from cache", zap.String("connectionId", id))
234 | return deleteConnectionId(id)
235 | case apigatewaymanagementapi.ErrCodeGoneException:
236 | logger.Instance.Info("delete stale connection details from cache", zap.String("connectionId", id))
237 | return deleteConnectionId(id)
238 | default:
239 | return err
240 | }
241 | }
242 |
243 | return err
244 | }
245 |
246 | // deleteConnectionId deletes the connection id from the REDIS cache. The function logs both error and success cases.
247 | func deleteConnectionId(id string) error {
248 | var result string
249 | err := redis.Client.Do(radix.Cmd(&result, "SREM", "connections", id))
250 | if err != nil {
251 | logger.Instance.Error("failed to delete connection details from cache",
252 | zap.String("connectionId", id),
253 | zap.Error(err))
254 |
255 | return err
256 | }
257 |
258 | logger.Instance.Info("websocket connection deleted from cache",
259 | zap.String("result", result),
260 | zap.String("connectionId", id))
261 |
262 | return err
263 | }
264 |
--------------------------------------------------------------------------------
/template.yml:
--------------------------------------------------------------------------------
1 | # MIT No Attribution
2 |
3 | # Copyright 2020 Amazon.com, Inc. or its affiliates.
4 |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 |
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 | AWSTemplateFormatVersion: "2010-09-09"
23 | Transform: AWS::Serverless-2016-10-31
24 | Description: >
25 |
26 | Parameters:
27 | ApplicationName:
28 | Type: String
29 | Default: API Gateway WebSockets Golang Example
30 | Description: The application name used when tagging resources
31 |
32 | VpcCIDR:
33 | Type: String
34 | Default: 10.1.0.0/16
35 | Description: The IP range (CIDR notation) for this VPC
36 | AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"
37 |
38 | PrivateSubnet1CIDR:
39 | Type: String
40 | Default: 10.1.1.0/24
41 | Description: The IP range (CIDR notation) for the private subnet in the first AZ
42 | AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"
43 |
44 | PrivateSubnet2CIDR:
45 | Type: String
46 | Default: 10.1.2.0/24
47 | Description: The IP range (CIDR notation) for the private subnet in the second AZ
48 | AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"
49 |
50 | PublicSubnet1CIDR:
51 | Type: String
52 | Default: 10.1.3.0/24
53 | Description: The IP range (CIDR notation) for the public subnet in the first AZ
54 | AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"
55 |
56 | PublicSubnet2CIDR:
57 | Type: String
58 | Default: 10.1.4.0/24
59 | Description: The IP range (CIDR notation) for the public subnet in the second AZ
60 | AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"
61 |
62 | CacheNodeType:
63 | Type: String
64 | Default: cache.t2.small
65 | Description: EC2 Instance Type to use for the Redis cache
66 | AllowedValues:
67 | - cache.t2.micro
68 | - cache.t2.small
69 | - cache.t2.medium
70 | - cache.t3.micro
71 | - cache.t3.small
72 | - cache.t3.medium
73 |
74 | Globals:
75 | Function:
76 | CodeUri: .
77 | Timeout: 5
78 | Tracing: Active
79 | MemorySize: 512
80 | Runtime: provided.al2
81 | Handler: my.bootstrap.file
82 | VpcConfig:
83 | SubnetIds:
84 | - !Ref PrivateSubnet1
85 | - !Ref PrivateSubnet2
86 | SecurityGroupIds:
87 | - !Ref NoIngressSecurityGroup
88 |
89 | Resources:
90 | VPC:
91 | Type: AWS::EC2::VPC
92 | Properties:
93 | CidrBlock: !Ref VpcCIDR
94 | EnableDnsSupport: true
95 | EnableDnsHostnames: true
96 | Tags:
97 | - Key: Name
98 | Value: !Ref ApplicationName
99 |
100 | PrivateSubnet1:
101 | Type: AWS::EC2::Subnet
102 | Properties:
103 | VpcId: !Ref VPC
104 | CidrBlock: !Ref PrivateSubnet1CIDR
105 | AvailabilityZone: !Select
106 | - 0
107 | - Fn::GetAZs: !Ref "AWS::Region"
108 | Tags:
109 | - Key: Name
110 | Value: !Sub ${ApplicationName} Private Subnet (AZ1)
111 |
112 | PrivateSubnet2:
113 | Type: AWS::EC2::Subnet
114 | Properties:
115 | VpcId: !Ref VPC
116 | CidrBlock: !Ref PrivateSubnet2CIDR
117 | AvailabilityZone: !Select
118 | - 1
119 | - Fn::GetAZs: !Ref "AWS::Region"
120 | Tags:
121 | - Key: Name
122 | Value: !Sub ${ApplicationName} Private Subnet (AZ2)
123 |
124 | PublicSubnet1:
125 | Type: AWS::EC2::Subnet
126 | Properties:
127 | VpcId: !Ref VPC
128 | CidrBlock: !Ref PublicSubnet1CIDR
129 | AvailabilityZone: !Select
130 | - 0
131 | - Fn::GetAZs: !Ref "AWS::Region"
132 | Tags:
133 | - Key: Name
134 | Value: !Sub ${ApplicationName} Public Subnet (AZ1)
135 |
136 | PublicSubnet2:
137 | Type: AWS::EC2::Subnet
138 | Properties:
139 | VpcId: !Ref VPC
140 | CidrBlock: !Ref PublicSubnet2CIDR
141 | AvailabilityZone: !Select
142 | - 1
143 | - Fn::GetAZs: !Ref "AWS::Region"
144 | Tags:
145 | - Key: Name
146 | Value: !Sub ${ApplicationName} Public Subnet (AZ2)
147 |
148 | InternetGateway:
149 | Type: AWS::EC2::InternetGateway
150 | Properties:
151 | Tags:
152 | - Key: Name
153 | Value: !Sub ${ApplicationName} Internet Gateway
154 |
155 | InternetGatewayAttachment:
156 | Type: AWS::EC2::VPCGatewayAttachment
157 | Properties:
158 | VpcId: !Ref VPC
159 | InternetGatewayId: !Ref InternetGateway
160 |
161 | NatGateway1EIP:
162 | Type: AWS::EC2::EIP
163 | DependsOn: InternetGatewayAttachment
164 | Properties:
165 | Domain: vpc
166 | Tags:
167 | - Key: Name
168 | Value: !Sub ${ApplicationName} (1)
169 |
170 | NatGateway2EIP:
171 | Type: AWS::EC2::EIP
172 | DependsOn: InternetGatewayAttachment
173 | Properties:
174 | Domain: vpc
175 | Tags:
176 | - Key: Name
177 | Value: !Sub ${ApplicationName} (2)
178 |
179 | NatGateway1:
180 | Type: AWS::EC2::NatGateway
181 | Properties:
182 | SubnetId: !Ref PublicSubnet1
183 | AllocationId: !GetAtt NatGateway1EIP.AllocationId
184 | Tags:
185 | - Key: Name
186 | Value: !Sub ${ApplicationName} (1)
187 |
188 | NatGateway2:
189 | Type: AWS::EC2::NatGateway
190 | Properties:
191 | SubnetId: !Ref PublicSubnet2
192 | AllocationId: !GetAtt NatGateway2EIP.AllocationId
193 | Tags:
194 | - Key: Name
195 | Value: !Sub ${ApplicationName} (2)
196 |
197 | PublicRouteTable:
198 | Type: AWS::EC2::RouteTable
199 | Properties:
200 | VpcId: !Ref VPC
201 | Tags:
202 | - Key: Name
203 | Value: !Sub ${ApplicationName} Public Routes
204 |
205 | DefaultPublicRoute:
206 | Type: AWS::EC2::Route
207 | DependsOn: InternetGatewayAttachment
208 | Properties:
209 | GatewayId: !Ref InternetGateway
210 | DestinationCidrBlock: 0.0.0.0/0
211 | RouteTableId: !Ref PublicRouteTable
212 |
213 | PublicSubnet1RouteTableAssociation:
214 | Type: AWS::EC2::SubnetRouteTableAssociation
215 | Properties:
216 | SubnetId: !Ref PublicSubnet1
217 | RouteTableId: !Ref PublicRouteTable
218 |
219 | PublicSubnet2RouteTableAssociation:
220 | Type: AWS::EC2::SubnetRouteTableAssociation
221 | Properties:
222 | SubnetId: !Ref PublicSubnet2
223 | RouteTableId: !Ref PublicRouteTable
224 |
225 | PrivateRouteTable1:
226 | Type: AWS::EC2::RouteTable
227 | Properties:
228 | VpcId: !Ref VPC
229 | Tags:
230 | - Key: Name
231 | Value: !Sub ${ApplicationName} Private Routes (AZ1)
232 |
233 | DefaultPrivateRoute1:
234 | Type: AWS::EC2::Route
235 | Properties:
236 | NatGatewayId: !Ref NatGateway1
237 | DestinationCidrBlock: 0.0.0.0/0
238 | RouteTableId: !Ref PrivateRouteTable1
239 |
240 | PrivateSubnet1RouteTableAssociation:
241 | Type: AWS::EC2::SubnetRouteTableAssociation
242 | Properties:
243 | SubnetId: !Ref PrivateSubnet1
244 | RouteTableId: !Ref PrivateRouteTable1
245 |
246 | PrivateRouteTable2:
247 | Type: AWS::EC2::RouteTable
248 | Properties:
249 | VpcId: !Ref VPC
250 | Tags:
251 | - Key: Name
252 | Value: !Sub ${ApplicationName} Private Routes (AZ2)
253 |
254 | DefaultPrivateRoute2:
255 | Type: AWS::EC2::Route
256 | Properties:
257 | NatGatewayId: !Ref NatGateway2
258 | DestinationCidrBlock: 0.0.0.0/0
259 | RouteTableId: !Ref PrivateRouteTable2
260 |
261 | PrivateSubnet2RouteTableAssociation:
262 | Type: AWS::EC2::SubnetRouteTableAssociation
263 | Properties:
264 | SubnetId: !Ref PrivateSubnet2
265 | RouteTableId: !Ref PrivateRouteTable2
266 |
267 | PrivateHostedZone:
268 | Type: "AWS::Route53::HostedZone"
269 | Properties:
270 | Name: "service.internal"
271 | HostedZoneConfig:
272 | Comment: Private Hosted Zone
273 | VPCs:
274 | - VPCId: !Ref VPC
275 | VPCRegion: !Ref "AWS::Region"
276 |
277 | CacheEndpointRecordSetGroup:
278 | Type: AWS::Route53::RecordSetGroup
279 | Properties:
280 | HostedZoneId: !Ref PrivateHostedZone
281 | Comment: Record Set for the primary Redis endpoint
282 | RecordSets:
283 | - TTL: "900"
284 | Type: CNAME
285 | Name: "redis.service.internal"
286 | ResourceRecords:
287 | - !GetAtt RedisReplicationGroup.PrimaryEndPoint.Address
288 | - TTL: "900"
289 | Type: SRV
290 | Name: "_redis._tcp.service.internal"
291 | ResourceRecords:
292 | - !Sub "1 0 ${RedisReplicationGroup.PrimaryEndPoint.Port} redis.service.internal"
293 |
294 | NoIngressSecurityGroup:
295 | Type: AWS::EC2::SecurityGroup
296 | Properties:
297 | VpcId: !Ref VPC
298 | GroupDescription: !Sub ${ApplicationName} Security Group (No Ingress)
299 | Tags:
300 | - Key: Name
301 | Value: !Sub ${ApplicationName} Security Group (No Ingress)
302 |
303 | RedisSecurityGroup:
304 | Type: AWS::EC2::SecurityGroup
305 | Properties:
306 | VpcId: !Ref VPC
307 | GroupDescription: !Sub ${ApplicationName} Security Group (Redis)
308 | SecurityGroupIngress:
309 | - ToPort: 6379
310 | FromPort: 6379
311 | IpProtocol: tcp
312 | SourceSecurityGroupId: !Ref NoIngressSecurityGroup
313 | Description: Redis cluster connections from security group
314 | Tags:
315 | - Key: Name
316 | Value: !Sub ${ApplicationName} Security Group (Redis)
317 |
318 | RedisSubnetGroup:
319 | Type: AWS::ElastiCache::SubnetGroup
320 | Properties:
321 | Description: Redis cluster subnet group
322 | SubnetIds:
323 | - !Ref PrivateSubnet1
324 | - !Ref PrivateSubnet2
325 |
326 | RedisReplicationGroup:
327 | Type: AWS::ElastiCache::ReplicationGroup
328 | Properties:
329 | Port: 6379
330 | Engine: redis
331 | NumCacheClusters: 2
332 | EngineVersion: 5.0.0
333 | CacheNodeType: !Ref CacheNodeType
334 | AutomaticFailoverEnabled: true
335 | AtRestEncryptionEnabled: false
336 | TransitEncryptionEnabled: false
337 | CacheSubnetGroupName: !Ref RedisSubnetGroup
338 | PreferredMaintenanceWindow: sun:23:00-mon:01:30
339 | ReplicationGroupDescription: ElastiCache For Redis Replication Group
340 | SecurityGroupIds:
341 | - !Ref RedisSecurityGroup
342 | PreferredCacheClusterAZs:
343 | - !Select
344 | - 0
345 | - Fn::GetAZs: !Ref "AWS::Region"
346 | - !Select
347 | - 1
348 | - Fn::GetAZs: !Ref "AWS::Region"
349 | Tags:
350 | - Key: Name
351 | Value: !Sub ${ApplicationName} ElastiCache for Redis
352 |
353 | ConnectFunction:
354 | Type: AWS::Serverless::Function
355 | Metadata:
356 | BuildMethod: makefile
357 | Properties:
358 | Policies:
359 | - VPCAccessPolicy: {}
360 |
361 | DisconnectFunction:
362 | Metadata:
363 | BuildMethod: makefile
364 | Type: AWS::Serverless::Function
365 | Properties:
366 | Policies:
367 | - VPCAccessPolicy: {}
368 |
369 | PublishFunction:
370 | Metadata:
371 | BuildMethod: makefile
372 | Type: AWS::Serverless::Function
373 | Properties:
374 | Timeout: 15
375 | MemorySize: 2048
376 | Policies:
377 | - VPCAccessPolicy: {}
378 | - Statement:
379 | - Effect: Allow
380 | Action:
381 | - "execute-api:ManageConnections"
382 | Resource:
383 | - !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocket}/*"
384 |
385 | WebSocket:
386 | Type: AWS::ApiGatewayV2::Api
387 | Properties:
388 | Name: !Ref ApplicationName
389 | ProtocolType: WEBSOCKET
390 | RouteSelectionExpression: "$request.body.message"
391 |
392 | Deployment:
393 | Type: AWS::ApiGatewayV2::Deployment
394 | DependsOn:
395 | - PublishRoute
396 | - ConnectRoute
397 | - DisconnectRoute
398 | Properties:
399 | ApiId: !Ref WebSocket
400 |
401 | Stage:
402 | Type: AWS::ApiGatewayV2::Stage
403 | Properties:
404 | StageName: v1
405 | ApiId: !Ref WebSocket
406 | Description: TO DO
407 | DeploymentId: !Ref Deployment
408 | DefaultRouteSettings:
409 | LoggingLevel: INFO
410 | DataTraceEnabled: true
411 | DetailedMetricsEnabled: true
412 |
413 | ConnectFunctionPermission:
414 | Type: AWS::Lambda::Permission
415 | DependsOn:
416 | - WebSocket
417 | Properties:
418 | Action: lambda:InvokeFunction
419 | Principal: apigateway.amazonaws.com
420 | FunctionName: !Ref ConnectFunction
421 |
422 | DisconnectFunctionPermission:
423 | Type: AWS::Lambda::Permission
424 | DependsOn:
425 | - WebSocket
426 | Properties:
427 | Action: lambda:InvokeFunction
428 | Principal: apigateway.amazonaws.com
429 | FunctionName: !Ref DisconnectFunction
430 |
431 | PublishFunctionPermission:
432 | Type: AWS::Lambda::Permission
433 | DependsOn:
434 | - WebSocket
435 | Properties:
436 | Action: lambda:InvokeFunction
437 | Principal: apigateway.amazonaws.com
438 | FunctionName: !Ref PublishFunction
439 |
440 | ConnectFunctionLogGroup:
441 | Type: AWS::Logs::LogGroup
442 | DependsOn:
443 | - ConnectFunction
444 | Properties:
445 | RetentionInDays: 30
446 | LogGroupName: !Sub /aws/lambda/${ConnectFunction}
447 |
448 | DisconnectFunctionLogGroup:
449 | Type: AWS::Logs::LogGroup
450 | DependsOn:
451 | - DisconnectFunction
452 | Properties:
453 | RetentionInDays: 30
454 | LogGroupName: !Sub /aws/lambda/${DisconnectFunction}
455 |
456 | PublishFunctionLogGroup:
457 | Type: AWS::Logs::LogGroup
458 | DependsOn:
459 | - PublishFunction
460 | Properties:
461 | RetentionInDays: 30
462 | LogGroupName: !Sub /aws/lambda/${PublishFunction}
463 |
464 | ConnectRoute:
465 | Type: AWS::ApiGatewayV2::Route
466 | Properties:
467 | RouteKey: $connect
468 | ApiId: !Ref WebSocket
469 | AuthorizationType: NONE
470 | OperationName: ConnectRoute
471 | Target: !Join
472 | - "/"
473 | - - "integrations"
474 | - !Ref ConnectIntegration
475 |
476 | DisconnectRoute:
477 | Type: AWS::ApiGatewayV2::Route
478 | Properties:
479 | RouteKey: $disconnect
480 | ApiId: !Ref WebSocket
481 | AuthorizationType: NONE
482 | OperationName: DisconnectRoute
483 | Target: !Join
484 | - "/"
485 | - - "integrations"
486 | - !Ref DisconnectIntegration
487 |
488 | PublishRoute:
489 | Type: AWS::ApiGatewayV2::Route
490 | Properties:
491 | RouteKey: $default
492 | ApiId: !Ref WebSocket
493 | AuthorizationType: NONE
494 | OperationName: PublishRoute
495 | Target: !Join
496 | - "/"
497 | - - "integrations"
498 | - !Ref PublishIntegration
499 |
500 | ConnectIntegration:
501 | Type: AWS::ApiGatewayV2::Integration
502 | Properties:
503 | ApiId: !Ref WebSocket
504 | Description: TO DO
505 | IntegrationType: AWS_PROXY
506 | IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn}/invocations
507 |
508 | DisconnectIntegration:
509 | Type: AWS::ApiGatewayV2::Integration
510 | Properties:
511 | ApiId: !Ref WebSocket
512 | Description: TO DO
513 | IntegrationType: AWS_PROXY
514 | IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DisconnectFunction.Arn}/invocations
515 |
516 | PublishIntegration:
517 | Type: AWS::ApiGatewayV2::Integration
518 | Properties:
519 | ApiId: !Ref WebSocket
520 | Description: TO DO
521 | IntegrationType: AWS_PROXY
522 | IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PublishFunction.Arn}/invocations
523 |
524 | CacheNodeCpuUtilizationAlarm:
525 | Type: AWS::CloudWatch::Alarm
526 | Properties:
527 | Period: 900
528 | Threshold: 90
529 | Unit: Percent
530 | Statistic: Average
531 | EvaluationPeriods: 2
532 | MetricName: CPUUtilization
533 | Namespace: AWS/ElastiCache
534 | ComparisonOperator: GreaterThanThreshold
535 | AlarmName: Redis Cache Cluster CPU Utilization
536 | AlarmDescription: Alarm if average percentage of Nodes CPU utilization goes over 90%
537 | Dimensions:
538 | - Name: CacheClusterId
539 | Value: !Ref RedisReplicationGroup
540 |
541 | CacheEvictionsAlarm:
542 | Type: AWS::CloudWatch::Alarm
543 | Properties:
544 | Period: 60
545 | Threshold: 0
546 | Statistic: Sum
547 | EvaluationPeriods: 3
548 | MetricName: Evictions
549 | Namespace: AWS/ElastiCache
550 | TreatMissingData: notBreaching
551 | ComparisonOperator: GreaterThanThreshold
552 | AlarmName: Redis Cache Cluster Evictions
553 | AlarmDescription: Alarm if cache is evicting non-expired items
554 | Dimensions:
555 | - Name: CacheClusterId
556 | Value: !Ref RedisReplicationGroup
557 |
558 | CacheSwapUsageAlarm:
559 | Type: AWS::CloudWatch::Alarm
560 | Properties:
561 | Period: 300
562 | Statistic: Maximum
563 | Threshold: 50000000
564 | DatapointsToAlarm: 5
565 | EvaluationPeriods: 5
566 | MetricName: SwapUsage
567 | Namespace: AWS/ElastiCache
568 | ComparisonOperator: GreaterThanThreshold
569 | AlarmName: Redis Cache Cluster Swap Usage
570 | AlarmDescription: Alarm if cache swap usage exceeds 50MB
571 | Dimensions:
572 | - Name: CacheClusterId
573 | Value: !Ref RedisReplicationGroup
574 |
575 | Outputs:
576 | VPC:
577 | Value: !Ref VPC
578 | Description: The created VPC
579 |
580 | PublicSubnets:
581 | Description: A list of the public subnets
582 | Value: !Join [",", [!Ref PublicSubnet1, !Ref PublicSubnet2]]
583 |
584 | PrivateSubnets:
585 | Description: A list of the private subnets
586 | Value: !Join [",", [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
587 |
588 | PublicSubnet1:
589 | Value: !Ref PublicSubnet1
590 | Description: A reference to the public subnet in the 1st Availability Zone
591 |
592 | PublicSubnet2:
593 | Value: !Ref PublicSubnet2
594 | Description: A reference to the public subnet in the 2nd Availability Zone
595 |
596 | PrivateSubnet1:
597 | Value: !Ref PrivateSubnet1
598 | Description: A reference to the private subnet in the 1st Availability Zone
599 |
600 | PrivateSubnet2:
601 | Value: !Ref PrivateSubnet2
602 | Description: A reference to the private subnet in the 2nd Availability Zone
603 |
604 | NoIngressSecurityGroup:
605 | Value: !Ref NoIngressSecurityGroup
606 | Description: Security group with no ingress rule
607 |
608 | RedisSecurityGroup:
609 | Value: !Ref RedisSecurityGroup
610 | Description: Security group for ElastiCache Redis
611 |
612 | RedisPrimaryEndpointAddress:
613 | Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Address
614 | Description: Primary Endpoint Address for the ElastiCache Redis Cluster
615 |
616 | RedisPrimaryEndpointPort:
617 | Value: !GetAtt RedisReplicationGroup.PrimaryEndPoint.Port
618 | Description: Primary Endpoint Port for the ElastiCache Redis Cluster
619 |
620 | WebSocketEndpoint:
621 | Description: URL for making WebSocket connections to the application's API
622 | Value: !Sub "wss://${WebSocket}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
623 |
--------------------------------------------------------------------------------