<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Serverless on Stuart Forrest</title><link>https://www.uglydirtylittlestrawberry.co.uk/categories/serverless/</link><description>Recent content in Serverless on Stuart Forrest</description><generator>Hugo -- gohugo.io</generator><language>en-uk</language><lastBuildDate>Tue, 07 Sep 2021 05:33:23 +0000</lastBuildDate><atom:link href="https://www.uglydirtylittlestrawberry.co.uk/categories/serverless/" rel="self" type="application/rss+xml"/><item><title>Hugo pretty URLs - migrating from Lambda@Edge to Cloudfront Functions</title><link>https://www.uglydirtylittlestrawberry.co.uk/posts/pretty-urls-cloudfront-functions/</link><pubDate>Tue, 07 Sep 2021 05:33:23 +0000</pubDate><guid>https://www.uglydirtylittlestrawberry.co.uk/posts/pretty-urls-cloudfront-functions/</guid><description>&lt;p>This post is intended to share the process of migrating URL prettifying functionality for a static site hosted in AWS to Cloudfront Functions from Lambda@Edge and deploying them with Serverless.&lt;/p>
&lt;ul>
&lt;li>&lt;a href="#a-bit-of-background">Some background on my site&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-are-cloudfront-functions">What are Cloudfront Functions?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-is-the-serverless-framework">What is the Serverless Framework?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#migrating-pretty-urls-from-lambdaedge-to-cloudfront-functions">Migrating from Lambda@Edge to Cloudfront Functions&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="a-bit-of-background">A bit of background&lt;/h2>
&lt;p>This website is built using a static site generator called &lt;a href="https://gohugo.io/">Hugo&lt;/a>, deployed using the Serverless Framework and hosted in AWS using &lt;a href="https://aws.amazon.com/s3/">S3 for storage&lt;/a>, &lt;a href="https://aws.amazon.com/cloudfront/">Cloudfront as a CDN&lt;/a> and up until recently &lt;a href="https://aws.amazon.com/lambda/edge/">Lambda@Edge&lt;/a>. At build time all content is rendered into static HTML and CSS files, there is no client side magic that something like React provides. Hugo produces an &lt;code>index.html&lt;/code> for every post in it&amp;rsquo;s own folder, I was using Lambda@Edge to transform more friendly paths like &lt;code>/some-fancy-post-name&lt;/code> into &lt;code>/some-fancy-post-name/index.html&lt;/code> that S3 understands so that the content can actually be found. It is nothing fancy, just appending &lt;code>index.html&lt;/code> to any path that doesn&amp;rsquo;t have it.&lt;/p>
&lt;h2 id="what-are-cloudfront-functions">What are Cloudfront Functions?&lt;/h2>
&lt;p>&lt;a href="https://aws.amazon.com/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/">Cloudfront
Functions&lt;/a>,
whilst not a successor to Lambda@Edge, are an evolution of them. Whereas Lambda@Edge allows fully fledged lambda
functions to run within a regional edge cache (AWS region) closest to the request origin, Cloudfront Functions run right
at the edge where the content is ideally going to be served from. In terms of Cloudfront architecture you cannot get
closer to the user. There are some &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html">restrictions to Cloudfront
Functions&lt;/a>
you have to bear in mind like having a limited runtime, execution time, memory allowance, package size and that they are
javascript only with no network access.&lt;/p>
&lt;h2 id="what-is-the-serverless-framework">What is the Serverless Framework?&lt;/h2>
&lt;p>The &lt;a href="https://serverless.com/">Serverless Framework&lt;/a> is an infrastructure-as-code tool that simplifies the defining and deployment of mostly event driven serverless architectures. When used with AWS it is an abstraction over &lt;a href="https://aws.amazon.com/cloudformation/">Cloudformation&lt;/a> and does a lot of the heavy lifting to create and wire up resources like lambdas, queues, apis and events. As it compiles down to Cloudformation it can support other resources too but these are defined in &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html">regular Cloudformation syntax&lt;/a>.&lt;/p>
&lt;h2 id="migrating-pretty-urls-from-lambdaedge-to-cloudfront-functions">Migrating pretty URLs from Lambda@Edge to Cloudfront Functions&lt;/h2>
&lt;p>Migrating the Lambda@Edge that handled pretty URLs to a Cloudfront Function was pretty straight forward. The code itself went from this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">exports&lt;/span>.&lt;span style="color:#a6e22e">handler&lt;/span> &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#a6e22e">event&lt;/span>, &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>&lt;span style="color:#a6e22e">_&lt;/span>, &lt;span style="color:#a6e22e">callback&lt;/span>) =&amp;gt; {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">event&lt;/span>.&lt;span style="color:#a6e22e">Records&lt;/span>[&lt;span style="color:#ae81ff">0&lt;/span>].&lt;span style="color:#a6e22e">cf&lt;/span>.&lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> RegExp(&lt;span style="color:#e6db74">&amp;#34;/$&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">match&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span>.&lt;span style="color:#a6e22e">exec&lt;/span>(&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">match&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">`&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">index.html`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">callback&lt;/span>(&lt;span style="color:#66d9ef">null&lt;/span>, &lt;span style="color:#a6e22e">request&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>to this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">function&lt;/span> &lt;span style="color:#a6e22e">handler&lt;/span>(&lt;span style="color:#a6e22e">event&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">event&lt;/span>.&lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">new&lt;/span> RegExp(&lt;span style="color:#e6db74">&amp;#34;/$&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">match&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">indexPath&lt;/span>.&lt;span style="color:#a6e22e">exec&lt;/span>(&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#a6e22e">match&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">newURL&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">`&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>&lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span>&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">index.html`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">request&lt;/span>.&lt;span style="color:#a6e22e">uri&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">newURL&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#a6e22e">request&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>As you can see very little changed, sourcing the value for &lt;code>request&lt;/code> was the only functional change to the code required. Some other changes were required because of the restricted runtime for Cloudfront Functions:&lt;/p>
&lt;ul>
&lt;li>No exports allowed!&lt;/li>
&lt;li>No &lt;code>let&lt;/code> or &lt;code>const&lt;/code>, not used good old fashioned hoistable &lt;code>var&lt;/code> in a while&lt;/li>
&lt;/ul>
&lt;p>The &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html">feature availability&lt;/a> in the Cloudfront Function runtime is a real jumble of pre-ES5 plus a bit of ES6, ES7 and beyond. And no exports! I was keen to unit test these functions and the lack of exports presented a challenge, I will write up how I went about that in a future post.&lt;/p>
&lt;p>Cloudfront Functions code is also required to be inline in the CloudFormation template. This isn&amp;rsquo;t great from a testability and IDE syntax linting/hinting/etc points of view. Also, only one function per trigger is allowed which makes reusability of the code a challenge. For example with my development version of the site I expected to be able to use two Functions in a pipeline on the &lt;code>viewer-request&lt;/code> trigger, one for HTTP Basic Auth and one for pretty URLs, then only pretty URLs in production. Alas, the code had to be duplicated, and this limitation already exists with Lambda@Edge.&lt;/p>
&lt;p>For the inline code problem, I was fortunate enough to be using a Typescript file to define my Serverless config instead of &lt;code>yml&lt;/code> so I just read an external file containing the full code for my Cloudfront Function:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-js" data-lang="js">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">fs&lt;/span>.&lt;span style="color:#a6e22e">readFileSync&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;./lambdas/cfFunctions/prettyUrls.js&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;utf8&amp;#34;&lt;/span>)&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This worked like a charm but is there an alternative? Yes, use CDK, which handles reading the code in from a file for you (&lt;a href="https://docs.aws.amazon.com/cdk/api/latest/typescript/api/aws-cloudfront/functioncode.html#aws_cloudfront_FunctionCode_fromFile">docs&lt;/a>).&lt;/p>
&lt;h2 id="summary">Summary&lt;/h2>
&lt;p>Migrating from Lambda@Edge to Cloudfront Functions was an opportunity to try something new from AWS and push some compute as close to the end user as possible. It went really well and the bulk of the time I spent doing it was spent figuring out unit testing (I wrote about that &lt;a href="https://www.uglydirtylittlestrawberry.co.uk/posts/unit-testing-cloudfront-functions/">here&lt;/a>) and a clean deployment strategy as opposed to writing the code and just getting it deployed.&lt;/p>
&lt;p>For simple uses that manipulate the request/response objects without the need for external input then Cloudfront Functions are a better alternative to Lambda@Edge, otherwise Lambda@Edge isn&amp;rsquo;t going anywhere. For those simple use cases migrating between the two is pretty straight forward though deployment and a robust development lifecycle for Cloudfront Functions can have a little bit more complexity.&lt;/p>
&lt;h3 id="get-in-contact">Get in contact&lt;/h3>
&lt;p>If you have comments, questions or better ways to do anything that I have discussed in this post then please get in contact via &lt;a href="https://linkedin.com/in/stuart-f-41a43b180">LinkedIn&lt;/a> or &lt;a href="mailto:stuart@uglydirtylittlestrawberry.co.uk">email&lt;/a>.&lt;/p></description></item><item><title>AWS API gateway websockets with Golang and Serverless framework</title><link>https://www.uglydirtylittlestrawberry.co.uk/posts/aws-api-gateway-websockets-golang-serverless/</link><pubDate>Sat, 25 Apr 2020 16:22:49 +0000</pubDate><guid>https://www.uglydirtylittlestrawberry.co.uk/posts/aws-api-gateway-websockets-golang-serverless/</guid><description>&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>AWS &lt;a href="https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/">announced API Gateway websockets&lt;/a> way back in December 2018 but I had not taken much of an interest in them until very recently. However, I recently proposed a little fun friday project at work to look at implementing websockets to push out inventory updates to clients for a very bare bones, small scale e-commerce site we recently launched. We had built in some basic inventory management but our current stock levels were kept intentionally low so a race condition of multiple customers checking out with the last of some stock is not unrealistic.&lt;/p>
&lt;p>The whole project is built on an AWS serverless stack including &lt;a href="https://aws.amazon.com/s3">S3&lt;/a>, &lt;a href="https://aws.amazon.com/dynamodb">DynamoDB&lt;/a>, &lt;a href="https://aws.amazon.com/lambda">Lambda&lt;/a> and &lt;a href="https://aws.amazon.com/api-gateway">API Gateway&lt;/a> so it was time to check out API Gateway&amp;rsquo;s websocket implementation. The codebase is written in &lt;a href="https://golang.org">Golang&lt;/a> and we practice infrastructure-as-code using the &lt;a href="https://serverless.com">Serverless Framework&lt;/a>. Luckily for us websockets enjoy &lt;a href="https://serverless.com/blog/framework-release-v138-websockets/">first class support&lt;/a> in Serverless with a simple implementation and documentation with decent coverage. Less fortunately the examples for using API Gateway websockets and Lambda are nearly all Node/Javascript (obviously) with a few Dotnet and Java stragglers (less obviously). It wasn&amp;rsquo;t terribly difficult to spin up a Golang lambda websocket demo but it would have been nice to have a complete one to reference. This is the purpose of this post and the demo repo &lt;a href="https://github.com/slcp/go-aws-websockets">attached&lt;/a>.&lt;/p>
&lt;h2 id="serverless">Serverless&lt;/h2>
&lt;p>Defining a lambda deployment with Serverless is not going to be covered in this post, there is good documentation &lt;a href="https://serverless.com/framework/docs/providers/aws/guide/intro">here&lt;/a>. Defining a lambda trigger to be sourced from a websocket is pretty much the same as any other kind of event (http/sns/sqs etc) with a few websocket specific pieces of config:&lt;/p>
&lt;script src="https://gist.github.com/stuartforrest-infinity/94e43a8a339d4b51f0207cad8add3190.js">&lt;/script>
&lt;p>Websockets are considered just another kind of &lt;a href="https://serverless.com/framework/docs/providers/aws/events/websocket/">event&lt;/a> like http or sns when defining your lambda functions.&lt;/p>
&lt;p>After looking at this gist you might have questions similar to:&lt;/p>
&lt;ul>
&lt;li>What is a route?&lt;/li>
&lt;li>What is a routeResponseSelectionExpression?&lt;/li>
&lt;li>What does $connect/$disconnect/$default represent?&lt;/li>
&lt;/ul>
&lt;p>All very valid questions but not really to do with Serverless (we will answer those next). That gist really does represent all you need to deploy the possible variants of lambdas that can be triggered by API Gateway websockets.&lt;/p>
&lt;h3 id="what-is-a-route">What is a route?&lt;/h3>
&lt;p>AWS docs &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-routes-integrations.html">here&lt;/a>. Routes provide a mechanism through which messages can be directed to different integrations, in the gist above they are being used to trigger different lambdas but they can just as easily be used to trigger other API gateway integrations. Aside from the AWS provided $connect/$disconnect/$default routes you can consider routes as arbitrary and defined at runtime. The route is specified in the &lt;code>action&lt;/code> key of the body of a websocket request.&lt;/p>
&lt;script src="https://gist.github.com/stuartforrest-infinity/e5c9a167474c4fde12eb381611915338.js">&lt;/script>
&lt;h3 id="what-do-connectdisconnectdefault-represent">What do $connect/$disconnect/$default represent?&lt;/h3>
&lt;ul>
&lt;li>API Gateway will route any messages without a target integration to the $default route. This allows you to handle the &lt;code>$default&lt;/code> route as a catch all, potentially as an exception handler if all routes in your service should be explicitly defined.&lt;/li>
&lt;li>The $connect route is triggered when a new socket is opened.&lt;/li>
&lt;li>The $disconnect route is triggered (not always reliably) when a socket is closed by a client.&lt;/li>
&lt;/ul>
&lt;h3 id="what-is-a-routeresponseselectionexpression">What is a routeResponseSelectionExpression?&lt;/h3>
&lt;p>AWS docs &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-selection-expressions.html#apigateway-websocket-api-route-response-selection-expressions">here&lt;/a>. The &lt;code>routeResponseSelectionExpression&lt;/code> in the serverless.yml configures the API Gateway/Lambda integration as a two way communication channel (as you might expect from http request). By default when a message is received from a client websocket connection is only one way. Only one route response value is currently accepted - &lt;code>$default&lt;/code> (similar to action routes), but it is designed to support custom ones at some point in the future. What this means is the body of the response from your lambda will be returned to the client over the websocket.&lt;/p>
&lt;h2 id="a-little-bit-of-pain">A little bit of pain&lt;/h2>
&lt;p>Websockets are inherently asynchronous and AWS doesn&amp;rsquo;t do much to help you manage their state across invocations, if you want to send a message to Client A at an arbitrary point in time you will have to keep a record of their &lt;code>ConnectionId&lt;/code> from when the socket was opened. It is also entirely possible that the socket to Client A has been closed, this could be by the client or by API Gateway, and whilst you might expect the $disconnect route to have been triggered this is not always going to happen. In this scenario you will find out it is closed when you try and send them a message and it will fail. So, again, you must maintain a record of this state yourself. That isn&amp;rsquo;t ideal but it is what it is. So, how do we do this? And, how can we do this in Golang?&lt;/p>
&lt;h3 id="handling-connect-and-disconnect">Handling $connect and $disconnect&lt;/h3>
&lt;p>We know that we need to maintain our own store of state for our websockets otherwise we will be limited to what we can do with them. The repo &lt;a href="https://github.com/slcp/go-aws-websockets">attached&lt;/a> to this post contains a concept DynamoDB table for this purpose with a table structure as follows:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>pk&lt;/th>
&lt;th>sk&lt;/th>
&lt;th>ID&lt;/th>
&lt;th>TTL&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&amp;ldquo;connectionID&amp;rdquo;&lt;/td>
&lt;td>[unix_timestamp&lt;N>]&lt;/td>
&lt;td>[connectionID]&lt;/td>
&lt;td>[unix_timestamp&lt;N>]&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>DynamoDB table designs are very much out of scope of this post but if you are interested in more info I highly recommend these articles by &lt;a href="https://twitter.com/adrianhesketh">Adrian Hesketh&lt;/a> (&lt;a href="https://adrianhesketh.com/2020/04/17/single-table-pattern-dynamodb-with-go-part-1">1&lt;/a>, &lt;a href="https://adrianhesketh.com/2020/04/17/single-table-pattern-dynamodb-with-go-part-2">2&lt;/a>, &lt;a href="https://adrianhesketh.com/2020/04/17/single-table-pattern-dynamodb-with-go-part-2">3&lt;/a>).&lt;/p>
&lt;p>The table above is designed for use in a simplistic scenario where we are limiting socket life to a specific time frame (for example 20 minutes). It supports a single access pattern:&lt;/p>
&lt;ul>
&lt;li>Get all connectionIDs from the last N[time unit]&lt;/li>
&lt;/ul>
&lt;p>The &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html">Time To Live&lt;/a> on the table is used to expire rows after 20 minutes. Because TTL expiration is &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/howitworks-ttl.html">not triggered immediately&lt;/a> on expiry the access pattern above allows us to also bake it into our query. The TTL does allows us to forget about removing stale connections, they will expire and get removed. There is no reason that our TTL and the condition on our &lt;code>RANGE&lt;/code> key at access time should be the same.&lt;/p>
&lt;p>On $connect, store the connectionId in the table above and use the timestamp of &lt;code>now&lt;/code> for the &lt;code>sk&lt;/code> value. In this demo, on $disconnect, we don&amp;rsquo;t really have to do anything as we have auto expiring rows and we can gracefully handle sending message failures later if the socket has been closed. Ordinarily and with a different table design, deleting the connection record would be prudent but as previously mentioned do not depend on $disconnect reliably being invoked.&lt;/p>
&lt;script src="https://gist.github.com/stuartforrest-infinity/43f2f6bfcd4028bedb994107eb33a87f.js">&lt;/script>
&lt;h2 id="doing-the-golang">Doing the Golang&lt;/h2>
&lt;h3 id="one-way-communication">One way communication&lt;/h3>
&lt;p>The gist below demonstrates one way communication via websocket in Golang. The lambda handler function signature is just as if it was being triggered by a http request except for not requiring a response return parameter. The &lt;code>WebSocketRequestBody&lt;/code> struct is the format of all incoming websocket request bodies. If action is not set API Gateway will reject the request and if there is no data set then there is no message. Use the connectionID to identify the client or correlate messages.&lt;/p>
&lt;script src="https://gist.github.com/stuartforrest-infinity/afbcf6e35b41028ce65957c83f7471e0.js">&lt;/script>
&lt;h3 id="two-way-communication">Two way communication&lt;/h3>
&lt;p>The gist below demonstrates two way communication via websocket in Golang. The lambda handler function signature is just as if it was being triggered by a http request including a response return parameter. API Gateway will return the body of the response to client over the socket as the message only if a &lt;code>2xx&lt;/code> status code is returned, otherwise it fail. This process is almost identical to managing a http request. Use the connectionID to identify the client or correlate messages.&lt;/p>
&lt;script src="https://gist.github.com/stuartforrest-infinity/99c7932e408104483645bdbf8ceb2cfa.js">&lt;/script>
&lt;h3 id="async-communication">Async communication&lt;/h3>
&lt;p>The gist below demonstrates async communication via websocket in Golang. For demonstration purposes this sends a message to socket client off the back of an SNS message but this could be happening anytime, anywhere. This can also be invoked by the CLI or the HTTP API. In the context of a lambda the message is easiest sent by using the &lt;code>APIGatewayManagementAPI&lt;/code> package from the AWS SDK using the socket endpoint for the gateway the client is using. Correlating clients to connectionIDs in this scenario could be built into the data store we saw earlier or included in any messages as a correlation Id. When using &lt;code>APIGatewayManagementAPI&lt;/code> in this context it may return a 410 error which signals that the socket has been closed. The unreliablility of the $disconnect action noted above means that stale or killed connections must be robustly handled here.
&lt;script src="https://gist.github.com/stuartforrest-infinity/f42d5667955247422bfff43a99cccdba.js">&lt;/script>&lt;/p>
&lt;h2 id="the-end">The end&lt;/h2>
&lt;p>The github repo with a sample implementation of all of this is &lt;a href="https://github.com/slcp/go-aws-websockets">here&lt;/a>. it goes far beyond the gists above so please do take a deeper dive for better idea of what has been described. It is intended to be used as a rough template for a Golang/Serverless implementation that requires the use of API Gateway websockets.&lt;/p>
&lt;h3 id="get-in-contact">Get in contact&lt;/h3>
&lt;p>If you have comments, questions or better ways to do anything that I have discussed in this post then please get in contact via &lt;a href="https://linkedin.com/in/stuart-f-41a43b180">LinkedIn&lt;/a> or &lt;a href="mailto:stuart@uglydirtylittlestrawberry.co.uk">email&lt;/a>.&lt;/p></description></item></channel></rss>