Better Programming

Advice for programmers.

Follow publication

Serverless Swift Cloud Compute With AWS Lambda

Kevin Hinkson
Better Programming
Published in
8 min readJan 19, 2023
Photo by panumas nikhomkhai from Pexels

Using Swift in the AWS Cloud can take many forms, but we will focus on a serverless, multiregional setup. The aim here is to explain what goes into the setup and demonstrate how the pieces fit together. You will come away with a working understanding of what it takes to build a serverless, multiregional Swift-based API served by Lambda.

You can jump straight into the setup, where you can find the guide, AWS Cloudformation templates, and Swift code related to this article on GitHub as the aws-swift-multiregion-api.

Getting Started

With any serious attempt to build in the cloud, the idea of limits is one of the first walls you slam your head against. It sounds antithetical to the entire concept of the cloud. It’s meant to scale and scale and scale some more. However, the cloud is built with actual hardware and run by humans. All of which have limits.

On AWS, one of the first areas to get familiar with when working with a new system is the AWS Service Quotas. Each service in each region has a mix of default quotas. Some of these are fixed, and some can change. It’s important to know these ahead of time, as you will need to design your scalable system around these limits. You will also need to stay ahead of your traffic in requesting increases to the soft quotas. For any service, you can find the limits in the Service Quotas console on your account.

Understanding the Infrastructure

At a high level, our multiregional Lambda API will look like this:

Swift multiregional Lambda API infrastructure diagram

User requests will first be routed to the nearest region by latency. This is handled at the DNS level by AWS Route53. Route53 constantly monitors Internet traffic keeping track of latency from different networks. It knows how to route requests to the fastest responding region intelligently. In our setup, we provide two (2) domains that map to each region.

  1. The main API domain automatically routes you to the nearest region.
  2. A region-specific domain routing you a single given region, regardless of latency.

When the request gets to the region, AWS API Gateway takes over. API Gateway is a monster of a product with many features and a large set of documentation.

Edge-Optimized vs. Regional Endpoints

To configure an API gateway, a good place to start is with the endpoint type, as it governs your design considerations. For our purposes, we chose Regional API endpoints. This setup matches our goal of routing users to the fastest-responding region nearby. It works well for mobile users and server-to-server API interactions and reduces connection overhead. Edge-optimized endpoints are best for geographically distributed clients routed to a single region.

REST vs. HTTP APIs

Once the endpoint type is chosen, the next step is between REST and HTTP APIs. Both are RESTful API products, but REST APIs support far more features than the HTTP API. However, HTTP APIs have all of the functionality most APIs will use. By being less featureful, they also perform with less latency than REST APIs and, by consuming fewer compute resources, are less expensive. So if you are just starting and do not need some of the more advanced features of REST APIs, HTTP V2 APIs are the way to go.

The API Gateway

The API gateway has many components, each configurable in many ways. In this instance, we went with as simple a setup as possible. Here I will briefly outline each component making up the gateway and how it can be used.

APIs define the endpoint type and protocol used. They act as a wrapping container for all the other elements.

Stages are logical references to a lifecycle state of an API. For example, running stages v1 and v2 would allow you to run and change multiple versions of your API side by side without impacting each other.

Routes direct incoming API requests to backend resources based on the HTTP method and the resource path.

Authorizers control and manage access to your HTTP API. API gateway supports multiple authorizer types, including Lambda, JWT (OIDC and OAuth 2.0), and IAM.

Integrations connect a route to backend resources, whether Lambda, AWS Services, HTTP, or private integrations, allowing you to connect with resources in a Virtual Private Cloud (VPC).

Custom domain names allow you to use your own domain name for the API rather than the randomized assigned name from AWS.

In the example templates and code, there is only one default stage, a default route, a single Lambda authorizer, and a single integration, but there are two (2) custom domain names. One domain handles the latency routing, and one maps to a specific region.

Lambda Authorizer

