Soto 5.0 Internals
Following on from my article detailing some of the new features in Soto 5, the swift SDK for AWS. I thought I would go into some of the internal changes in Soto from version 4 to version 5. Some of these changes you will notice, as even though they are internal, they still affect APIs, some of them have new dependencies and some you possibly wouldn't even notice.
In version 4.0 we had our own internally developed HTTP client. It was quite a simple client but for most cases it covered what was needed. With 5.0 we have moved to using the swift-server developed AsyncHTTPClient. This is a much more heavily tested client and is considerably more configurable. The client has also allowed us to support streaming of request and response payloads.
When creating your AWS client
AWSClient you can either provide your own instance of a
AsyncHTTPClient.HTTPClient or let the
AWSClient create one for you. Reason to supply your own HTTP client could be
- You need your HTTP client configured in a particular manner. Maybe you are behind a proxy, or want response decompression.
- You want an HTTP client that uses a global
- You want to use a common HTTP client across all your services.
The HTTP client you provide does not have to be the
AsyncHTTPClient. The client just has to conform to protocol
AWSHTTPClient. This means you can write your own or use another client that conforms to this protocol. I wouldn't necessarily recommend it though.
Early in 2020 Apple released swift-crypto an open source implementation of the Apple framework CryptoKit. Previously swift server applications generally relied on a version of OpenSSL being available. This caused all sorts of issues as different versions of the OpenSSL library have different APIs, specifically v1.1 changed the way HMAC contexts were allocated. With
swift-crypto the OpenSSL requirement was removed and this simplified including some form of encryption in your library.
There was one gotcha though that, I wasn't really prepared to accept at the time.
swift-crypto requires macOS 10.15 and iOS 13.0 or later. I want to keep Soto open to as many users as possible. Therefore I have created a library
SotoCrypto which uses
swift-crypto on Linux and provides the same interfaces on macOS and iOS but uses
CommonCrypto which doesn't have the same platform requirements.
You could move between Soto v4 and v5 without noticing any real change in how requests are encoded and responses are decoded. It is all internal to the client. But there have been a number of major changes under the hood that are worth discussing.
A number of AWS services use query strings for passing parameters. In Soto 5 the query string construction has been replaced with a
Encoder. This was added to ensure consistency in request encoding across all systems. All systems be they JSON, XML or query are now using
Codable to encode requests and decode responses.
Coding Property Wrappers
The XML and query string encoding/decoding has many ways in which collection objects can be formatted. For example Arrays can be an formatted as a list of sibling XML nodes all with the name of the array or they can be a single node named after the array that contains a list of children nodes all with an arbitrary name (most likely "member" but not always). Dictionaries have even more configurations.
In v4.0 of the sdk each input or output shape had additional data attached to it to indicate how to format collection objects and the code that did this formatting was internal to the
XMLDecoder and the query encoder. This made them complex, difficult to debug and code was duplicated.
In v5.0 this code was removed from the
Decoder and the formatting of collections is controlled by a couple of property wrappers and a series of encoding/decoding objects. The two property wrappers are
OptionalCustomCoding, one for non-optional objects and one for optional objects. Each of these hold a
CustomCoder which can conform to either
CustomDecoder. When the property wrapper is encoded it will call
CustomEncoder.encode to apply the custom encoding. Similarly there is a
CustomDecoder.decode function for custom decoding. I then wrote
CustomEncoders/Decoders for Arrays and Dictionaries. With these, mark up for an array could be as follows
@Coding<DefaultArrayCoder> public var actionName: [String]
DefaultArrayCoder formats the XML to be a node named after the array, in this case "actionName", containing multiple nodes named "member", one for each array entry.
Much of the Codable property wrapper work was inspired by the Paul Fechner CodableWrapper project.
Sometimes network communication fails. There are untold number of reasons a connection to a server might not be successful. Given this is a possibility many systems add in the ability to retry a connection if it fails and this is the case with Soto 5. When creating your
AWSClient you can provide a
RetryPolicyFactory. This will generate a
RetryPolicy for the client. Possible
RetryPolicies include no retries, exponential and jitter.
- No retries is pretty self explanatory.
- Exponential means the wait time between each retry increases exponentially. This is standard practice to ensure a client doesn't hammer a server with requests if it keep failing.
- Jitter is similar to exponential but add a random element to the amount of increase in wait time. This avoids the issue where multiple clients all hit the server at the same time then continue to do so with each retry.
Jitter is the method recommended by Amazon. Given that, it is the default
AWSClient. If you want to override this and not have any retries you can create your
AWSClient as follows.
let client = AWSClient(retryPolicy: .noRetry, ...)