In the example on GitHub, we provide the option to have an open or authorized access-only API. With the authorized access-only option, a custom Lambda Authorizer is used. This means that before API requests can be processed, they are routed to a separate Lambda that only handles authorization.

/** APIGateway to Lambda Authorizer Payload Format **/
{
"version": "2.0",
"type": "REQUEST",
"routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request",
"identitySource": ["user1", "123"],
"routeKey": "$default",
"rawPath": "/my/path",
"rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
"cookies": ["cookie1", "cookie2"],
"headers": {
"Header1": "value1",
"Header2": "value2"
},
"queryStringParameters": {
"parameter1": "value1,value2",
"parameter2": "value"
},
"requestContext": {
"accountId": "123456789012",
"apiId": "api-id",
"authentication": {
"clientCert": {
"clientCertPem": "CERT_CONTENT",
"subjectDN": "www.example.com",
"issuerDN": "Example issuer",
"serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
"validity": {
"notBefore": "May 28 12:30:02 2019 GMT",
"notAfter": "Aug 5 09:36:04 2021 GMT"
}
}
},
"domainName": "id.execute-api.us-east-1.amazonaws.com",
"domainPrefix": "id",
"http": {
"method": "POST",
"path": "/my/path",
"protocol": "HTTP/1.1",
"sourceIp": "IP",
"userAgent": "agent"
},
"requestId": "id",
"routeKey": "$default",
"stage": "$default",
"time": "12/Mar/2020:19:03:58 +0000",
"timeEpoch": 1583348638390
},
"pathParameters": { "parameter1": "value1" },
"stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" }
}

We use the simple version of the format to send a response back. This means we only have to send back true or false in the isAuthorized parameter to the API gateway to signal if the challenge succeeded. The simplified format also allows a successful challenge to pass custom data in the context parameter back to the API lambda.

For example, we might want to pass the exact level of permissions and other metadata to the API. Authorization requests can be cached by the API gateway for up to an hour, reducing the need to constantly roundtrip to the Lambda Authorizer.

/** Lambda Authorizer Simple Response Format **/
{
"isAuthorized": true/false,
"context": {
"exampleKey": "exampleValue"
}
}

Once authorized, the request is forwarded to the API Lambda for processing.

/** API Gateway to Lambda API Integration Request Format **/
{
"version": "2.0",
"routeKey": "$default",
"rawPath": "/my/path",
"rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
"cookies": [
"cookie1",
"cookie2"
],
"headers": {
"header1": "value1",
"header2": "value1,value2"
},
"queryStringParameters": {
"parameter1": "value1,value2",
"parameter2": "value"
},
"requestContext": {
"accountId": "123456789012",
"apiId": "api-id",
"authentication": {
"clientCert": {
"clientCertPem": "CERT_CONTENT",
"subjectDN": "www.example.com",
"issuerDN": "Example issuer",
"serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
"validity": {
"notBefore": "May 28 12:30:02 2019 GMT",
"notAfter": "Aug 5 09:36:04 2021 GMT"
}
}
},
"authorizer": {
"jwt": {
"claims": {
"claim1": "value1",
"claim2": "value2"
},
"scopes": [
"scope1",
"scope2"
]
}
},
"domainName": "id.execute-api.us-east-1.amazonaws.com",
"domainPrefix": "id",
"http": {
"method": "POST",
"path": "/my/path",
"protocol": "HTTP/1.1",
"sourceIp": "192.0.2.1",
"userAgent": "agent"
},
"requestId": "id",
"routeKey": "$default",
"stage": "$default",
"time": "12/Mar/2020:19:03:58 +0000",
"timeEpoch": 1583348638390
},
"body": "Hello from Lambda",
"pathParameters": {
"parameter1": "value1"
},
"isBase64Encoded": false,
"stageVariables": {
"stageVariable1": "value1",
"stageVariable2": "value2"
}
}

As you can see, Lambda has easy access to the request data, stageVariables, authorizer context, and routing information specific to the API gateway configuration. Once the lambda is finished processing, it sends a specially formatted response back.

/** Lambda API Integration Response to API Gateway **/
{
"isBase64Encoded": false,
"statusCode": 200,
"body": "{ \"message\": \"Hello from Lambda!\" }",
"headers": {
"content-type": "application/json"
}
}

A few projects allow you to do all this in Swift. Here’s some of them:

  • Swift AWS Runtime is an AWS Lambda Runtime API implementation in Swift and makes building Lambda functions in Swift simple and safe.
  • Soto is a Swift language SDK for Amazon Web Services (AWS), working on Linux, macOS, and iOS and providing access to all AWS services.
  • Vapor — although not used in this project, it receives special mention as a framework for writing server applications, HTTP services, and backends in Swift. Most general Swift server-side development depends on one Vapor package or another, and it can be used to do everything from routing to running a blog to connecting to MySQL, etc.

These packages will get you started on your journey in using Swift on the Server and in the Cloud. There are many more libraries for you to discover as the Swift community grows daily.

A Live Example

The related Swift code and templates of this project can be found at aws-swift-multiregion-api on GitHub. The APIs, both open and requiring authorization, have been set up for anyone who wants to see the latency. These are here to give you an idea of what you can expect from this setup. Both APIs were installed on a fresh AWS account following the exact instructions here.

Note: If these APIs get a lot of abuse, I will be forced to take them down. For these quick tests, both APIs were warmed up first by running a few requests, and the authorizer was and is set to cache its results for an hour. This test run was not comprehensive. Your results will vary.

A successful response looks like this and should contain the region, so you know which one you were routed to.

/** JSON API Request & Response Auto-Routed to AWS Region us-east-2 (Ohio) **/
curl "https://openapi.swiftdemo0.flew.cloud/hello"
{
"created": "2023-01-11T18:49:01.386Z",
"description": "We said hello.",
"id": "2806E285-ACA9-4E3E-9045-6067334307E8",
"region": "us-east-2",
"title": "Hello"
}

Open Access SwiftLambdaAPI

From an EC2 instance in us-east-2 (Ohio) to the Open SwiftLambdaAPI in us-east-2 (Ohio) — approximately 37ms to get a response.

curl -w "@curl-format.txt" \
-o /dev/null \
-s "https://openapi.swiftdemo0.flew.cloud/hello"

DNS Lookup: 0.001086s
Remote Host Connect: 0.001606s
SSL Handshake: 0.010577s
Pretransfer Setup: 0.010671s
Any Redirects: 0.000000s
First Byte Transfer: 0.037326s
----------
Total: 0.037417s

Authorized Access SwiftLambdaAPI

From an EC2 instance in us-east-1 (N. Virginia) to the authorization required SwiftLambdaAPI in us-east-1 (N. Virginia) — approximately 40ms to get a response.

curl -w "@curl-format.txt" \
-o /dev/null \
-s "https://authapi.swiftdemo0.flew.cloud/hello" \
-H 'Content-Type: application/json' \
-H 'Authorization: SKOcSFZd8pKoZ24WXYK2dSEc5Nf2eao9QOOvpPYM'

DNS Lookup: 0.001171s
Remote Host Connect: 0.002046s
SSL Handshake: 0.011209s
Pretransfer Setup: 0.011324s
Any Redirects: 0.000000s
First Byte Transfer: 0.040475s
----------
Total: 0.040564s

Next Steps

As mentioned above, the code and templates related to this project can be found at aws-swift-multiregion-api on GitHub. The next article in this Swift in the Cloud Series deals with DynamoDB — a multiregional database (key value NOSQL database) that you can use across regions in conjunction with the multiregional lambda API.

Want to Connect?

If this article and the corresponding project interest you,
join our mailing list for more great content like this.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Kevin Hinkson
Kevin Hinkson

Written by Kevin Hinkson

Kevin is an experienced software developer who works mainly with start up companies and understands the unique perspective required. Twitter: @kevin_hinkson

No responses yet

Write a response