├── README.md ├── process └── incubation.graffle ├── proposals ├── 0000-template.md ├── 0001-logging-api.md ├── 0002-metrics-api.md ├── 0003-nio-postgres.md ├── 0004-nio-redis.md ├── 0005-nio-http-client.md ├── 0006-nio-apns-api.md ├── 0007-statsd-client.md ├── 0008-swift-prometheus.md ├── 0009-swift-crypto.md ├── 0010-mongo-swift-driver.md ├── 0011-openapikit.md ├── 0013-swift-aws-lambda-runtime.md ├── 0014-swift-backtrace.md ├── 0015-swift-service-lifecycle.md ├── 0016-soto.md ├── 0017-multipart-kit.md ├── 0018-mqtt-nio.md ├── 0019-graphql.md ├── 0020-distributed-actor-cluster.md ├── 0021-swift-cassandra-client.md ├── 0022-sqlite-nio.md ├── 0023-discordbm.md ├── 0024-service-context.md ├── 0025-swift-distributed-tracing.md ├── 0026-swift-openapi-generator.md ├── 0027-mongokitten.md ├── 0028-oracle-nio.md ├── 0029-jwsetkit.md ├── 0030-jwtkit.md ├── 0031-vapor.md ├── 0032-hummingbird.md └── 0033-swift-asn1.md └── reviews └── .gitkeep /README.md: -------------------------------------------------------------------------------- 1 | # Swift Server Work Group (SSWG) 2 | 3 | The Swift Server work group is a steering team that promotes the use of Swift for developing and deploying server applications. The Swift Server work group will: 4 | 5 | * Define and prioritize efforts that address the needs of the Swift server community 6 | * Define and run an incubation process for these efforts to reduce duplication of effort, increase compatibility and promote best practices 7 | * Channel feedback for Swift language features needed by the server development community to the Swift Core Team 8 | 9 | Analogous to the Core Team for Swift, the work group is responsible for providing overall technical direction and establishing the standards by which libraries and tools are proposed, developed and eventually recommended. 10 | 11 | The current list of Swift Server work group members can be [found on the Swift website](https://www.swift.org/sswg/). 12 | 13 | ## Communication 14 | 15 | The Swift Server work group uses the [Swift Server forum](https://forums.swift.org/c/server) for general discussion. 16 | 17 | ## Community Participation 18 | 19 | Everyone is welcome to contribute in the following ways: 20 | 21 | * Proposing new libraries and tools to be considered 22 | * Participating in design discussions 23 | * Asking or answering questions on the forums 24 | * Reporting or triaging bugs 25 | * Submitting pull requests to the library projects for implementation or tests 26 | 27 | These conversations will take place on the [Swift Server forum](https://forums.swift.org/c/server). Over time, the work group may form smaller working groups to focus on specific technology areas. 28 | 29 | ## Work Group Membership 30 | 31 | Membership in the work group is contribution-based and expected to evolve over time. Adding new members and removing inactive ones is subject to a SSWG vote and requires unanimous agreement. A cap of two members per company is in place to avoid overweight representation. A cap of ten members total is in place to keep the group small enough to be effective. Membership term is capped at 2 years, but exiting members may re-apply at the end of their term. When multiple candidates compete for the same seat, the SSWG will vote between all candidates, with a final voting round between the two candidates that received most votes in the first round. 32 | 33 | Companies or individuals that would like to join the SSWG should apply by posting a request to the [Swift Server forum](https://forums.swift.org/c/server). Applicants will then be invited to the next available SSWG meeting to present their case. 34 | 35 | Inactive members that do not participate in four consecutive SSWG meetings will be contacted to confirm their desire to stay with the group. After missing ten consecutive meetings, the SSWG will vote on removing them from the group. 36 | 37 | ## Charter 38 | 39 | The main goal of the Swift Server work group is to eventually recommend libraries and tools for server application development with Swift. The difference between this work group and the Swift Evolution process is that server-oriented libraries and tools that are produced as a result of work group efforts will exist outside of the Swift language project itself. The work group will work to nurture, mature and recommend projects as they move into their development and release stages. 40 | 41 | ## Voting 42 | 43 | In various situations the SSWG shall hold a vote. These votes can happen on the phone, email, or via a voting service, when appropriate. SSWG members can either respond "agree, yes, +1", "disagree, no, -1", or "abstain". A vote passes with two-thirds vote of votes cast based on the SSWG charter. An abstain vote equals not voting at all. 44 | 45 | ## Incubation Process 46 | 47 | See [Incubation Process](https://swift.org/sswg/incubation-process.html) 48 | 49 | ## Projects 50 | 51 | See [Swift Server Ecosystem Index](https://swift.org/server/#projects) 52 | 53 | ## Security 54 | 55 | Please refer to our [Security](https://swift.org/sswg/security/) page for matters related to security of any software affiliated with the SSWG or listed on the SSWG's package index. 56 | 57 | ## Meeting Time 58 | 59 | The SSWG meets on the 1st and 3rd Wednsday of every month at 9:00AM PT (USA Pacific) 60 | 61 | ## Meeting Notes 62 | 63 | All meeting notes are posted to the [Swift Server forums](https://forums.swift.org/c/server/workgroup-meeting-notes). 64 | -------------------------------------------------------------------------------- /process/incubation.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swift-server/sswg/1eac5c50a530adc85fb306762f0c869b4e5447be/process/incubation.graffle -------------------------------------------------------------------------------- /proposals/0000-template.md: -------------------------------------------------------------------------------- 1 | # Solution name 2 | 3 | * Proposal: [SSWG-NNNN](NNNN-filename.md) 4 | * Authors: [Author 1](https://github.com/swiftdev), [Author 2](https://github.com/swiftdev) 5 | * Review Manager: TBD 6 | * Status: **Awaiting implementation** 7 | * Implementation: [repo name](https://github.com/repo-url) 8 | * Forum Threads: [Pitch](https://forums.swift.org/), [Discussion](https://forums.swift.org/), [Review](https://forums.swift.org/) 9 | 10 | *During the review process, add the following fields as needed:* 11 | 12 | * Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) 13 | * Previous Revision(s): [1](https://github.com/swift-server/sswg/blob/...commit-ID.../proposals/NNNN-filename.md) 14 | * Previous Proposal(s): [SSWG-XXXX](XXXX-filename.md) 15 | 16 | ## Package Description 17 | A quick 1-2 sentence description of the solution's package, like you might see in a package catalog. 18 | 19 | | | | 20 | |--|--| 21 | | **Package Name** | `your proposed package name, eg. swift-package` | 22 | | **Module Name** | `your formatted module name, eg. SwiftPackage` | 23 | | **Proposed Maturity Level** | [Sandbox](https://www.swift.org/sswg/incubation-process.html#process-diagram) | 24 | | **License** | [TBD](https://choosealicense.com/) | 25 | | **Dependencies** | List your dependencies (and their versions), with links | 26 | 27 | ## Introduction 28 | 29 | A short explanation as to what is hoping to be accomplished with the proposed package. Try to keep it to a 30 | single-paragraph "elevator pitch" so the reader understands what 31 | problem this proposal is addressing. 32 | 33 | ## Motivation 34 | 35 | Describe the reasoning for this package to be proposed to the Swift Server Working Group to be recommended for use across the Swift Server ecosystem. 36 | 37 | If there are missing capabilities or flexibility with existing packages, outline them with clear examples. 38 | 39 | ## Proposed solution 40 | 41 | Describe your solution to the problem. Provide examples and describe 42 | how they work. Show how your solution is better than current 43 | workarounds: is it cleaner, safer, or more efficient? 44 | 45 | ## Detailed design 46 | 47 | Describe the design of the solution in detail. If it's a new API, show the full API and its documentation 48 | comments detailing what it does. The detail in this section should be 49 | sufficient for someone who is *not* one of the authors to be able to 50 | reasonably re-implement the feature. 51 | 52 | ## Maturity Justification 53 | 54 | Explain why this solution should be accepted at the proposed maturity level. 55 | 56 | ## Alternatives considered 57 | 58 | Describe alternative approaches to addressing the same problem, and 59 | why you chose this approach instead. 60 | -------------------------------------------------------------------------------- /proposals/0002-metrics-api.md: -------------------------------------------------------------------------------- 1 | # Server Metrics API 2 | 3 | * Proposal: [SSWG-0002](https://github.com/swift-server/sswg/blob/master/proposals/SSWG-0002.md) 4 | * Authors: [Tomer Doron](https://github.com/tomerd) 5 | * Sponsor(s): Apple 6 | * Review Manager: [Tanner Nelson](https://github.com/tanner0101) 7 | * Status: **Accepted as Sandbox Maturity** 8 | * Implementation: [tomerd/swift-server-metrics-api-proposal](https://github.com/tomerd/swift-server-metrics-api-proposal/) 9 | * Forum Threads: [Pitch](https://forums.swift.org/t/metrics/19353), [Discussion](https://forums.swift.org/t/discussion-server-metrics-api/19600/), [Review](https://forums.swift.org/t/feedback-server-metrics-api/21353) 10 | * Decision Notes: [Rationale](https://forums.swift.org/t/april-4th-2019/22704) 11 | 12 | ## Package Description 13 | 14 | | | | 15 | |--|--| 16 | | **Package Name** | `swift-metrics` | 17 | | **Module Name** | `Metrics` | 18 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 19 | | **License** | [Apache 2](https://www.apache.org/licenses/LICENSE-2.0.html) | 20 | | **Dependencies** | *none* | 21 | 22 | ## Introduction 23 | 24 | Almost all production server software needs to emit metrics information for observability. The SSWG aims to provide a number of packages that can be shared across the whole Swift Server ecosystem so we need some amount of standardization. Because it's unlikely that all parties can agree on one full metrics implementation, this proposal is attempting to establish a metrics API that can be implemented by various metrics backends which then post the metrics data to backends like prometheus, graphite, publish over statsd, write to disk, etc. 25 | 26 | ## Motivation 27 | 28 | As outlined above, we should standardize on an API that if well adopted would allow application owners to mix and match libraries from different parties with a consistent metrics collection solution. 29 | 30 | ## Proposed solution 31 | 32 | The proposed solution is to introduce the following types that encapsulate metrics data: 33 | 34 | `Counter`: A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart. For example, you can use a counter to represent the number of requests served, tasks completed, or errors. 35 | 36 | ```swift 37 | counter.increment(100) 38 | ``` 39 | 40 | `Recorder`: A recorder collects observations within a time window (usually things like response sizes) and can provides aggregated information about the data sample, for example count, sum, min, max and various quantiles. 41 | 42 | ```swift 43 | recorder.record(100) 44 | ``` 45 | 46 | `Gauge`: A Gauge is a metric that represents a single numerical value that can arbitrarily go up and down. Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads. Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation. 47 | 48 | ```swift 49 | gauge.record(100) 50 | ``` 51 | 52 | `Timer`: A timer collects observations within a time window (usually things like request durations) and provides aggregated information about the data sample, for example min, max and various quantiles. It is similar to a `Recorder` but specialized for values that represent durations. 53 | 54 | ```swift 55 | timer.recordMilliseconds(100) 56 | ``` 57 | 58 | How would you use `counter`, `recorder`, `gauge` and `timer` in you application or library? Here is a contrived example for request processing code that emits metrics for: total request count per url, request size and duration and response size: 59 | 60 | ```swift 61 | func processRequest(request: Request) -> Response { 62 | let requestCounter = Counter("request.count", ["url": request.url]) 63 | let requestTimer = Timer("request.duration", ["url": request.url]) 64 | let requestSizeRecorder = Recorder("request.size", ["url": request.url]) 65 | let responseSizeRecorder = Recorder("response.size", ["url": request.url]) 66 | 67 | requestCounter.increment() 68 | requestSizeRecorder.record(request.size) 69 | 70 | let start = Date() 71 | let response = ... 72 | requestTimer.record(Date().timeIntervalSince(start)) 73 | responseSizeRecorder.record(response.size) 74 | } 75 | ``` 76 | 77 | ## Detailed design 78 | 79 | As seen above, the constructor functions `Counter`, `Timer`, `Gauge` and `Recorder` provides a concrete metric object. This raises the question of what metrics backend will you actually get? The answer is that it's configurable _per application_. The application sets up the metrics backend it wishes the whole application to use when it first starts. Libraries should never change the metrics implementation as that is something owned by the application. Configuring the metrics backend is straightforward: 80 | 81 | ```swift 82 | MetricsSystem.bootstrap(MyFavoriteMetricsImplementation()) 83 | ``` 84 | 85 | This instructs the `MetricsSystem` to install `MyFavoriteMetricsImplementation` as the metrics backend to use. This can only be done once at the beginning of the program. 86 | 87 | ### Metrics Types 88 | 89 | #### Counter 90 | 91 | Following is the user facing `Counter` API. It must have reference semantics, and its behavior depends on the `CounterHandler` implementation. 92 | 93 | ```swift 94 | public class Counter: CounterHandler { 95 | var handler: CounterHandler 96 | 97 | public let label: String 98 | public let dimensions: [(String, String)] 99 | 100 | public init(label: String, dimensions: [(String, String)], handler: CounterHandler) 101 | 102 | /// Increments by 1 103 | public func increment() 104 | public func increment(_ value: DataType) 105 | } 106 | ``` 107 | 108 | #### Recorder 109 | 110 | Following is the user facing `Recorder` API. It must have reference semantics, and its behavior depends on the `RecorderHandler` implementation. 111 | 112 | ```swift 113 | public class Recorder: RecorderHandler { 114 | var handler: RecorderHandler 115 | 116 | public let label: String 117 | public let dimensions: [(String, String)] 118 | public let aggregate: Bool 119 | 120 | public init(label: String, dimensions: [(String, String)], aggregate: Bool, handler: RecorderHandler) 121 | 122 | public func record(_ value: DataType) 123 | public func record(_ value: DataType) 124 | } 125 | ``` 126 | 127 | #### Gauge 128 | 129 | `Gauge` is a specialized `Recorder` that does not preform aggregation. 130 | 131 | ```swift 132 | public class Gauge: Recorder { 133 | public convenience init(label: String, dimensions: [(String, String)] = []) { 134 | self.init(label: label, dimensions: dimensions, aggregate: false) 135 | } 136 | } 137 | ``` 138 | 139 | #### Timer 140 | 141 | Following is the user facing `Timer` API. It must have reference semantics, and its behavior depends on the `TimerHandler` implementation. 142 | 143 | ```swift 144 | public class Timer: TimerHandler { 145 | var handler: TimerHandler 146 | 147 | public let label: String 148 | public let dimensions: [(String, String)] 149 | 150 | public init(label: String, dimensions: [(String, String)], handler: TimerHandler) 151 | 152 | public func recordNanoseconds(_ duration: Int64) 153 | } 154 | ``` 155 | 156 | ## Implementing a metrics backend (eg prometheus client library) 157 | 158 | An implementation of a metric backend needs to conform to the `MetricsFactory` protocol: 159 | 160 | ```swift 161 | public protocol MetricsFactory { 162 | func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler 163 | func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler 164 | func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler 165 | } 166 | ``` 167 | 168 | Having `CounterHandler`, `TimerHandler` and `RecorderHandler` define the metric capturing API: 169 | 170 | ```swift 171 | public protocol CounterHandler: AnyObject { 172 | func increment(_ value: DataType) 173 | } 174 | 175 | public protocol TimerHandler: AnyObject { 176 | func recordNanoseconds(_ duration: Int64) 177 | } 178 | 179 | public protocol RecorderHandler: AnyObject { 180 | func record(_ value: DataType) 181 | func record(_ value: DataType) 182 | } 183 | ``` 184 | 185 | Here is an example of contrived in-memory implementation: 186 | 187 | ```swift 188 | class SimpleMetrics: MetricsFactory { 189 | init() {} 190 | 191 | func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { 192 | return ExampleCounter(label, dimensions) 193 | } 194 | 195 | func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { 196 | let maker:(String, [(String, String)]) -> Recorder = aggregate ? ExampleRecorder.init : ExampleGauge.init 197 | return maker(label, dimensions) 198 | } 199 | 200 | func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler { 201 | return ExampleTimer(label, dimensions) 202 | } 203 | 204 | private class ExampleCounter: CounterHandler { 205 | init(_: String, _: [(String, String)]) {} 206 | 207 | let lock = NSLock() 208 | var value: Int64 = 0 209 | func increment(_ value: DataType) { 210 | self.lock.withLock { 211 | self.value += Int64(value) 212 | } 213 | } 214 | } 215 | 216 | private class ExampleRecorder: RecorderHandler { 217 | init(_: String, _: [(String, String)]) {} 218 | 219 | private let lock = NSLock() 220 | var values = [(Int64, Double)]() 221 | func record(_ value: DataType) { 222 | self.record(Double(value)) 223 | } 224 | 225 | func record(_ value: DataType) { 226 | // this may loose precision, but good enough as an example 227 | let v = Double(value) 228 | // TODO: sliding window 229 | lock.withLock { 230 | values.append((Date().nanoSince1970, v)) 231 | self._count += 1 232 | self._sum += v 233 | self._min = min(self._min, v) 234 | self._max = max(self._max, v) 235 | } 236 | } 237 | 238 | var _sum: Double = 0 239 | var sum: Double { 240 | return self.lock.withLock { _sum } 241 | } 242 | 243 | private var _count: Int = 0 244 | var count: Int { 245 | return self.lock.withLock { _count } 246 | } 247 | 248 | private var _min: Double = 0 249 | var min: Double { 250 | return self.lock.withLock { _min } 251 | } 252 | 253 | private var _max: Double = 0 254 | var max: Double { 255 | return self.lock.withLock { _max } 256 | } 257 | } 258 | 259 | private class ExampleGauge: RecorderHandler { 260 | init(_: String, _: [(String, String)]) {} 261 | 262 | let lock = NSLock() 263 | var _value: Double = 0 264 | func record(_ value: DataType) { 265 | self.record(Double(value)) 266 | } 267 | 268 | func record(_ value: DataType) { 269 | // this may loose precision but good enough as an example 270 | self.lock.withLock { _value = Double(value) } 271 | } 272 | } 273 | 274 | private class ExampleTimer: ExampleRecorder, TimerHandler { 275 | func recordNanoseconds(_ duration: Int64) { 276 | super.record(duration) 277 | } 278 | } 279 | } 280 | ``` 281 | -------------------------------------------------------------------------------- /proposals/0003-nio-postgres.md: -------------------------------------------------------------------------------- 1 | # NIOPostgres: A NIO-based PostgreSQL Driver 2 | 3 | > **Note**: Since this proposal was published, `NIOPostgres` has been renamed to `PostgresNIO`. 4 | > 5 | > For more context behind this change, see the following threads: [[1]](https://forums.swift.org/t/namespacing-of-packages-modules-especially-regarding-swiftnio/24726), [[2]](https://forums.swift.org/t/sswg-minimum-requirements-to-require-no-existing-clashes/24932) 6 | 7 | * Proposal: [SSWG-0003](https://github.com/swift-server/sswg/tree/master/proposals/SSWG-0003.md) 8 | * Author(s): [Tanner Nelson](https://github.com/tanner0101) 9 | * Review Manager: TBD 10 | * Status: **Accepted as Sandbox Maturity** 11 | * Implementation: [vapor/nio-postgres](https://github.com/vapor/nio-postgres) 12 | * Forum Threads: [Pitch](https://forums.swift.org/t/pitch-swiftnio-based-postgresql-client/18020), [Discussion](https://forums.swift.org/t/discussion-niopostgres-a-nio-based-postgresql-driver/22178), [Review](https://forums.swift.org/t/feedback-niopostgres-a-nio-based-postgresql-driver/24398) 13 | * Decision Notes: [Rationale](https://forums.swift.org/t/may-16th-2019/25036) 14 | 15 | ## Introduction 16 | 17 | `NIOPostgres` is a client package for connecting to, authorizing, and querying a PostgreSQL server. At the heart of this module are channel handlers for parsing and serializing messages in PostgreSQL's proprietary wire protocol. These channel handlers are combined in a request / response style connection type that provides a convenient, client-like interface for performing queries. Support for both simple (text) and parameterized (binary) querying is provided out of the box alongside a `PostgresData` type that handles conversion between PostgreSQL's wire format and native Swift types. 18 | 19 | ## Motiviation 20 | 21 | Most Swift implementations of Postgres clients are based on the [libpq](https://www.postgresql.org/docs/11/libpq.html) C library which handles transport internally. Building a library directly on top of Postgres' wire protocol using SwiftNIO should yield a more reliable, maintainable, and performant interface for PostgreSQL databases. 22 | 23 | ## Dependencies 24 | 25 | This package has four dependencies: 26 | 27 | - `swift-nio` from `2.0.0` 28 | - `swift-nio-ssl` from `2.0.0` 29 | - `swift-log` from `1.0.0` 30 | - `swift-metrics` from `1.0.0` 31 | 32 | This package has no additional system dependencies. 33 | 34 | ## Proposed Solution 35 | 36 | This section goes into detail on a few distinct types from this module to give an idea of how they work together and what using the package looks like. 37 | 38 | ### PostgresConnection 39 | 40 | The base connection type, `PostgresConnection`, is a wrapper around NIO's `ClientBootstrap` that initializes the pipeline to communicate via Postgres messages using a request / response pattern. 41 | 42 | ```swift 43 | import NIOPostgres 44 | 45 | // create a new event loop group 46 | let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) 47 | defer { try! elg.syncShutdownGracefully() } 48 | 49 | // create a new connection 50 | let address = try SocketAddress(ipAddress: "127.0.0.1", port: 5432) 51 | let conn = try PostgresConnection.connect( 52 | to: address, 53 | // optionally configure TLS 54 | tlsConfiguration: .forClient(certificateVerification: .none), 55 | serverHostname: "127.0.0.1" 56 | on: elg.eventLoop 57 | ).wait() 58 | defer { try! conn.close().wait() } 59 | 60 | // authenticate the connection using credentials 61 | try conn.authenticate(username: "username", database: "database", password: "password").wait() 62 | 63 | // ready to query 64 | print(conn) // PostgresConnection 65 | ``` 66 | 67 | #### Closing 68 | 69 | A connection _must_ be closed before it deinitializes. `PostgresConnection` ensures this by asserting that it has been closed in its `deinit` handler. This is meant to help developers implement proper graceful shutdown early and avoid leaking memory or sockets. 70 | 71 | ### Simple Query 72 | 73 | Assuming we have an active, authenticated `PostgresConnection`, we can query the connected server using PostgreSQL's simple, text format. 74 | 75 | ```swift 76 | import NIOPostgres 77 | 78 | let conn: PostgresConnection ... 79 | 80 | // select the current version 81 | let rows = try conn.simpleQuery("SELECT version()").wait() 82 | print(rows) // [PostgresRow] 83 | 84 | // fetch the version column from the first row casting it to a Swift string 85 | let version = rows[0].column("version")?.string 86 | print(version) // String? 87 | ``` 88 | 89 | This format does not support parameterizing input and returns all data in string format. To bind values, insert them into the string: 90 | 91 | ```swift 92 | try conn.simpleQuery("SELECT * FROM planets WHERE name = 'Earth'") 93 | ``` 94 | 95 | ### Query 96 | 97 | We can also perform parameterized queries with an active `PostgresConnection`. These queries support binding input parameters and return data in a more compact binary format. 98 | 99 | Input parameters are passed as an array of `PostgresData` following the SQL string. In the query string, input parameters are referenced by incrementing placeholders, starting with `$1`. 100 | 101 | ```swift 102 | import NIOPostgres 103 | 104 | let conn: PostgresConnection ... 105 | 106 | // selects all planets where name is equal to the first bound parameter 107 | let rows = try conn.query("SELECT * FROM planets WHERE name = $1", ["Earth"]).wait() 108 | 109 | // fetch the "name" column from the first row, casting it to a string 110 | let foo = rows[0].column("name")?.string 111 | print(foo) // "Earth" 112 | ``` 113 | 114 | ### PostgresData 115 | 116 | `PostgresData` represents data both going to and coming from Postgres. 117 | 118 | #### Input 119 | 120 | An array of `PostgresData` is supplied alongside parameterized queries, one for each parameter. There are many initializers for creating `PostgresData` from Swift's standard types. For example: 121 | 122 | ```swift 123 | import NIOPostgres 124 | 125 | let string = PostgresData(string: "Hello") 126 | let double = PostgresData(double: 3.14) 127 | let date = PostgresData(date: Date(timeIntervalSince1970: 42)) 128 | ``` 129 | 130 | `PostgresData` also conforms to Swift's `Expressible...` protocols, allowing for conversion between Swift literals. 131 | 132 | ```swift 133 | import NIOPostgres 134 | 135 | let inputs: [PostgresData] = ["hello", 3.14] 136 | ``` 137 | 138 | #### Output 139 | 140 | Likewise, `PostgresData` can be converted back to Swift types. This is useful for converting data returned by Postgres queries into meaningful types. There are many methods for Swift's standard types, for example: 141 | 142 | ```swift 143 | import NIOPostgres 144 | 145 | let data: PostgresData 146 | print(data.string) // String? 147 | ``` 148 | 149 | Here is a full list of types supported currently: 150 | 151 | - `Swift.String` 152 | - `Swift.Int` 153 | - `Swift.Int64` 154 | - `Swift.Int32` 155 | - `Swift.Int16` 156 | - `Swift.Int8` 157 | - `Swift.UInt` 158 | - `Swift.UInt64` 159 | - `Swift.UInt32` 160 | - `Swift.UInt16` 161 | - `Swift.UInt8` 162 | - `Swift.Float` 163 | - `Swift.Double` 164 | - `Foundation.Date` 165 | - `Foundation.Data` 166 | - `Foundation.UUID` 167 | 168 | ### PostgresRow 169 | 170 | Both `simpleQuery` and `query` return an array of `PostgresRow`. Each row can be thought of as a dictionary with column names as the key and data as the value. While the actual storage implementation is private, `PostgresRow` gives the following method for accessing column data: 171 | 172 | ```swift 173 | struct PostgresRow { 174 | func column(_ column: String) -> PostgresData? 175 | } 176 | ``` 177 | 178 | If no column with that name is contained by the row, `nil` is returned. Matching columns from _any_ table will be returned on a first match basis. 179 | 180 | ### PostgresError 181 | 182 | The `PostgresError` type represents errors thrown from both the Postgres package itself (during parsing, for example) and errors returned by the server: 183 | 184 | ```swift 185 | public enum PostgresError: Error { 186 | case proto(String) 187 | case server(PostgresMessage.Error) 188 | case connectionClosed 189 | 190 | var code: Code { ... } 191 | } 192 | ``` 193 | 194 | The `PostgresError.Code` type is a large enum-like struct containing all recognized Postgres error codes. This is useful for error handling: 195 | 196 | ```swift 197 | let conn: PostgresConnection ... 198 | 199 | do { 200 | _ = try conn.simpleQuery("SELECT &").wait() 201 | } catch let error as PostgresError { 202 | switch error.code { 203 | case .syntaxError: ... 204 | default: ... 205 | } 206 | } 207 | ``` 208 | 209 | ### PostgresClient 210 | 211 | While `PostgresConnection` is the main type to use for connecting, authorizing, and requesting TLS, the `PostgresClient` protocol is sufficient for performing both text and parameterized queries. 212 | 213 | ```swift 214 | protocol PostgresClient { 215 | var eventLoop: EventLoop { get } 216 | func send(_ request: PostgresRequest) -> EventLoopFuture 217 | } 218 | ``` 219 | 220 | `PostgresConnection` is the only conformer that `NIOPostgres` provides, but it is expected that dependencies will add additional conformers. For example, a consumer of this package might add conformance to a _pool_ of connections, allowing for automatic recycling as needed, a crucial feature for long-running applications. 221 | 222 | #### Note on usage 223 | 224 | Since most of `NIOPostgres`'s convenience methods are added to `PostgresClient` instead of `PostgresConnection` directly, any additional conformers should feel exactly the same to use. Because of this, it is expected that `PostgresClient` should be used any place where you need to make queries. For example, in a theoretical controller: 225 | 226 | ```swift 227 | final class UserController: Controller { 228 | let db: PostgresClient 229 | init(db: PostgresClient) { 230 | self.db = db 231 | } 232 | 233 | func names(_ req: HTTPRequest) -> EventLoopFuture<[String]> { 234 | return self.db.query("SELECT name FROM users").map { rows in 235 | return rows.map { $0.column("name")?.string! } 236 | } 237 | } 238 | } 239 | ``` 240 | 241 | Because this controller relies on `PostgresClient`, any of the following could be supplied to it: 242 | 243 | - Connected `PostgresConnection` 244 | - Pool of `PostgresConnection`s 245 | - Dummy conformer for testing 246 | 247 | #### PostgresRequest 248 | 249 | Postgres' wire protocol uses a request / response pattern, but unlike HTTP or Redis, one request can yield one _or more_ responses. `PostgresRequest` conformers handle this with the following protocol. 250 | 251 | ```swift 252 | protocol PostgresRequest { 253 | func respond(to message: PostgresMessage) throws -> [PostgresMessage]? 254 | func start() throws -> [PostgresMessage] 255 | } 256 | ``` 257 | 258 | `PostgresRequest` is responsible for sending zero or more initial messages and handling the server's responses. When the request is complete, `nil` is returned by `respond`, causing the client's send future to complete. 259 | 260 | ### CMD5 261 | 262 | MD5 hashing is required for PostgreSQL's authentication flow. This module follows NIO's approach and embeds a private C-based implementation rather than relying on external Swift crypto libraries. 263 | 264 | ### Todo / Discussion 265 | 266 | Here are some things that are still a work in progress: 267 | 268 | - **Prepared Statement API**: Postgres allows for parameterized queries to be re-used multiple times with different inputs. An API for doing this in NIO Postgres would be useful. 269 | - **PostgresRequest edge cases**: Finer grain input / output from this protocol would be useful in assisting with protocol edge cases. For example, sometimes a Postgres error message can signal request completion depending on state. 270 | 271 | ### How to use 272 | 273 | To try this package out at home, add the following dependency to your `Package.swift` file: 274 | 275 | ```swift 276 | .package(url: "https://github.com/vapor/nio-postgres.git", .branch("master")), 277 | ``` 278 | 279 | Then add `"NIOPostgres"` to your module target's dependencies array. 280 | 281 | ### Seeking Feedback 282 | 283 | * If anything, what does this proposal *not cover* that you will definitely need? 284 | * If anything, what could we remove from this and still be happy? 285 | * API-wise: what do you like, what don't you like? 286 | 287 | Feel free to post feedback as response to this post and/or GitHub issues on [vapor/nio-postgres](https://github.com/vapor/nio-postgres). 288 | -------------------------------------------------------------------------------- /proposals/0005-nio-http-client.md: -------------------------------------------------------------------------------- 1 | # SSWG HTTP Client Library 2 | 3 | > **Note**: Since this proposal was published, the module `NIOHTTPClient` has been renamed to `AsyncHTTPClient`. The renamed GitHub repo is found at https://github.com/swift-server/async-http-client/ 4 | > 5 | > For more context behind this change, see the following threads: [[1]](https://forums.swift.org/t/namespacing-of-packages-modules-especially-regarding-swiftnio/24726), [[2]](https://forums.swift.org/t/sswg-minimum-requirements-to-require-no-existing-clashes/24932), [[3]](https://forums.swift.org/t/feedback-nio-based-http-client/26149/27) 6 | 7 | * Proposal: SSWG-0005 8 | * Authors: [Artem Redkin](https://github.com/artemredkin), [Tomer Doron](https://github.com/tomerd), [Tanner Nelson](https://github.com/tanner0101), [Ian Partridge](https://github.com/ianpartridge/) 9 | * Sponsors: Swift Server Working Group 10 | * Status: **Accepted as Sandbox Maturity** 11 | * Implementation: [swift-server/async-http-client](https://github.com/swift-server/async-http-client/) 12 | * Forum Threads: [Pitch](https://forums.swift.org/t/generic-http-client-library-pitch/23341), [Discussion](https://forums.swift.org/t/discussion-nio-based-http-client/24195/), [Review](https://forums.swift.org/t/feedback-nio-based-http-client/26149) 13 | * Decision Notes: [Rationale](https://forums.swift.org/t/june-27th-2019/26580) 14 | 15 | ## Introduction 16 | Number of projects implemented their own HTTP client libraries, like: 17 | - [IBM-Swift](https://github.com/IBM-Swift/Kitura-NIO/blob/master/Sources/KituraNet/HTTP/HTTPServer.swift) 18 | - [vapor](https://github.com/vapor/http/blob/master/Sources/HTTP/Responder/HTTPClient.swift) 19 | - [smoke-framework](https://github.com/amzn/smoke-http/blob/master/Sources/SmokeHTTPClient/HTTPClient.swift) 20 | 21 | This shows that there is a need for generic, multi-purpose, non-blocking, asynchronous HTTP client library built on top of `SwiftNIO`. SSWG aims to provide a number of packages that could be shared between different projects, and I think proposed HTTP client library would be a good fit for those projects and other use-cases. 22 | 23 | ## Motivation 24 | Having one, community-driver project that can be shared between different projects will hopefully solve the need to implement this functionality in every project from scratch. 25 | 26 | ## Proposed solution 27 | Proposed solution is to have a `HTTPClient` class, that support a number of often-used methods, as well as an ability to pass a delegate for more precise control over the HTTP data transmission: 28 | ```swift 29 | class HTTPClient { 30 | 31 | public func get(url: String, timeout: Timeout? = nil) -> EventLoopFuture { 32 | } 33 | 34 | public func post(url: String, body: Body? = nil, timeout: Timeout? = nil) -> EventLoopFuture { 35 | } 36 | 37 | public func put(url: String, body: Body? = nil, timeout: Timeout? = nil) -> EventLoopFuture { 38 | } 39 | 40 | public func delete(url: String, timeout: Timeout? = nil) -> EventLoopFuture { 41 | } 42 | 43 | public func execute(request: Request, timeout: Timeout? = nil) -> EventLoopFuture { 44 | } 45 | 46 | public func execute(request: Request, delegate: T, timeout: Timeout? = nil) -> Task { 47 | } 48 | } 49 | ``` 50 | For the first release we have the following features implemented: 51 | 1. Simple follow-redirects (cookie headers are dropped) 52 | 2. Streaming body download 53 | 3. TLS support 54 | 4. Cookie parsing (but not storage) 55 | 56 | ## Detailed design 57 | 58 | ### Lifecycle 59 | Creating a client is strait-forward: 60 | ```swift 61 | let client = HTTPClient(eventLoopGroupProvider: .createNew) 62 | ``` 63 | This initializer will create new `EventLoopGroup`, so every client instance will have it's own separate `EventLoopGroup`. Alternatively, users can supply shared `EventLoopGroup`: 64 | ```swift 65 | let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) 66 | let client = HTTPClient(eventLoopGroupProvider: .shared(group)) 67 | ``` 68 | It is important to shutdown your client instance after it is no longer needed, in order to shutdown internal `SwiftNIO` machinery: 69 | ```swift 70 | try client.syncShutdown() 71 | ``` 72 | In case `EventLoopGroup` was provided to the client instance, there is no need to shutdown the client, we expect that lifecycle of that group will be controlled by its owner. 73 | 74 | ### Request 75 | In case helper methods do not provide required functionality (for example, if user needs to set headers, or use specific HTTP method), clients of the library can use `HTTPClient.Request`: 76 | ```swift 77 | extension HTTPClient { 78 | typealias ChunkProvider = (@escaping (IOData) -> EventLoopFuture) -> EventLoopFuture 79 | 80 | struct Body { 81 | var length: Int? 82 | var provider: HTTPClient.ChunkProvider 83 | 84 | static func byteBuffer(_ buffer: ByteBuffer) -> Body 85 | static func stream(length: Int? = nil, _ provider: @escaping HTTPClient.ChunkProvider) -> Body 86 | static func data(_ data: Data) -> Body 87 | static func string(_ string: String) -> Body 88 | } 89 | 90 | struct Request { 91 | public var version: HTTPVersion 92 | public var method: HTTPMethod 93 | public var url: URL 94 | public var headers: HTTPHeaders 95 | public var body: Body? 96 | 97 | public init(url: String, 98 | version: HTTPVersion = HTTPVersion(major: 1, minor: 1), 99 | method: HTTPMethod = .GET, 100 | headers: HTTPHeaders = HTTPHeaders(), 101 | body: Body? = nil) throws {} 102 | } 103 | } 104 | ``` 105 | Example: 106 | ```swift 107 | var request = try HTTPClient.Request(url: "http://swift.org") 108 | request.headers.add(name: "User-Agent", value: "nio-http-client") 109 | 110 | let future = client.execute(request: request) 111 | ``` 112 | 113 | ### Response 114 | `HTTPClient`'s methods return an `EventLoopFuture`. This struct is defined as follows: 115 | ```swift 116 | extension HTTPClient { 117 | struct Response { 118 | public var host: String 119 | public var status: HTTPResponseStatus 120 | public var headers: HTTPHeaders 121 | public var body: ByteBuffer? 122 | } 123 | } 124 | ``` 125 | where `HTTPResponseStatus` is an enum that describes HTTP codes that could be returned by the server: 126 | ```swift 127 | client.get(url: "http://swift.org").whenSuccess { response in 128 | switch response.status { 129 | case .ok: print("server return 200 OK") 130 | case .notFound: print("server return 404 Not Found") 131 | case .internalServerError: print("server return 500 Internal Server Error") 132 | ... 133 | } 134 | } 135 | ``` 136 | 137 | ### HTTPClientResponseDelegate 138 | In addition to helper/request methods, library also provides the following delegate, which provides greater control over how HTTP response is processed: 139 | ```swift 140 | public protocol HTTPClientResponseDelegate: class { 141 | associatedtype Response 142 | 143 | // this method will be called when request body is sent 144 | func didTransmitRequestBody(task: HTTPClient.Task) 145 | 146 | // this method will be called when we receive Head response, with headers and status code 147 | func didReceiveHead(task: HTTPClient.Task, _ head: HTTPResponseHead) 148 | 149 | // this method will be called multiple times with chunks of the HTTP response body (if there is a body) 150 | // returning event EventLoopFuture could be used for backpressure handling, all reads will be stopped untill this future is resolved 151 | func didReceivePart(task: HTTPClient.Task, _ buffer: ByteBuffer) -> EventLoopFuture? 152 | 153 | // this method will be called if an error occurs during request processing 154 | func didReceiveError(task: HTTPClient.Task, _ error: Error) 155 | 156 | // this will be called when HTTP response is read fully 157 | func didFinishRequest(task: HTTPClient.Task) throws -> Response 158 | } 159 | ``` 160 | This delegate will be especially helpful when you need to process HTTP response body in a streaming fashion, for example: 161 | ```swift 162 | class CountingDelegate: HTTPClientResponseDelegate { 163 | typealias Response = Int 164 | 165 | var count = 0 166 | 167 | func didTransmitRequestBody() { 168 | } 169 | 170 | func didReceiveHead(_ head: HTTPResponseHead) { 171 | } 172 | 173 | func didReceivePart(_ buffer: ByteBuffer) -> EventLoopFuture? { 174 | count += buffer.readableBytes 175 | return nil 176 | } 177 | 178 | func didFinishRequest() throws -> Int { 179 | return count 180 | } 181 | 182 | func didReceiveError(_ error: Error) { 183 | } 184 | } 185 | 186 | let request = try HTTPRequest(url: "https://swift.org") 187 | let delegate = CountingDelegate() 188 | 189 | try client.execute(request: request, delegate: delegate).future.whenSuccess { count in 190 | print(count) // count is of type Int 191 | } 192 | ``` 193 | 194 | ## Seeking feedback 195 | Feedback that would really be great is: 196 | - Streaming API: what do you like, what don't you like? 197 | - What feature set would be acceptable for `1.0.0`? 198 | -------------------------------------------------------------------------------- /proposals/0006-nio-apns-api.md: -------------------------------------------------------------------------------- 1 | # NIO-based Apple Push Notification Service 2 | 3 | > **Note**: Since this proposal was published, the module `NIOAPNS` has been renamed to `APNSwift`. The renamed GitHub repo is found at https://github.com/kylebrowning/APNSwift 4 | > 5 | > For more context behind this change, see the following threads: [[1]](https://forums.swift.org/t/namespacing-of-packages-modules-especially-regarding-swiftnio/24726), [[2]](https://forums.swift.org/t/sswg-minimum-requirements-to-require-no-existing-clashes/24932), [[3]](https://forums.swift.org/t/nioapns-naming-brainstorm/26520) 6 | 7 | * Proposal: SSWG-0006 8 | * Authors: [Kyle Browning](https://github.com/kylebrowning) 9 | * Sponsor: Vapor 10 | * Review Manager: TBD 11 | * Status: **Accepted as Sandbox Maturity** 12 | * Implementation: [kylebrowning/swift-nio-http2-apns](https://github.com/kylebrowning/swift-nio-apns) 13 | * Forum Threads: [Pitch](https://forums.swift.org/t/apple-push-notification-service-implementation-pitch/20193), [Discussion](https://forums.swift.org/t/discussion-nioapns-nio-based-apple-push-notification-service/23384), [Feedback](https://forums.swift.org/t/feedback-nioapns-nio-based-apple-push-notification-service/24393) 14 | * Decision Notes: [Rationale](https://forums.swift.org/t/june-27th-2019/26580) 15 | 16 | ## Package Description 17 | Apple push notification service implementation built on Swift NIO. 18 | 19 | | | | 20 | |--|--| 21 | | **Package name** | `nio-apns` | 22 | | **Module Name** | `NIOAPNS` | 23 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 24 | | **License** | [Apache 2](https://www.apache.org/licenses/LICENSE-2.0.html) | 25 | | **Dependencies** | [SwiftNIO](https://github.com/apple/swift-nio) 2.x, [SwiftNIOSSL](https://github.com/apple/swift-nio-ssl.git) 2.x, [SwiftNIOHTTP2](https://github.com/apple/swift-nio-http2.git) 1.x| 26 | 27 | ## Introduction 28 | 29 | `NIOAPNS` is a module thats gives server side swift applications the ability to use the [Apple push notification service.](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server) 30 | 31 | 32 | ## Motivation 33 | 34 | APNS is used to push billions of pushes a day, (7 billion per day in 2012). Many of us using Swift on the backend are using it to power our iOS applications. Having a community supported APNS implementation would go a long way to making it the fastest, free-ist, and simplest solution that exists. 35 | 36 | All of the currently maintained libraries either have framework specific dependencies, are not built with NIO, or do not provide enough extensibility while providing "out of the box" capabilities. 37 | 38 | ### Existing Solutions 39 | 40 | - [NIO-APNS](https://github.com/moritzsternemann/nio-apns) 41 | - [PerfectNotifications](https://github.com/PerfectlySoft/Perfect-Notifications) 42 | - [VaporNotifications](https://github.com/hjuraev/VaporNotifications) 43 | 44 | ## Proposed Solution 45 | 46 | `NIOApns` provides the essential types for interacting with APNS Server (both production and sandbox). 47 | 48 | ### What it does do 49 | 50 | - Provides an API for handling connection to Apples HTTP2 APNS server. 51 | - Provides proper error messages that APNS might respond with. 52 | - Uses custom/non dependency implementations of JSON Web Token specific to APNS (using [rfc7519](https://tools.ietf.org/html/rfc7519). 53 | - Imports OpenSSL for SHA256 and ES256. 54 | - Provides an interface for signing your Push Notifications. 55 | - Signs your token request. 56 | - Sends push notifications to a specific device. 57 | - [Adheres to guidelines Apple Provides.](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns) 58 | 59 | ### What it won't do. 60 | - Store/register device tokens. 61 | - Build an HTTP2 generic client. 62 | - Google Cloud Message. 63 | - Refresh your token no more than once every 20 minutes and no less than once every 60 minutes. (up to connection handler) 64 | 65 | ### Future considerations and dependencies 66 | - BoringSSL 67 | - Common connection handler options 68 | - swift-log 69 | - swift-metrics 70 | - swift-jwt? 71 | - swift-http2-client? 72 | 73 | ### APNSConfiguration 74 | 75 | [`APNSConfiguration`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSConfiguration.swift) is a structure that provides the system with common configuration. 76 | 77 | ```swift 78 | public struct APNSConfiguration { 79 | public var keyIdentifier: String 80 | public var teamIdentifier: String 81 | public var signer: APNSSigner 82 | public var topic: String 83 | public var environment: Environment 84 | public var tlsConfiguration: TLSConfiguration 85 | 86 | public var url: URL { 87 | switch environment { 88 | case .production: 89 | return URL(string: "https://api.push.apple.com")! 90 | case .sandbox: 91 | return URL(string: "https://api.development.push.apple.com")! 92 | } 93 | } 94 | ``` 95 | #### Example `APNSConfiguration` 96 | ```swift 97 | let signer = ... 98 | let apnsConfig = try APNSConfiguration(keyIdentifier: "9UC9ZLQ8YW", 99 | teamIdentifier: "ABBM6U9RM5", 100 | signer: signer), 101 | topic: "com.grasscove.Fern", 102 | environment: .sandbox) 103 | ``` 104 | 105 | ### Signer 106 | 107 | [`APNSSigner`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSSigner.swift) provides a structure to sign the payloads with. This should be loaded into memory at the configuration level. It requires the data to be in a ByteBuffer format. We've provided a convenience initializer for users to do this from filePath. This should only be done once, and not on an EventLoop. 108 | 109 | ```swift 110 | let signer = try! APNSSigner(filePath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8") 111 | ``` 112 | ### APNSConnection 113 | 114 | [`APNSConnection`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSConnection.swift) is a class with methods thats provides a wrapper to NIO's ClientBootstrap. The `swift-nio-http2` dependency is utilized here. It also provides a function to send a notification to a specific device token string. 115 | 116 | 117 | #### Example `APNSConnection` 118 | ```swift 119 | let apnsConfig = ... 120 | let apns = try APNSConnection.connect(configuration: apnsConfig, on: group.next()).wait() 121 | ``` 122 | 123 | ### APNSConnection.send 124 | [`APNSConnection.send`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSConnection.swift) is a method with a few extra parameters for your convenience. Use a custom `JSONEncoder`, set an expiration date, a priority, or a collapseIdentifier. 125 | 126 | #### Example `APNSConnection.send` 127 | ```swift 128 | let apnsConfig = ... 129 | let apns = ... 130 | do { 131 | let expiry = Date().addingTimeInterval(5) 132 | try apns.send(notification, to: "b27a07be2092c7fbb02ab5f62f3135c615e18acc0ddf39a30ffde34d41665276", with: JSONEncoder(), expiration: expiry, priority: 10, collapseIdentifier: "huro2").wait() 133 | } catch { 134 | print(error) 135 | } 136 | ``` 137 | 138 | ### Alert 139 | 140 | [`Alert`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSRequest.swift) is the actual meta data of the push notification alert someone wishes to send. More details on the specifics of each property are provided [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). They follow a 1-1 naming scheme listed in Apple's documentation 141 | 142 | 143 | #### Example `Alert` 144 | ```swift 145 | let alert = Alert(title: "Hey There", subtitle: "Full moon sighting", body: "There was a full moon last night did you see it") 146 | ``` 147 | 148 | ### APSPayload 149 | 150 | [`APSPayload`](https://github.com/kylebrowning/swift-nio-http2-apns/blob/master/Sources/NIOAPNS/APNSRequest.swift) is the meta data of the push notification. Things like the alert, badge count. More details on the specifics of each property are provided [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). They follow a 1-1 naming scheme listed in Apple's documentation 151 | 152 | 153 | #### Example `APSPayload` 154 | ```swift 155 | let alert = ... 156 | let aps = APSPayload(alert: alert, badge: 1, sound: .normal("cow.wav")) 157 | ``` 158 | 159 | ## Putting it all together 160 | 161 | ```swift 162 | let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) 163 | let url = URL(fileURLWithPath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8") 164 | let data: Data 165 | do { 166 | data = try Data(contentsOf: url) 167 | } catch { 168 | throw APNSError.SigningError.certificateFileDoesNotExist 169 | } 170 | var byteBuffer = ByteBufferAllocator().buffer(capacity: data.count) 171 | byteBuffer.writeBytes(data) 172 | let signer = try! APNSSigner.init(buffer: byteBuffer) 173 | 174 | let apnsConfig = APNSConfiguration(keyIdentifier: "9UC9ZLQ8YW", 175 | teamIdentifier: "ABBM6U9RM5", 176 | signer: signer, 177 | topic: "com.grasscove.Fern", 178 | environment: .sandbox) 179 | 180 | let apns = try APNSConnection.connect(configuration: apnsConfig, on: group.next()).wait() 181 | let alert = Alert(title: "Hey There", subtitle: "Full moon sighting", body: "There was a full moon last night did you see it") 182 | let aps = APSPayload(alert: alert, badge: 1, sound: .normal("cow.wav")) 183 | let notification = BasicNotification(aps: aps) 184 | let res = try apns.send(notification, to: "de1d666223de85db0186f654852cc960551125ee841ca044fdf5ef6a4756a77e").wait() 185 | try apns.close().wait() 186 | try group.syncShutdownGracefully() 187 | ``` 188 | 189 | ### Custom Notification Data 190 | 191 | Apple provides engineers with the ability to add custom payload data to each notification. In order to facilitate this we have the `APNSNotification`. 192 | 193 | #### Example 194 | ```swift 195 | struct AcmeNotification: APNSNotification { 196 | let acme2: [String] 197 | let aps: APSPayload 198 | 199 | init(acme2: [String], aps: APSPayload) { 200 | self.acme2 = acme2 201 | self.aps = aps 202 | } 203 | } 204 | 205 | let apns: APNSConnection: = ... 206 | let aps: APSPayload = ... 207 | let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps) 208 | let res = try apns.send(notification, to: "de1d666223de85db0186f654852cc960551125ee841ca044fdf5ef6a4756a77e").wait() 209 | ``` 210 | 211 | 212 | ## Maturity 213 | 214 | This package meets the following criteria according to the [SSWG Incubation Process](https://github.com/swift-server/sswg/blob/master/process/incubation.md): 215 | - Ecosystem (SwiftNIO) 216 | - Code Style is up to date 217 | - Errors implemented 218 | - Tests being used 219 | - documentation 220 | - [Apache 2 license](https://github.com/kylebrowning/swift-nio-apns/blob/master/LICENSE) 221 | - [Swift Code of Conduct](https://github.com/kylebrowning/swift-nio-apns/blob/master/CODE_OF_CONDUCT.md) 222 | - [Contributing Guide](https://github.com/kylebrowning/swift-nio-apns/blob/master/CONTRIBUTING.md) 223 | - [CircleCI builds](https://circleci.com/gh/kylebrowning/swift-nio-apns/tree/master) 224 | 225 | ## Alternatives Considered 226 | 227 | N/A 228 | 229 | ## Special Thanks 230 | | | | 231 | |--|--| 232 | | **[Tanner](https://github.com/tanner0101)** | Everything really | 233 | | **[fumoboy007](https://forums.swift.org/u/fumoboy007)** | APNSSigner idea | 234 | | **[David Hart](https://forums.swift.org/u/hartbit)** | Custom Notifications, best practices and Apples naming conventions | 235 | | **[IanPartridge](https://forums.swift.org/u/IanPartridge)** | JWT, crypto usages, overall feedback | 236 | | **[Nathan Harris](https://forums.swift.org/u/mordil/summary)** | General questions on Incubation process and templates | 237 | | **[Laurent](https://github.com/lgaches)** | All the tests | 238 | |**[Everyone who participated in the pitch](https://forums.swift.org/t/apple-push-notification-service-implementation-pitch/20193)**| The feedback thread was so lively, thank you!| 239 | -------------------------------------------------------------------------------- /proposals/0007-statsd-client.md: -------------------------------------------------------------------------------- 1 | # Statsd Client 2 | 3 | * Proposal: [SSWG-0007](https://github.com/swift-server/sswg/blob/master/proposals/SSWG-0007.md) 4 | * Authors: [Tom Doron](https://github.com/tomerd) 5 | * Sponsor(s): TBD 6 | * Review Manager: TBD 7 | * Status: **Implemented** 8 | * Implementation: [tomerd/swift-statsd-client](https://github.com/tomerd/swift-statsd-client) 9 | * Forum Threads: [Pitch](https://forums.swift.org/t/statsd-client-in-swift/25134), [Discussion](https://forums.swift.org/t/discussion-swift-statsd-client-implementation/26109) 10 | 11 | ## Package Description 12 | 13 | metrics backend for swift-metrics that uses the statsd protocol 14 | 15 | ||| 16 | | --- | --- | 17 | |**Package Name**|`swift-statsd-client`| 18 | |**Module Name**|`StatsdClient `| 19 | |**Proposed Maturity Level**|[Sandbox ](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram)| 20 | |**License**|[Apache 2.0](https://github.com/MrLotU/SwiftPrometheus/blob/master/LICENSE)| 21 | |**Dependencies**|swift-nio > 1.0.0, swift-metrics > 1.0.0| 22 | 23 | ## Introduction 24 | 25 | for a background on metrics in swift applications, see the [metrics proposal](https://forums.swift.org/t/feedback-server-metrics-api) 26 | 27 | the [statsd](https://github.com/b/statsd_spec) protocol is simpler than most custom metrics solutions (eg prometheus), yet extremely popular since it is language agnostic and easy to implement both client and server side. as such, it can be used to integrate applications with many observability solutions such as: 28 | 29 | * [aws ](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-custom-metrics-statsd.html) 30 | * [azure ](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-platform) 31 | * [google cloud ](https://cloud.google.com/monitoring/agent/plugins/statsd) 32 | * [ibm cloud ](https://cloud.ibm.com/catalog/services/ibm-cloud-monitoring-with-sysdig) 33 | * [grafana ](https://grafana.com) 34 | * [graphite ](https://graphiteapp.org) 35 | 36 | ## Motivation 37 | 38 | a `statsd` client will allow server applications written in swift to easily integrate with many popular observability solutions. the client, like the protocol, is designed to be lightweight and delegate most computation to the observability server 39 | 40 | ## Proposed solution 41 | 42 | the proposed solution is a client library that implements the [swift metric api](https://github.com/apple/swift-metrics) and uses [swift nio](https://github.com/apple/swift-nio) to establish a `UDP` connection to the statsd server. 43 | 44 | metrics types are mapped as following: 45 | 46 | | Metrics API | Statsd | 47 | | --- | --- | 48 | | Counter | Counter | 49 | | Gauge | Gauge | 50 | | Timer | Timer | 51 | | Recorder | Histogram | 52 | 53 | ## Detailed design 54 | 55 | ### api 56 | 57 | the client is primarily designed to be used within the context of the swift metrics api. as such, the application needs to create an instance of the `StatsdClient` and bootstrap the `MertricsSystem` as part of the application's main: 58 | 59 | ```swift 60 | let statsdClient = try StatsdClient(host: host, port: port) 61 | MetricsSystem.bootstrap(statsdClient) 62 | ``` 63 | 64 | to cleanly release the networking resources occupied by the client, the application should also shutdown the client before it terminates: 65 | 66 | ```swift 67 | statsdClient.shutdown() 68 | ``` 69 | as mentioned above, usage of the client is designed to be done primarily via the swift metrics api and the client does not provide additional functionality beyond the metrics api surface area. in other words, users are not expected to interact with the client directly beyond bootstrap and shutdown. emitting metrics information is done via the standard metrics api, as described in https://github.com/apple/swift-metrics#metric-types 70 | 71 | ### protocol 72 | 73 | the client implements the `statsd` protocol as following: 74 | 75 | ||| 76 | | --- | --- | 77 | | counter | `:|c` | 78 | | timer | `:|ms` | 79 | | gauge | `:|g` | 80 | | histogram | `:|h` | 81 | 82 | **name** is constructed from the metric object label and dimensions concatenated with a dot between them 83 | 84 | **value** is computed from the data provided to the metrics API. no in-memory aggregation is done at the client level, and data is mostly pass-through. note that some `statsd` metrics types accept positive values only in the UInt64 range, while the metrics API generally accepts Int64. in cases of such mismatch, the client truncates negative values to zero 85 | 86 | ### networking 87 | 88 | thew client uses [swift-nio](https://github.com/apple/swift-nio) to establish a `UDP` connection to the statsd server. in later phases, the client will also support `TCP` connections. 89 | 90 | ## Maturity Justification 91 | 92 | this is a fairly new library, not enough production usage to justify anything beyond sandbox 93 | 94 | ## Alternatives considered 95 | 96 | there are no clear alternatives 97 | -------------------------------------------------------------------------------- /proposals/0008-swift-prometheus.md: -------------------------------------------------------------------------------- 1 | # SwiftPrometheus - Prometheus Metrics in Swift 2 | 3 | * Proposal: SSWG-0008 4 | * Authors: [Jari Koopman / LotU](https://github.com/MrLotU) 5 | * Review Manager: TBD 6 | * Status: [Implemented ](https://github.com/MrLotU/SwiftPrometheus/) 7 | * Pitch: [Pitches/Prometheus](https://forums.swift.org/t/client-side-prometheus-implementation/18098/) 8 | 9 | ## Package Description 10 | Prometheus client side implementation. 11 | 12 | | | | 13 | |--|--| 14 | | **Package Name** | `SwiftPrometheus` | 15 | | **Module Name** | `Prometheus` | 16 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 17 | | **License** | [Apache 2.0](https://github.com/MrLotU/SwiftPrometheus/blob/master/LICENSE) | 18 | | **Dependencies** | swift-nio 1.x.x or 2.x.x[1](#footnote_1) - swift-metrics > 1.0.0 | 19 | 20 | ## Introduction 21 | 22 | _For a background on [`swift-metrics`](https://github.com/apple/swift-metrics) see the metrics [proposal discussion](https://forums.swift.org/t/discussion-server-metrics-api/19600) and [feedback](https://forums.swift.org/t/feedback-server-metrics-api/21353) threads._ 23 | 24 | [Prometheus](https://prometheus.io) is a service that scrapes metrics from other services, and stores them in a time series database to be queried and alerted upon. SwiftPrometheus is a client-side library to allow packages to export metrics in the [Prometheus format](https://openmetrics.io) in Swift, with the ability to use it both connected to & separately from swift-metrics. 25 | 26 | ## Motivation 27 | 28 | With Prometheus being one of the most widely used metric reporting tools, as well as a [graduated project of the Cloud Native Computing Foundation](https://www.cncf.io/announcement/2018/08/09/prometheus-graduates/), it's a cornerstone that can not be left out in a serverside ecosystem. This package is created for everyone to use & build upon for their metric reporting. 29 | 30 | ## Detailed design 31 | 32 | `SwiftPrometheus` is a [Prometheus](https://prometheus.io) client implementation, conforming to the `SwiftMetrics` API. It centers around the `PrometheusClient` class, and provides various metric types. 33 | 34 | Swift _libraries_ should depend upon and use the generic `SwiftMetrics` API, in order to not force end-users down the path of using a specific backend. Swift _applications_ though should bootstrap the `PrometheusClient` and use the `SwiftMetrics` API _when practical_, yet are free to use the `PrometheusClient` directly as well – as an application does not need to concern itself with its consumers being "locked into" a specific backend. 35 | The [Prometheus metric types](https://www.prometheus.io/docs/concepts/metric_types/) are: 36 | * Counter - A *counter* is a cumulative metric that represents a single [monotonically increasing counter](https://en.wikipedia.org/wiki/Monotonic_function) whose value can only increase or be reset to zero on restart. 37 | * Gauge - A *gauge* is a metric that represents a single numerical value that can arbitrarily go up and down. 38 | * Histogram - A *histogram* samples observations (usually things like request durations or response sizes) and counts them in configurable buckets. It also provides a sum of all observed values. 39 | * Summary - Similar to a *histogram* , a *summary* samples observations (usually things like request durations and response sizes). While it also provides a total count of observations and a sum of all observed values, it calculates configurable quantiles over a sliding time window. 40 | 41 | SwiftPrometheus provides fully featured implementations for all of them, including a thin wrapper around them for integration with `swift-metrics`. 42 | 43 | ### API Layout 44 | The following section describes the public API of this package and is split into two parts. The first describes the suggested pattern of integrating directly with the `swift-metrics` package. The second delves into the increased flexibility granted by directly working with Prometheus metric types, at the cost of increased lock-in. 45 | 46 | #### With swift-metrics 47 | For use with swift-metrics, bootstrap the MetricsSystem with an instance of `PrometheusClient`: 48 | ```swift 49 | let myProm = PrometheusClient() 50 | MetricsSystem.bootstrap(myProm) 51 | ``` 52 | After that, you can use the metric types used by swift-metrics for your metrics. The mapping is as follows: 53 | | swift-metrics | SwiftPrometheus | 54 | |--|--| 55 | | Counter | Counter | 56 | | Gauge | Gauge | 57 | | Recorder (agg) | Histogram | 58 | | Timer | Summary | 59 | 60 | --- 61 | 62 | To get a hold of your `PrometheusClient` either to: 63 | a) use custom prometheus behaviour; or 64 | b) get your metrics output 65 | there is a utility function on `MetricsSystem` 66 | ```swift 67 | let myProm = try MetricsSystem.prometheus() 68 | ``` 69 | This will either return the `PrometheusClient` used with `.bootstrap()` or throw an error if `MetricsSystem` was not bootstrapped with `PrometheusClient` 70 | *Note: There currently is no support for retrieving a `PrometheusClient` when using `MultiplexMetricsHandler`* 71 | 72 | #### Without swift-metrics 73 | 74 | To get started, initialise an instance of `PrometheusClient` 75 | ```swift 76 | import Prometheus 77 | let myProm = PrometheusClient() 78 | ``` 79 | Once done, you can use the `create*` APIs to create any of the above described metric types. 80 | 81 | ```swift 82 | // MetricLabels is a helper type used to add labeled metrics. 83 | struct MyCodable: MetricLabels { 84 | var thing: String = "*" 85 | } 86 | 87 | // - Counter 88 | let counter = myProm.createCounter(forType: Int.self, named: "my_counter", helpText: "Just a counter", initialValue: 42, withLabelType: MyCodable.self) 89 | 90 | counter.inc() // Increment by one 91 | counter.inc(12) // Increment by a value 92 | counter.inc(12, MyCodable(thing: "test")) // Increment a labeled counter 93 | 94 | // - Gauge 95 | let gauge = myProm.createGauge(forType: Int.self, named: "my_gauge", helpText: "Just a gauge", initialValue: 42, withLabelType: MyCodable.self) 96 | gauge.inc() // Same APIs as Counter 97 | gauge.dec() // Same APIs as `inc()` but reversed. 98 | gauge.set(42) // Set the gauge to a specific value 99 | 100 | // - Histogram 101 | // Histograms use special labels, different than the Counter & Gauge 102 | struct HistogramLabel: HistogramLabels { 103 | var le: String = "" 104 | let route: String 105 | 106 | init() { 107 | self.route = "*" 108 | } 109 | 110 | init(_ route: String) { 111 | self.route = route 112 | } 113 | } 114 | 115 | let histogram = myProm.createHistogram(forType: Double.self, named: "my_histogram", helpText: "Just a histogram", labels: HistogramLabel.self) 116 | 117 | histogram.observe(123) // Observes a value 118 | 119 | // - Summary 120 | // Like Histograms, Summaries use different label types. 121 | struct SummaryLabel: SummaryLabels { 122 | var quantile: String = "" 123 | let route: String 124 | 125 | init() { 126 | self.route = "*" 127 | } 128 | 129 | init(_ route: String) { 130 | self.route = route 131 | } 132 | } 133 | 134 | let summary = myProm.createSummary(forType: Double.self, named: "my_summary", helpText: "Just a summary", labels: SummaryLabel.self) 135 | 136 | summary.observe(123) // Observes a value 137 | ``` 138 | Then, after you have some metric types, you can use `.collect()` on your `PromtheusClient` to get your Prometheus formatted string with all the data. 139 | For example, in a Vapor app: 140 | ```swift 141 | router.get("/metrics") { req -> String in 142 | return myProm.collect() 143 | } 144 | ``` 145 | 146 | ## Maturity Justification 147 | 148 | The implementation has the full feature set required for production use and meets the minimum requirements set forth by the SSWG. 149 | 150 | ## Alternatives considered 151 | 152 | Other than using a different metrics backend than Prometheus, there are not many alternatives to consider. One thing I'd like to point out though: 153 | 154 | This library has support for the destroying of metrics in the way set forth by the `swift-metrics` package. However, as described in the Prometheus documentation, once a metric is created with a specific type, so for example a `Counter` named `my_counter` and that counter is destroyed, it's not allowed to, at a later time, re-create a metric named `my_counter` with a DIFFERENT type. (Creating another counter is fine). To keep track of this, `PrometheusClient` will hold a dictionary of metric names & types. (`[String: MetricType]`). This means that even if you destroy your metrics, your memory footprint will (gradually) increase. All of this is process bound and will reset on a process restart. 155 | 156 | --- 157 | 158 | 1 For NIO 2 use SwiftPrometheus 1.x.x, for NIO 1 use SwiftPrometheus 0.x.x 159 | -------------------------------------------------------------------------------- /proposals/0009-swift-crypto.md: -------------------------------------------------------------------------------- 1 | # Swift Crypto 2 | 3 | * Proposal: [SSWG-0009](0009-swift-crypto.md) 4 | * Authors: [Cory Benfield](https://github.com/Lukasa), [Frederic Jacobs](https://github.com/FredericJacobs) 5 | * Sponsor(s): Apple 6 | * Review Manager: TBD 7 | * Status: [**Implemented**](https://github.com/apple/swift-crypto) 8 | 9 | ## Package Description 10 | 11 | Swift Crypto is an open-source implementation of a substantial portion of the API of [Apple CryptoKit](https://developer.apple.com/documentation/cryptokit) suitable for use on Linux platforms. It enables cross-platform or server applications with the advantages of CryptoKit. 12 | 13 | | | | 14 | |--|--| 15 | | **Package Name** | `swift-crypto` | 16 | | **Module Name** | `Crypto` | 17 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 18 | | **License** | [Apache 2.0](https://choosealicense.com/licenses/apache-2.0/) | 19 | | **Dependencies** | None (Vendored copy of [BoringSSL](https://boringssl.googlesource.com/boringssl/)) | 20 | 21 | ## Introduction 22 | 23 | An extremely wide range of applications rely on accessing some form of cryptographic primitive in order to function safely and correctly. Swift Crypto brings the API of Apple CryptoKit, a framework focused on providing solutions to common cryptographic problems with safe, composable, high-level interfaces, into the wider Swift ecosystem. As server-side Swift is a vital part of the wider Swift ecosystem, Swift Crypto aims to be an important building block for a wide range of server-side applications and frameworks, bringing the same powerful APIs that are available on Apple platforms to server-side use-cases. 24 | 25 | ## Motivation 26 | 27 | The number of server-side use-cases for cryptography is enormous, and affects almost all server-side applications in one form or another. Often these use-cases can be quite simple, such as needing to verify message hashes. However, in many cases the work required is substantially more complex, such as performing key agreement operations to establish shared encryption secrets, or performing message signing operations. 28 | 29 | Today there are a wide range of cryptographic libraries available to server developers to allow them to perform cryptographic operations. Most of these libraries, however, are quite low-level. This leads to users needing to interact with raw pointers, or to manually manage memory, or to have to make complex choices about exactly what cryptographic technologies to use. In the best case this can lead to decision paralysis, but in the worst case users can inadvertently introduce security critical vulnerabilities into their applications. 30 | 31 | We believe that there is an important space in the ecosystem for a library that focuses on providing high-quality interfaces to a limited number of cryptographic primitives. The goal is to provide safe, high-performance APIs to a collection of cryptographic "good choices". In the vast majority of cases, developers will not need anything other than what Swift Crypto provides, and will be able to write safe Swift code to interact with these high-level APIs without needing to be experts in their use. In some cases developers will be developing brand new systems that use cryptography: in these cases, they can safely pick anything from Swift Crypto (that doesn't include the word `Insecure` in its API!) and be confident that their choice is a good one. 32 | 33 | ## Detailed design 34 | 35 | Swift Crypto has an extremely large API surface, so an exhaustive exploration of that API would make this document impractically long. We recommend consulting [the Apple CryptoKit documentation](https://developer.apple.com/documentation/cryptokit) for an outline of this API. However, we can provide a high level overview of the functions provided by Swift Crypto, and some examples of the API. 36 | 37 | ### Cryptographic Hash Functions 38 | 39 | Cryptographic hash functions are commonly used to provide a "message digest": essentially, it maps an arbitrarily sized chunk of data into a fixed size chunk of data. A cryptographic hash function must be a "one-way function", which means that given a specific digest it must be very hard to produce a message that would generate that digest. Note that cryptographic hash functions are not suitable for password hashing: at this time, Swift Crypto does not contain any hash functions that _are_ suitable for this use-case. 40 | 41 | Swift Crypto provides 5 cryptographic hash functions: 42 | 43 | - `SHA256` 44 | - `SHA384` 45 | - `SHA512` 46 | - `Insecure.SHA1` 47 | - `Insecure.MD5` 48 | 49 | Each of these hash functions supports two usage modes: one-shot and incremental/streaming. The one shot mode is extremely straightforward to use: 50 | 51 | ```swift 52 | static func hash(data: D) -> Self.Digest where D : DataProtocol 53 | ``` 54 | 55 | They can also be used incrementally with the following functions: 56 | 57 | ```swift 58 | init() 59 | 60 | mutating func update(data: D) where D : DataProtocol 61 | 62 | mutating func update(bufferPointer: UnsafeRawBufferPointer) 63 | 64 | func finalize() -> Self.Digest 65 | ``` 66 | 67 | Swift Crypto's cryptographic hash functions and digest types are all value types. This allows interesting use-cases built on top of value semantics, such as rapidly calculating the digest for a number of different values that have the same prefix and different suffixes. 68 | 69 | Digest's are simple types: they are sequences of UInt8, and they are able to be compared with both themselves and any arbitrary sequence of bytes. 70 | 71 | ### Message Authentication Codes 72 | 73 | A message authentication code (MAC) is a short chunk of information that is used to confirm that a message was sent by a specific sender, and has not been modified in transit. These are heavily used in any circumstance where information is transferred between multiple peers where it is possible to ensure that these peers have a shared secret, or in constructions where data is handed to an untrusted party that will eventually send it back to you (e.g. macaroons). 74 | 75 | Swift Crypto supports one such MAC, HMAC. HMAC is parameterised over the hash functions above. Like the hash functions above, HMACs can be calculated using both a one-shot API and a streaming API. The streaming API looks essentially identical to the hash function API, but substitutes `HashFunction.Digest` for `HMAC.MAC`. The one-shot API is: 76 | 77 | ```swift 78 | static func authenticationCode(for data: D, using key: SymmetricKey) -> HMAC.MAC where D : DataProtocol 79 | ``` 80 | 81 | Once again, this is a generic API that allows a wide range of data types to be used, including SwiftNIO's `ByteBufferView` and Foundation's `Data`. 82 | 83 | This API also provides an extra helper for users that are already holding a `HMAC.MAC`: 84 | 85 | ```swift 86 | static func isValidAuthenticationCode(_ authenticationCode: HMAC.MAC, authenticating authenticatedData: D, using key: SymmetricKey) -> Bool where D : DataProtocol 87 | ``` 88 | 89 | This allows users to express the high level question ("is this MAC valid for that data") without needing to directly calculate the MAC over the data they want to verify. 90 | 91 | ### Ciphers 92 | 93 | Swift Crypto provides two ciphers for use. In both cases these are Authenticated Encryption with Additional Data (AEAD) ciphers. These are modern constructions that are extremely resilient against attacks that can be launched by modifying the ciphertext of encrypted data. As AEAD ciphers cannot operate in a streaming mode, both ciphers offer one-shot APIs. There are four functions provided: 94 | 95 | ```swift 96 | static func seal(_ message: Plaintext, using key: SymmetricKey, nonce: Cipher.Nonce? = nil) throws -> Cipher.SealedBox where Plaintext : DataProtocol 97 | static func seal<Plaintext, AuthenticatedData>(_ message: Plaintext, using key: SymmetricKey, nonce: Cipher.Nonce? = nil, authenticating authenticatedData: AuthenticatedData) throws -> Cipher.SealedBox where Plaintext : DataProtocol, AuthenticatedData : DataProtocol 98 | 99 | static func open(_ sealedBox: Cipher.SealedBox, using key: SymmetricKey) throws -> Data 100 | static func open<AuthenticatedData>(_ sealedBox: Cipher.SealedBox, using key: SymmetricKey, authenticating authenticatedData: AuthenticatedData) throws -> Data where AuthenticatedData : DataProtocol 101 | ``` 102 | 103 | These APIs provide simple options for the common case where there is no authenticated additional data, while allowing users to provide that data when needed. They also choose sensible random values for nonces when users don't need to provide a specific one. 104 | 105 | Both ciphers also offer a `SealedBox` construction. This construction is an association of plaintext, nonce, and tag, the complete union of things that need to be preserved for decryption of the data. `SealedBox` supports a default serialization mode as well, so in many cases users can simply use `SealedBox.combined` to emit the data and `SealedBox.init(combined:)` to parse it. 106 | 107 | ### Public Key Cryptography 108 | 109 | The gold standard of modern public key cryptography is elliptic curve cryptography. Swift Crypto supports the most widely-used elliptic curve constructions. In particular, it supports both key agreement and signing using the three major NIST elliptic curves (P-256, P-384, and P-521) and Curve 25519. These curves cover the vast majority of elliptic curve uses today, and offer a wide range of compatibility with other implementations. 110 | 111 | The curves provide extensive APIs for signing and verifying signatures, as well as performing key exchanges. These APIs follow the patterns above: they provide generic interfaces over `DataProtocol`, they return typed objects (e.g. `ECDSASignature`) with value semantics, and they have very little configuration. There are also helpers for generating keys from random bytes. 112 | 113 | ## Maturity Justification 114 | 115 | While CryptoKit has wide usage on Apple platforms, Swift Crypto has up until it was released on February 3rd 2020 only been used by Apple internally. For this reason it does not meet the criterion for moving directly to **Incubating**. Due to the importance of this use-case and the value of a shared API between client and server platforms, we expect that Swift Crypto will rapidly acquire sufficient use to move to **Incubating** and **Graduated**, but at this time **Sandbox** is the most appropriate case. 116 | 117 | 118 | -------------------------------------------------------------------------------- /proposals/0014-swift-backtrace.md: -------------------------------------------------------------------------------- 1 | # Swift Backtrace 2 | 3 | * Proposal: SSWG-0014 4 | * Authors: [Tomer Doron](https://github.com/tomerd) 5 | * Review Manager: [Tanner Nelson](https://github.com/tanner0101) 6 | * Status: Active Review, 15 Jun 2020 ..< 30 Jun 2020 7 | * Implementation: [Swift Backtrace](https://github.com/swift-server/swift-backtrace) 8 | 9 | ## Package Description 10 | 11 | Swift Backtrace provides support for automatically printing crash backtraces of Swift programs on Linux. 12 | 13 | | | | 14 | |--|--| 15 | | **Package Name** | `swift-backtrace` | 16 | | **Module Name** | `Backtrace` | 17 | | **Proposed Maturity Level** | [Incubating](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 18 | | **License** | [Apache License 2.0](https://github.com/swift-server/swift-backtrace/blob/master/LICENSE.txt) | 19 | | **Dependencies** | [libbacktrace](https://github.com/ianlancetaylor/libbacktrace) (vendored) | 20 | 21 | ## Introduction & Motivation 22 | 23 | Printing backtraces when applications crash is a critical component in diagnostics of 24 | real-world production issues on the Server. 25 | 26 | When applications crash on the Server, it is desired for the crash information to be 27 | captured and printed along side the application logs so that log aggregation tools 28 | (e.g. Splunk) could include the crash information and used for alerting and further 29 | analysis offline. 30 | 31 | At this point of time, Swift does not print crash backtraces when compiled in 32 | release mode on Linux. 33 | This means that Swift server applications can crash silently making the operation of 34 | such applications difficult. 35 | 36 | 37 | ## Proposed solution 38 | 39 | Introduce a Linux centric library that captures crashes and prints backtraces 40 | before the program exits. 41 | 42 | Applications (or application frameworks on their behalf) will need to add the 43 | following as the first thing on the application's entrypoint (`main.swift`): 44 | 45 | ```swift 46 | import Backtrace // <-- Import the module 47 | 48 | Backtrace.install() // <--- Install the crash handler 49 | ``` 50 | 51 | ## Detailed design 52 | 53 | ### Capturing backtraces 54 | 55 | Crashes are captured by installing a `SIGILL` signal listener. 56 | Swift will uses `SIGILL` to signal a crash, which allows the library to begin 57 | collecting the backtraces. `libbacktrace` which is vendored as part of this library 58 | is then used to produce symbolic backtraces. 59 | 60 | Note: Capturing crash signal and halting the execution of the program is generally not a good idea. 61 | For example, attempting to allocate memory while crashing under certain conditions 62 | may lead to security vulnerabilities and deadlocks. 63 | In this case, this is acceptable since the backtrace collection will continue to 64 | eventually crash the program. 65 | 66 | ### Demangaling symbols 67 | 68 | The library parses the symbolic backtraces from `libbacktrace` and demangles 69 | them using the internal `swift_demangle` function from the stdlib. 70 | 71 | Note: Using internal functions like `swift_demangle` is generally not a good idea. 72 | In this case, this is acceptable since we plan to deprecate this package as soon 73 | as the Swift runtime will offer this functionality. 74 | 75 | ### Printing 76 | 77 | The library currently prints directly to `stderr`. 78 | This works in most cases, but we should consider an extension with 79 | [swift-log](https://github.com/apple/swift-log) to hook the printing into the 80 | Application's logging system. 81 | 82 | ## Maturity Justification 83 | 84 | This package is already being used across the Swift server ecosystem in many projects. 85 | 86 | ## Alternatives considered 87 | 88 | The obvious correct solution to backtraces on Linux is to add support 89 | for them in the Swift runtime. 90 | Since the changes required to support that are non-trivial and may take a awhile to ship, 91 | this package provides a stop-gap solution. 92 | 93 | Another aspect considered was the in-process vs. out-of-process approach. 94 | While macOS/iOS have strong support for collecting crash reports out-of-process 95 | such support does not exits universally on the server ecosystem which is made of 96 | a wide variety of operating systems. Therefore, we found the in-process approach 97 | to be more practical. 98 | -------------------------------------------------------------------------------- /proposals/0015-swift-service-lifecycle.md: -------------------------------------------------------------------------------- 1 | # Swift Service Lifecycle 2 | 3 | * Proposal: SSWG-0015 4 | * Authors: [Tomer Doron](https://github.com/tomerd) 5 | * Review Manager: [Peter Adams](https://github.com/PeterAdams-A) 6 | * Status: Active Review 7 Aug 2020 ..< 22 Aug 2020 7 | * Implementation: [Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle) 8 | 9 | ## Package Description 10 | 11 | Swift Service Lifecycle provides a basic mechanism to cleanly start up and shut down the application, freeing resources in order before exiting. 12 | It also provides a `Signal`-based shutdown hook, to shut down on signals like `TERM` or `INT`. 13 | 14 | | | | 15 | |--|--| 16 | | **Package Name** | `swift-service-lifecycle` | 17 | | **Module Name** | `ServiceLifecycle` | 18 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 19 | | **License** | [Apache License 2.0](https://github.com/swift-server/swift-backtrace/blob/master/LICENSE.txt) | 20 | | **Dependencies** | [Backtrace](https://github.com/swift-server/swift-backtrace), [SwiftLog](https://github.com/apple/swift-log), [SwiftMetrics](https://github.com/apple/swift-metrics) | 21 | 22 | ## Introduction & Motivation 23 | 24 | Most services have startup and shutdown workflow-logic which is often sensitive to failure and hard to get right. 25 | Startup sequences include actions like initializing thread pools, running data migrations, warming up caches, and other forms of state initialization before 26 | taking traffic or accepting events. 27 | Shutdown sequences include freeing up resources that hold on to file descriptors or other system resources that may leak if not cleared correctly. 28 | 29 | Today, server applications and frameworks must find ways to address the need on their own, which could be error prone. 30 | To make things safer and easier, Service Lifecycle codifies this common need in a safe, reusable and framework-agnostic way. 31 | It is designed to be integrated with any server framework or directly in a server application’s main. 32 | 33 | ## Proposed solution 34 | 35 | The main types in the library are `ServiceLifecycle` and `ComponentLifecycle`. 36 | 37 | `ServiceLifecycle` is the most commonly used type. 38 | It is designed to manage the top-level Application (Service) lifecycle, 39 | and in addition to managing the startup and shutdown flows it can also set up `Signal` trap for shutdown and install backtraces. 40 | 41 | `ComponentLifecycle` manages a state machine representing the startup and shutdown logic flow. 42 | In larger Applications (Services) `ComponentLifecycle` can be used to manage the lifecycle of subsystems, such that `ServiceLifecycle` can start and shutdown `ComponentLifecycle`s. 43 | 44 | ### Registering items 45 | 46 | `ServiceLifecycle` and `ComponentLifecycle` are containers for `LifecycleTask`s which need to be registered using a `LifecycleHandler` - a container for synchronous or asynchronous closures. 47 | 48 | Synchronous handlers are defined as `() throws -> Void`. 49 | 50 | Asynchronous handlers defined are as `(@escaping (Error?) -> Void) -> Void`. 51 | 52 | `LifecycleHandler` comes with static helpers named `async` and `sync` designed to help simplify the registration call to: 53 | 54 | ```swift 55 | let foo = ... 56 | lifecycle.register( 57 | label: "foo", 58 | start: .sync(foo.syncStart), 59 | shutdown: .sync(foo.syncShutdown) 60 | ) 61 | ``` 62 | 63 | Or the async version: 64 | 65 | ```swift 66 | let foo = ... 67 | lifecycle.register( 68 | label: "foo", 69 | start: .async(foo.asyncStart), 70 | shutdown: .async(foo.asyncShutdown) 71 | ) 72 | ``` 73 | 74 | or, just shutdown: 75 | 76 | ```swift 77 | let foo = ... 78 | lifecycle.registerShutdown( 79 | label: "foo", 80 | .sync(foo.syncShutdown) 81 | ) 82 | ``` 83 | Or the async version: 84 | 85 | ```swift 86 | let foo = ... 87 | lifecycle.registerShutdown( 88 | label: "foo", 89 | .async(foo.asyncShutdown) 90 | ) 91 | ``` 92 | 93 | you can also register a collection of `LifecycleTask`s (less typical) using: 94 | 95 | ```swift 96 | func register(_ tasks: [LifecycleTask]) 97 | 98 | func register(_ tasks: LifecycleTask...) 99 | ``` 100 | 101 | ### Configuration 102 | 103 | `ServiceLifecycle` initializer takes optional `ServiceLifecycle.Configuration` to further refine the `ServiceLifecycle` behavior: 104 | 105 | * `logger`: Defines the `Logger` to work with. By default, `Logger(label: "Lifecycle")` is used. 106 | 107 | * `callbackQueue`: Defines the `DispatchQueue` on which startup and shutdown handlers are executed. By default, `DispatchQueue.global` is used. 108 | 109 | * `shutdownSignal`: Defines what, if any, signals to trap for invoking shutdown. By default, `INT` and `TERM` are trapped. 110 | 111 | * `installBacktrace`: Defines if to install a crash signal trap that prints backtraces. This is especially useful for applications running on Linux since Swift does not provide backtraces on Linux out of the box. This functionality is provided via the [Swift Backtrace](https://github.com/swift-server/swift-backtrace) library. 112 | 113 | ### Starting the lifecycle 114 | 115 | Use the `start` function to start the application. 116 | Start handlers passed using the `register` function will be called in the order the items were registered in. 117 | 118 | `start` is an asynchronous operation. 119 | If a startup error occurred, it will be logged and the startup sequence will halt on the first error, and bubble it up to the provided completion handler. 120 | 121 | ```swift 122 | lifecycle.start { error in 123 | if let error = error { 124 | logger.error("failed starting \(self) ☠️: \(error)") 125 | } else { 126 | logger.info("\(self) started successfully 🚀") 127 | } 128 | } 129 | ``` 130 | 131 | ### Shutdown 132 | 133 | The typical use of the library is to call on `wait` after calling `start`. 134 | 135 | ```swift 136 | lifecycle.start { error in 137 | ... 138 | } 139 | lifecycle.wait() // <-- blocks the thread 140 | ``` 141 | 142 | If you are not interested in handling start completion, there is also a convenience method: 143 | 144 | ```swift 145 | lifecycle.startAndWait() // <-- blocks the thread 146 | ``` 147 | 148 | Both `wait` and `startAndWait` are blocking operations that wait for the lifecycle library to finish the shutdown sequence. 149 | The shutdown sequence is typically triggered by the `shutdownSignal` defined in the configuration. By default, `INT` and `TERM` are trapped. 150 | 151 | During shutdown, the shutdown handlers passed using the `register` or `registerShutdown` functions are called in the reverse order of the registration. E.g. 152 | 153 | ``` 154 | lifecycle.register("1", ...) 155 | lifecycle.register("2", ...) 156 | lifecycle.register("3", ...) 157 | ``` 158 | 159 | startup order will be 1, 2, 3 and shutdown order will be 3, 2, 1. 160 | 161 | If a shutdown error occurred, it will be logged and the shutdown sequence will *continue* to the next item, and attempt to shut it down until all registered items that have been started are shut down. 162 | 163 | In more complex cases, when `Signal`-trapping-based shutdown is not appropriate, you may pass `nil` as the `shutdownSignal` configuration, and call `shutdown` manually when appropriate. This is designed to be a rarely used pressure valve. 164 | 165 | `shutdown` is an asynchronous operation. Errors will be logged and bubbled up to the provided completion handler. 166 | 167 | ### Complex Systems and Nesting of Subsystems 168 | 169 | In larger Applications (Services) `ComponentLifecycle` can be used to manage the lifecycle of subsystems, such that `ServiceLifecycle` can start and shutdown `ComponentLifecycle`s. 170 | 171 | In fact, since `ComponentLifecycle` conforms to `LifecycleTask`, 172 | it can start and stop other `ComponentLifecycle`s, forming a tree. E.g.: 173 | 174 | ```swift 175 | struct SubSystem { 176 | let lifecycle = ComponentLifecycle(label: "SubSystem") 177 | let subsystem: SubSubSystem 178 | 179 | init() { 180 | self.subsystem = SubSubSystem() 181 | self.lifecycle.register(self.subsystem.lifecycle) 182 | } 183 | 184 | struct SubSubSystem { 185 | let lifecycle = ComponentLifecycle(label: "SubSubSystem") 186 | 187 | init() { 188 | self.lifecycle.register(...) 189 | } 190 | } 191 | } 192 | 193 | let lifecycle = ServiceLifecycle() 194 | let subsystem = SubSystem() 195 | lifecycle.register(subsystem.lifecycle) 196 | 197 | lifecycle.start { error in 198 | ... 199 | } 200 | lifecycle.wait() 201 | ``` 202 | 203 | ### Compatibility with SwiftNIO Futures 204 | 205 | [SwiftNIO](https://github.com/apple/swift-nio) is a popular networking library that among other things provides Future abstraction named `EventLoopFuture`. 206 | 207 | Swift Service Lifecycle comes with a compatibility module designed to make managing SwiftNIO based resources easy. 208 | 209 | Once you import `LifecycleNIOCompat` module, `LifecycleHandler` gains a static helper named `eventLoopFuture` designed to help simplify the registration call to: 210 | 211 | ```swift 212 | let foo = ... 213 | lifecycle.register( 214 | label: "foo", 215 | start: .eventLoopFuture(foo.start), 216 | shutdown: .eventLoopFuture(foo.shutdown) 217 | ) 218 | ``` 219 | 220 | or, just shutdown: 221 | 222 | ```swift 223 | let foo = ... 224 | lifecycle.registerShutdown( 225 | label: "foo", 226 | .eventLoopFuture(foo.shutdown) 227 | ) 228 | ``` 229 | 230 | 231 | ## Maturity Justification 232 | 233 | This is a new package. 234 | 235 | ## Alternatives considered 236 | 237 | None 238 | -------------------------------------------------------------------------------- /proposals/0016-soto.md: -------------------------------------------------------------------------------- 1 | # Soto for AWS 2 | 3 | * Proposal: [SSWG-0016](0016-soto.md) 4 | * Authors: [Adam Fowler](https://github.com/adam-fowler), [Fabian Fett](https://github.com/fabianfett), [Jonathan McAllister](https://github.com/jonnymacs), [Joe Smith](https://github.com/Yasumoto), [Yuki Takei](https://github.com/noppoman) 5 | * Review Manager: [Kaitlin Mahar](https://github.com/kmahar) 6 | * Status: Active Review 28 Oct 2020 ..< 12 Nov 2020 7 | * Implementation: [soto](https://github.com/soto-project/soto.git) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/soto-for-aws/40806) 9 | 10 | <!-- *During the review process, add the following fields as needed:* 11 | 12 | * Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) 13 | * Previous Revision(s): [1](https://github.com/swift-server/sswg/blob/...commit-ID.../proposals/NNNN-filename.md) 14 | * Previous Proposal(s): [SSWG-XXXX](XXXX-filename.md) 15 | --> 16 | ## Package Description 17 | Soto is a Swift only SDK for Amazon Web Services. It supports Linux, macOS and iOS. 18 | 19 | | | | 20 | |--|--| 21 | | **Package Name** | `soto` | 22 | | **Module Name** | `SotoAPIGateway`, `SotoDynamoDB`, `SotoS3`, `SotoSNS` etc | 23 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/master/process/incubation.md#process-diagram) | 24 | | **License** | [Apache 2.0](https://choosealicense.com/licenses/apache-2.0/) | 25 | | **Dependencies** | [swift-nio](https://github.com/apple/swift-nio), [swift-nio-ssl](https://github.com/apple/swift-nio-ssl), [swift-nio-transport-services](https://github.com/apple/swift-nio-transport-services), [swift-crypto](https://github.com/apple/swift-crypto), [swift-metrics](https://github.com/apple/swift-metrics), [swift-log](https://github.com/apple/swift-log), [async-http-client](https://github.com/swift-server/async-http-client) | 26 | | **Code Generator Dependencies** | [swift-argument-parser](https://github.com/apple/swift-argument-parser), [Stencil](https://github.com/soto-project/Stencil) | 27 | 28 | 29 | ## Introduction 30 | 31 | Amazon Web Services (AWS) is the largest provider of cloud services. Many companies rely on the systems and automation they provide. These include storage, security, identity, messaging, databases, compute, machine learning and analytics to mention a few. 32 | 33 | AWS provides SDKs to interact with their services using the languages Javascript, Go, Python, C#, PHP, C++, Java, Ruby but don't provide a first-party, fully comprehensive SDK in Swift. 34 | 35 | Soto provides a Swift NIO based interface to access all Amazon Web Services. 36 | 37 | ## Motivation 38 | 39 | There are many Swift libraries that provide access to one or maybe two AWS services. But many teams working in the AWS ecosystem leverage multiple services, typically within the same application codebase. Having to deal with multiple libraries with different APIs, signing and serialisation code is not ideal. 40 | 41 | The main purpose of Soto project is to rectify the lack of comprehensive Swift SDK for AWS. The project provides access to all AWS services through a consistent API that is closely integrated with Swift NIO. 42 | 43 | ### Proposed solution 44 | 45 | Soto is a Swift based interface to Amazon Web Services. It supports Linux, macOS and iOS. It is split into three sections. 46 | 47 | 1) The core functionality repository [soto-core](https://github.com/soto-project/soto-core). This is a NIO 2.0 based module. Requests to the core return an `EventLoopFuture` that will be populated with results at a later point. The core module encodes requests, signs requests using AWS credentials, sends them to AWS and then decodes the response. 48 | 49 | 2) [Soto](https://github.com/soto-project/soto) contains the service API files. Each AWS service has its own target which contains a service client object along with its operations and their input and output structures. The operations map to the REST api of the AWS service. 50 | 51 | 3) The Code Generator which builds the service API files mentioned in section 2. This uses the JSON model files Amazon provides (which are synced from Amazon's [aws-sdk-go](https://github.com/aws/aws-sdk-go/tree/master/models) repository) to generate Swift versions of the service APIs. It loads the model files using `Codable` and then outputs the Swift service files using the templating engine [Stencil](https://github.com/stencilproject/Stencil). 52 | 53 | ### Releases 54 | 55 | The current official release of Soto is 4.9.0. For this proposal though I will be proposing what will become v5.0.0. A beta release of v5.0.0 has already been released which includes all that is detailed below. 56 | 57 | ## Detailed design 58 | 59 | The SDK consists of two main parts. The client object `AWSClient` which does all the communication with AWS and manages your AWS credentials and the service objects which each contain the configuration setup for communicating with a specific AWS service and functions for each of the individual operations available to that service. 60 | 61 | ### AWSClient 62 | 63 | The `AWSClient` holds your credential information and HTTP client and is the control room of the Soto project. All requests to AWS go through it. When initialising the `AWSClient` you can provide credentials directly as in the sample code below, but the client can also extract credentials from environment variables, the ~/.aws/credentials file in your home directory, from EC2 instance metadata or ECS provided credentials. 64 | 65 | For the HTTP client you can either provide your own or let it create its own. If left to create its own HTTP client it will use the `HTTPClient` from the swift-server library `async-http-client`. 66 | 67 | It is generally best practice to have only the one `AWSClient` in your Application. 68 | 69 | ```swift 70 | let awsClient = AWSClient( 71 | credentialProvider: .static(accessKeyId: "Your-Access-Key", secretAccessKey: "Your-Secret-Key"), 72 | httpClientProvider: .createNew 73 | ) 74 | ``` 75 | 76 | ### Service Object 77 | 78 | Each AWS service has it's own target and in that target a service object containing the service configuration and service API. These can be created as and when they are needed. 79 | 80 | ```swift 81 | import SotoDynamoDB 82 | let dynamoDB = DynamoDB(client: awsClient, region: .euwest1) 83 | ``` 84 | 85 | ### Operations 86 | 87 | Once you have your service object you can call AWS operations from it. The general structure is create request object, call operation with request object which returns a NIO `EventLoopFuture` that will be fulfilled with the response from the call later on. A simple example would be as follows. 88 | 89 | ```swift 90 | func downloadFile(filename: String, bucket: String) -> EventLoopFuture<ByteBuffer?> { 91 | let s3 = S3(client: awsClient, region: .useast1) 92 | let request = S3.GetObjectRequest(bucket: bucket, key: filename) 93 | return s3.getObject(request).map { $0.body?.asByteBuffer() } 94 | } 95 | ``` 96 | This downloads a file from S3 and returns an `EventLoopFuture` that will receive the file contents. 97 | 98 | As the SDK returns `EventLoopFutures` these can be chained together to produce more complex behaviours. As in the following example. 99 | 100 | ```swift 101 | let cloudTrail = CloudTrail(client: awsClient, region:.euwest1) 102 | let ses = SES(client: awsClient, region:.euwest1) 103 | 104 | let request = CloudTrail.LookupEventsRequest( 105 | endTime: Date(), 106 | lookupAttributes: [.init(attributeKey: .username, attributeValue: "adamfowler")], 107 | startTime: Date(timeIntervalSinceNow: -24*60*60) 108 | ) 109 | let response = cloudTrail.lookupEvents(request) 110 | .map { $0.events ?? [] } 111 | .flatMap { events -> EventLoopFuture<SES.SendEmailResponse> in 112 | let messageBody = "Usage:\n\(events.description)" 113 | let messageSubject = "Usage report for Adam Fowler" 114 | let request = SES.SendEmailRequest( 115 | destination: SES.Destination(toAddresses: ["joesmith@soto-project.org"]), 116 | message: .init(body:.init(text:.init(data:messageBody)), subject: .init(data: messageSubject)), 117 | source: "admin@soto-project.org") 118 | return ses.sendEmail(request) 119 | } 120 | ``` 121 | Here we are generating a Cloud Trail report detailing all the AWS events generated by user "adamfowler" in the last 24 hours and then sending it as an email to joesmith@soto-project.org using the Simple Email Service. 122 | 123 | ### Code Generation 124 | 125 | Soto provides interfaces for over 200 different AWS services. Each of these services has its own library. The service library code is generated from json model files supplied by Amazon, which we get via their [aws-sdk-go](https://github.com/aws/aws-sdk-go/tree/master/models) repository. The Soto project includes a Code Generation tool which loads the json model files using `Codable` and generates Swift code for each service using a custom version of the templating library [Stencil](https://github.com/stencilproject/Stencil). 126 | 127 | AWS have a new interface modeling language [Smithy](https://awslabs.github.io/smithy/). They have Smithy models for all the AWS services and are using these to generate code for their [`aws-sdk-go-v2`](https://github.com/aws/aws-sdk-go-v2) developer preview library. We are aware of this and are actively looking to support it once regular model updates are published. 128 | 129 | ### Additional functionality 130 | 131 | #### Pagination 132 | 133 | Many AWS operations return lists of items. These can be of an undefined length, so to avoid returning overly large responses pagination is a commmon pattern used across the services. For each operation that returns paginated results the SDK provides an additional function, with suffix "Paginator", for extracting all the results. The Cloud Trail example from above would only ever return the first 50 events in the list, as that is the maximum number of events the function is allowed to return. We can use the pagintor version of the function to extract all the events in the following manner. 134 | 135 | ```swift 136 | var events: [CloudTrail.Event] = [] 137 | let request = CloudTrail.LookupEventsRequest( 138 | endTime:TimeStamp(Date()), 139 | lookupAttributes: [.init(attributeKey: .username, attributeValue: "adamfowler")], 140 | startTime: TimeStamp(Date(timeIntervalSinceNow: -24*60*60)) 141 | ) 142 | let response = cloudTrail.lookupEventsPaginator(request) { response, eventLoop in 143 | if let eventsPage = response.events { 144 | events.append(contentsOf: eventsPage) 145 | } 146 | return eventLoop.makeSucceededFuture(true) 147 | } 148 | ``` 149 | The supplied closure appends results, supplied to it by the paginator function, to the `events` array. When finished, `events` should hold all the Cloud Trail events. 150 | 151 | #### Streaming Request data 152 | 153 | Where an operation expects a raw data payload. It is possible to stream that data. Instead of providing a single `ByteBuffer` you can provide a closure that supplies your data in smaller chunks. This is useful for situations where you don't want to store all your data in memory at one point. 154 | 155 | ```swift 156 | func streamData(eventLoop: EventLoop) -> EventLoopFuture<ByteBuffer> { 157 | let buffer = getNextChunk() // your function returning the next chunk from your buffer 158 | return eventLoop.makeSucceededFuture(buffer) 159 | } 160 | let request = S3.PutObjectRequest(body: .stream(size: totalDataSize, streamData), bucket: "myBucket", key: "myFile") 161 | let response = s3.putObject(request) 162 | ``` 163 | Here we are uploading a block of data supplied in chunks by the function `getNextChunk()` to an S3 bucket. Given the most popular use of this will probably be uploading files, there is also a `fileHandle` wrapper to `stream` that takes an `NIOFileHandle`. 164 | 165 | #### Streaming response data 166 | 167 | If an operation returns a raw data payload the SDK supplies an extra function, with suffix "Streaming", for receiving that raw payload in the smaller chunks that are passed through the HTTP Client. 168 | 169 | ```swift 170 | let kinesisVideo = KinesisVideoMedia(client: awsClient, endpoint: myVideoEndpoint) 171 | let request = KinesisVideoMedia.GetMediaInput(startSelector: .init(startSelectorType: .now), streamName: "myVideo") 172 | _ = kinesisVideo.getMediaStreaming(request) { byteBuffer, eventLoop in 173 | processMediaData() // your function processing byte buffer returned from HTTP client ... 174 | return eventLoop.makeSucceededFuture(()) 175 | } 176 | ``` 177 | The code above is streaming media content from a Kinesis video stream. The closure processes the byte buffers as they come through the HTTP client. It returns an eventLoop which is fulfilled when each buffer has been processed, in this case immediately. 178 | 179 | ### Swift server eco-system 180 | 181 | Soto is heavily integrated in the Swift server eco-system. It uses Swift server projects [swift-nio](https://github.com/apple/swift-nio), [async-http-client](https://github.com/swift-server/async-http-client), [swift-crypto](https://github.com/apple/swift-crypto), [swift-log](https://github.com/apple/swift-log) and [swift-metrics](https://github.com/apple/swift-metrics). The plan is also to integrate with the swift tracing library when that becomes available. 182 | 183 | ### Swift AWS Lambda runtime 184 | 185 | We think that Soto is the perfect match for the recently released Swift AWS Lambda runtime, providing access to all your AWS resources from your Swift developed Lambda functions. 186 | 187 | ## Maturity Justification 188 | 189 | Soto adheres to the minimal requirements of the [SSWG Incubation Process](https://github.com/swift-server/sswg/blob/master/process/incubation.md#minimal-requirements). The project is fairly mature and has had five different developers working on it over a three year period, although at the moment the majority of work is done by one developer. I don't know if this is good enough for the 2+ developers requirement. 190 | 191 | ## Alternatives considered 192 | 193 | AWS already provide an iOS targetted SDK [aws-sdk-ios](https://github.com/aws-amplify/aws-sdk-ios), but this is written almost exclusively in Objective C so is no use for server side Swift which is required to run on non-Darwin platforms. 194 | 195 | Another alternative to Soto is [smoke-aws](https://github.com/amzn/smoke-aws) which is part of the Amazon developed Smoke framework. For AWS they have generated code for a small set of services. In theory their code generator should be able to generate code for all AWS services. While the internals of Smoke do use Swift NIO, their API uses callbacks instead of `EventLoopFuture` making it harder to integrate with other Swift NIO based systems. 196 | 197 | Thanks for taking the time to read this proposal and we are looking forward to hear your comments and suggestions. 198 | -------------------------------------------------------------------------------- /proposals/0017-multipart-kit.md: -------------------------------------------------------------------------------- 1 | # MultipartKit 2 | 3 | * Proposal: [SSWG-0017](0017-multipart-kit.md) 4 | * Authors: [Siemen Sikkema](https://github.com/siemensikkema), [Tim Condon](https://github.com/0xTim) 5 | * Review Manager: [Kaitlin Mahar](https://github.com/kmahar) 6 | * Status: **Implemented** 7 | * Implementation: [MultipartKit](https://github.com/vapor/multipart-kit) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/multipartkit), [Proposal](https://forums.swift.org/t/sswg-0017-multipartkit/52586) 9 | 10 | <!-- 11 | *During the review process, add the following fields as needed:* 12 | 13 | * Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) 14 | * Previous Revision(s): [1](https://github.com/swift-server/sswg/blob/...commit-ID.../proposals/NNNN-filename.md) 15 | * Previous Proposal(s): [SSWG-XXXX](XXXX-filename.md) 16 | --> 17 | 18 | ## Package Description 19 | 20 | Multipart parser and serializer with `Codable` support for Multipart Form Data. 21 | 22 | | | | 23 | |--|--| 24 | | **Package Name** | `multipart-kit` | 25 | | **Module Name** | `MultipartKit` | 26 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 27 | | **License** | [MIT](https://choosealicense.com/licenses/mit/) | 28 | | **Dependencies** | [SwiftNIO >= 2.2.0](https://github.com/apple/swift-nio) | 29 | 30 | ## Introduction 31 | 32 | MultipartKit offers both low level parsing and serializing of Multipart data as well as high level Codable support for encoding and decoding Multipart form data. 33 | 34 | ## Motivation 35 | 36 | Multipart is a commonly used standard for transmitting data, often originating from HTML forms. It is particularly well-suited for binary data (like file uploads) but also supports structured data or a combination of the two. 37 | 38 | There exists currently no standard package for multipart in the Swift server ecosystem. MultipartKit was originally developed for Vapor but is independent of it. Lifting MultipartKit's status as an officially supported package would increase its visibility so people are less likely to reinvent the wheel and can contribute to the features, stability, and performance of MultipartKit instead. 39 | 40 | ## Proposed solution 41 | 42 | We propose that the existing MultipartKit implementation be promoted to an officially supported package as is. 43 | 44 | ## Detailed design 45 | 46 | ### Form Data 47 | 48 | The high level Form Data API revolves around `FormDataEncoder` and `FormDataDecoder`. 49 | 50 | Encoding some encodable value `foo` as multipart using a boundary value of `abc123` can be achieved as follows: 51 | 52 | ```swift 53 | let encoded = try FormDataEncoder().encode(foo, boundary: "abc123") 54 | ``` 55 | 56 | Similarly to decode this value into the type `Foo` we can do: 57 | 58 | ```swift 59 | let foo = try FormDataDecoder().decode(Foo.self, from: encoded, boundary: "abc123") 60 | ``` 61 | 62 | ### Nesting and Collections 63 | 64 | Nested data and collections can be represented using an angle bracket notation convention for the part names as follows: 65 | 66 | - `root[branch]`: a value named `branch` nested inside `root` 67 | - `root[branch][leaf]`: like the above but with `leaf` nested inside `branch` 68 | - `array[0]`: first value of `array` 69 | - `array[1][leaf]`: a value named `leaf` nested inside the second value of `array` 70 | 71 | ### Low level parsing of Multipart data 72 | 73 | MultipartKit comes with a callback-based `MultipartParser` that forms the basis for the Form Data coders. 74 | 75 | ```swift 76 | let parser = MultipartParser(boundary: "abc123") 77 | ``` 78 | 79 | Set up the callbacks in order to process the parsed data: 80 | 81 | ```swift 82 | parser.onHeader = { (field: String, value: String) in 83 | // gets called after each multipart header that is parsed 84 | } 85 | parser.onBody = { (buffer: ByteBuffer) in 86 | // gets called when (a part of) a multipart body is parsed 87 | } 88 | parser.onPartComplete = { 89 | // gets called when a part is fully parsed 90 | // the body along with any collected headers for the current part can now be considered complete 91 | } 92 | ``` 93 | 94 | Feed data into the parser: 95 | ```swift 96 | let data: ByteBuffer = ... 97 | parser.execute(data) 98 | ``` 99 | 100 | In case the data is arriving in chunks. The `execute` method can be called once for each chunk. 101 | 102 | ## Maturity Justification 103 | 104 | Although it has been used in production successfully alongside Vapor it would be good to see it used and tested in the wider Swift on the Server ecosystem. 105 | 106 | ## Alternatives considered 107 | 108 | None 109 | -------------------------------------------------------------------------------- /proposals/0018-mqtt-nio.md: -------------------------------------------------------------------------------- 1 | # MQTTNIO 2 | 3 | * Proposal: [SSWG-0018](0018-mqtt-nio.md) 4 | * Author: [Adam Fowler](https://github.com/adam-fowler) 5 | * Review Manager: [Tim Condon](https://github.com/0xTim) 6 | * Status: **Implemented** 7 | * Implementation: [mqtt-nio](https://github.com/adam-fowler/mqtt-nio) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/mqttnio/53238/), [Proposal](https://forums.swift.org/t/sswg-0018-mqttnio-proposal/54004) 9 | 10 | <!-- *During the review process, add the following fields as needed:* 11 | 12 | * Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) 13 | * Previous Revision(s): [1](https://github.com/swift-server/sswg/blob/...commit-ID.../proposals/NNNN-filename.md) 14 | * Previous Proposal(s): [SSWG-XXXX](XXXX-filename.md) 15 | --> 16 | ## Package Description 17 | MQTTNIO is a MQTT client built on top of SwiftNIO, that provides full support for both v3.1.1 and v5 of the MQTT protocol. 18 | 19 | | | | 20 | |--|--| 21 | | **Package Name** | mqtt-nio | 22 | | **Module Name** | MQTTNIO | 23 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 24 | | **License** | [Apache 2.0](https://choosealicense.com/licenses/apache-2.0/) | 25 | | **Dependencies** | [swift-nio](https://github.com/apple/swift-nio), [swift-nio-ssl](https://github.com/apple/swift-nio-ssl), [swift-nio-transport-services](https://github.com/apple/swift-nio-transport-services), [swift-log](https://github.com/apple/swift-log) | 26 | 27 | ## Introduction 28 | 29 | MQTT is a messaging protocol commonly used for communicating with IoT (Internet of Things) devices. It is a lightweight publish/subscribe message transport designed to have a small code footprint and network bandwidth. 30 | 31 | MQTTNIO is a MQTT client built on top of SwiftNIO, that provides full support for both v3.1.1 and v5 of the MQTT protocol. It runs on macOS, iOS and Linux. It supports WebSocket connections and TLS through both NIOSSL and NIOTransportServices. 32 | 33 | ## Motivation 34 | 35 | There are a number of Swift MQTT libraries out there but many are not built on top of SwiftNIO. And many only support one version of the protocol or don’t provide WebSocket or TLS connections. MQTTNIO provides all of these. The library has also recently gained new Swift concurrency APIs. 36 | 37 | ## Detailed design 38 | 39 | Most functionality MQTTNIO has is accessed through one object `MQTTClient`. This allows you to send MQTT messages to a MQTT server and add listeners to read messages received from the server. `MQTTClient` has `EventLoopFuture` and `async` APIs. For the purposes of this proposal I shall use the `async` APIs. 40 | 41 | ### Connection 42 | 43 | To connect to an MQTT server you need to create a `MQTTClient` and then call `connect`. The `configuration` parameter in `MQTTClient.init` allows you to define what kind of connection you would like (TLS, Websocket etc). 44 | 45 | ```swift 46 | // connect to mosquitto test server 47 | let client = MQTTClient( 48 | host: "test.mosquitto.org", 49 | port: 1883, 50 | identifier: "My unique identifier", 51 | eventLoopGroupProvider: .createNew, 52 | configuration: .init() 53 | ) 54 | // if a session was previously present for your identifier `sessionPresent` 55 | // will be `true` 56 | let sessionPresent = try await client.connect(cleanSession: false) 57 | ``` 58 | 59 | As with many Swift NIO based packages, the client will need to be shutdown before it can be deleted. 60 | ```swift 61 | try await client.shutdown() 62 | ``` 63 | 64 | ### Sending messages 65 | 66 | `MQTTClient` has functions to send all the standard MQTT messages, except the ACK messages which are dealt with internally. Where it is expected the server will respond with an ACK the function will wait for that ACK before returning. 67 | 68 | ### Publish 69 | 70 | You can send a `PUBLISH` message to a topic as follows 71 | ```swift 72 | try await client.publish( 73 | to: "TopicName", 74 | payload: ByteBuffer(string: "Message Payload"), 75 | qos: .atLeastOnce 76 | ) 77 | ``` 78 | Publish messages can be sent with three different levels of QoS: `.atMostOnce`, `.atLeastOnce` and `.exactlyOnce`. The `publish` function will deal with all three QoS levels and only return when all relevant ACK messages have been dealt with. 79 | 80 | ### Subscribe and Listen 81 | 82 | You can send a `SUBSCRIBE` message to the server and listen for `PUBLISH` messages from the server. A listener will respond to all `PUBLISH` messages sent back from the server. If you have multiple subscriptions you will need to check the topic name to verify you have the correct message. 83 | 84 | ```swift 85 | let subscription = MQTTSubscribeInfo(topicFilter: "MyTopic", qos: .exactlyOnce) 86 | // subscribe returns a SUBACK packet which contains the QoS to be expected for each 87 | // topic subscription 88 | let suback = try await client.subscribe(to: [subscription]) 89 | // By adding a PUBLISH listener you can process all the PUBLISH messages being sent from 90 | // the server to the client 91 | client.addPublishListener("My Listener") { result in 92 | switch result { 93 | case .success(let publish): 94 | if publish.topicName == "MyTopic" { 95 | var buffer = publish.payload 96 | let string = buffer.readString(length: buffer.readableBytes) 97 | print(string) 98 | } 99 | case .failure(let error): 100 | print("Error while receiving PUBLISH event") 101 | } 102 | } 103 | ``` 104 | 105 | ### Version 5 106 | 107 | `MQTTNIO` includes support for version 5 features of the MQTT protocol. To connect using the v5 protocol you need to specify this in your `MQTTClient` configuration. 108 | 109 | ```swift 110 | let client = MQTTClient( 111 | host: "test.mosquitto.org", 112 | port: 1883, 113 | identifier: "My unique identifier", 114 | eventLoopGroupProvider: .createNew, 115 | configuration: .init(version: .v5_0) 116 | ) 117 | ``` 118 | You can use the standard `MQTTClient` APIs with a v5 client but to access v5 features, including MQTT properties and v5 return codes use `MQTTClient.v5`. This includes versions of the `connect`, `publish`, `subscribe`, `unsubscribe` and `disconnect` functions. Below you will see a `publish` call adding the `contentType` property. 119 | 120 | ```swift 121 | let puback = try await client.v5.publish( 122 | to: "JSONTest", 123 | payload: payload, 124 | qos: .atLeastOnce, 125 | properties: [.contentType("application/json")] 126 | ) 127 | ``` 128 | Whoever subscribes to the "JSONTest" topic with a v5.0 client will also receive the `contentType` property along with the payload. 129 | 130 | ## Maturity Justification 131 | 132 | Sandbox. The package has one author. It is fairly new, v1.0.0 was released a year ago. 133 | 134 | ## Alternatives considered 135 | 136 | There are two other major Swift MQTT client implementations [CocoaMQTT](https://github.com/emqx/CocoaMQTT) and [SwiftMQTT](https://github.com/aciidb0mb3r/SwiftMQTT). Neither of these are built on top of Swift NIO and SwiftMQTT only supports v3.1.1 of the MQTT protocol. 137 | -------------------------------------------------------------------------------- /proposals/0020-distributed-actor-cluster.md: -------------------------------------------------------------------------------- 1 | # Distributed Actors Cluster 2 | 3 | * Proposal: [SSWG-0020](0020-distributed-actors-cluster.md) 4 | * Authors: [Konrad ‘ktoso’ Malawski](https://github.com/ktoso) 5 | * Review Manager: [Kaitlin Mahar](https://github.com/kmahar) 6 | * Status: Accepted at Sandbox maturity level 7 | * Implementation: [swift-distributed-actors](https://github.com/apple/swift-distributed-actors) 8 | * Forum Threads: 9 | * [Pitch thread](https://forums.swift.org/t/pitch-swift-distributed-actors-cluster/61061) 10 | * [Review thread](https://forums.swift.org/t/sswg-0020-distributed-actors-cluster/61164) 11 | 12 | ## Package Description 13 | 14 | The swift-distributed-actors package provides a fully functional server-side focused cluster implementation for the `distributed actor` language feature, introduced in Swift 5.7. 15 | 16 | This package contains a feature rich implementation of an actor system cluster, and general framework for building distributed systems in Swift. It integrates with various observability libraries the SSWG endorses and is based on top of swift-nio for its networking stack. 17 | 18 | | | | 19 | |--|--| 20 | | **Package Name** | `swift-distributed-actors` | 21 | | **Module Name** | `DistributedCluster` | 22 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 23 | | **License** | [Apache 2.0](https://github.com/swift-server/swift-backtrace/blob/master/LICENSE.txt) | 24 | | **Dependencies** | [swift-nio](https://github.com/apple/swift-nio), [swift-log](https://github.com/apple/swift-log), [swift-metrics](https://github.com/apple/swift-metrics), [swift-service-discovery](https://github.com/apple/swift-service-discovery), [swift-cluster-membership](https://github.com/apple/swift-cluster-membership) | 25 | 26 | 27 | ## Introduction 28 | 29 | Distributed actors are an extension of the "local only" actor model offered by Swift with its `actor` keyword. 30 | 31 | Distributed actors used the `distributed actor` keywords (after importing the `Distributed` module, provided by Swift), and enable the declaring of `distributed func` methods inside such actor. Such methods may then be invoked remotely, from other peers in a distributed actor system. 32 | 33 | The distributed actor *language feature* does not include any specific *runtime*, and only defines the language and semantic rules surrounding distributed actors. This library provides a feature-rich clustering server-side focused implementation of such runtime (i.e. a `DistributedActorSystem` implementation) for distributed actors. 34 | 35 | Clusters are a fundamental building block of server-side distributed systems, such as lobby systems for game backends, IoT service backends, orchestration systems and general control plane systems of distributed databases or other distributed systems. 36 | 37 | ## Motivation 38 | 39 | Distributed actors are introduced as nominal types in Swift 5.7. Similar to actors, they can be declared using the `distributed actor` pair of keywords. By themselves, they cannot really do anything – all the distributed actions such actor performs are actually handled by an `ActorSystem` associated with given actor type. 40 | 41 | Specifically, an actor must declare what type of actor system it is going to be used with, like this: 42 | 43 | ```swift 44 | import Distributed 45 | import DistributedCluster 46 | 47 | distributed actor Greeter { 48 | typealias ActorSystem = ClusterSystem 49 | 50 | distributed func hello(name: String) -> String { 51 | return "Hello \(name)!" 52 | } 53 | } 54 | ``` 55 | 56 | Such `Greeter` declaration can then be used in the context of the clustered distributed actor system. It is also possible to declare a module-wide default distributed actor system type, for more information refer to [Swift Distributed Actor Runtime](https://github.com/apple/swift-evolution/blob/main/proposals/0344-distributed-actor-runtime.md) and [Swift Distributed Actor Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md). It is also possible to declare a module-wide default distributed actor system, which is how most users of distributed actors are likely to use this feature, like this: 57 | 58 | ```swift 59 | typealias DefaultDistributedActorSystem = ClusterSystem 60 | ``` 61 | 62 | Which avoids having to repeat the `ActorSystem` typealias in every distributed actor in a module. 63 | 64 | The package being proposed here provides the implementation of the `ClusterSystem`. 65 | 66 | ## Proposed solution 67 | 68 | The `DistributedCluster` package includes the `ClusterSystem` type which is the central piece of the library. Once created, it binds to a host/port pair and begins listening for incoming connections: 69 | 70 | ```swift 71 | @main 72 | struct Main { 73 | static func main() async throws { 74 | let system = await ClusterSystem("FirstSystem") { settings in 75 | settings.endpoint.host = "127.0.0.1" 76 | settings.endpoint.port = 7337 77 | } 78 | 79 | try await system.terminated 80 | } 81 | } 82 | ``` 83 | 84 | Clusters are formed by “joining” other nodes. So the above created node has bound on port 7337, and if we had another node running on the same host but on port 8228 we could join in programmatically like this: 85 | 86 | ```swift 87 | let other = Cluster.Endpoint(host: "127.0.0.1", port: 8228) 88 | system.cluster.join(endpoint: other) 89 | ``` 90 | 91 | The `join(endpoint:)` method returns immediately, however it is possible to use `try await joined(endpoint:within:)` to suspend until the other node has been joined (or we failed doing so within the timeout). 92 | 93 | Alternatively we can configure an actor system to automatically discover and join nodes by configuring a [`swift-service-discovery` node-based discovery mechanism](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/clustering#Automatic-Node-Discovery), like this: 94 | 95 | ```swift 96 | import ServiceDiscovery 97 | import K8sServiceDiscovery // See: [tuplestream/swift-k8s-service-discovery](https://github.com/tuplestream/swift-k8s-service-discovery) 98 | import DistributedCluster 99 | 100 | ClusterSystem("DiscoverNodes") { settings in 101 | let discovery = K8sServiceDiscovery() 102 | let target = K8sObject(labelSelector: ["name": "actor-cluster"], namespace: "actor-cluster") 103 | 104 | settings.discovery = ServiceDiscoverySettings(discovery, service: target) 105 | } 106 | ``` 107 | 108 | Once a cluster has been formed, distributed actors use the [Receptionist](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/receptionist) to discover check-in and discover distributed actors from other nodes. For example, we can write a distributed worker actor like this: 109 | 110 | ```swift 111 | distributed actor Worker { 112 | typealias ActorSystem = ClusterSystem 113 | 114 | init(actorSystem: ActorSystem) async { 115 | self.actorSystem = actorSystem 116 | await actorSystem.receptionist.checkIn(worker, with: .workers) 117 | } 118 | 119 | distributed func work() -> String { "Completed some work!" } 120 | } 121 | 122 | extension DistributedReception.Key { 123 | static var workers: DistributedReception.Key<Worker> { 124 | "workers" 125 | } 126 | } 127 | ``` 128 | 129 | Such distributed actors are then created using normal initialization on nodes within the cluster. Each such node can then use the [receptionist listing](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/receptionist#Receptionist-Listings) API to obtain all workers from all other nodes, like this: 130 | 131 | ```swift 132 | for await worker in await actorSystem.receptionist.listing(of: .workers) { 133 | log.info("Found worker \(worker) on node \(worker.id.node)") 134 | let reply = try await worker.work() 135 | log.info("Worker \(worker.id) returned: \(reply)") 136 | } 137 | ``` 138 | 139 | While this allows nodes to discover distributed actors on other processes, it does not deal with their termination. For example, if a remote node crashes, remote calls to any actor located on this node will fail. In order to notice such crash, or just plain actor termination (i.e. the remote `worker` having deinitialized) one can use the [`LifecycleWatch`](https://apple.github.io/swift-distributed-actors/1.0.0-beta.2/documentation/distributedcluster/lifecycle) functionality of the cluster: 140 | 141 | ```swift 142 | distributed actor Boss: LifecycleWatch { 143 | func findWorkers() { 144 | for await worker in await actorSystem.receptionist.listing(of: .workers) { 145 | watchTermination(of: worker) 146 | } 147 | } 148 | 149 | // Invoked whenever a watched actor terminates 150 | func terminated(actor id: ActorID) async { 151 | print("Oh no! Worker \(id) has terminated!") 152 | } 153 | } 154 | ``` 155 | 156 | The mechanisms underlying lifecycle watch are powered by the advanced distributed failure detection mechanisms implemented in [swift-cluster-membership](https://github.com/apple/swift-cluster-membership), and allow for reliable failure detection in a distributed system. The same mechanism also works if the workers were local actors, and just happened to deinitialize. A remote actor that deinitializes also signals to all of its watchers that it has terminated. This way, applications can program against actor “lifecycle” rather than having to worry about where and how a distributed actor was running. 157 | 158 | This concludes a quick overview of the primary features of the cluster. The cluster also offers low-level [cluster events](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/cluster/event), as well as high level abstractions such as “[Cluster Singletons](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/clustersingleton)“ which are explored in depth in the library’s [reference documentation](https://apple.github.io/swift-distributed-actors/1.0.0-beta.3/documentation/distributedcluster/introduction). 159 | 160 | ## Maturity Justification 161 | 162 | As the package is currently still on its way towards its 1.0 release, we propose to include it in **Sandbox** level. At this point in time the package does not guarantee source stability, but we are very close to doing so along with the 1.0 release of the package. 163 | 164 | 165 | ## Alternatives considered 166 | 167 | None. 168 | 169 | There is no alternative feature complete clustering solution available in Swift today. The inclusion of this library does not preclude future inclusion of other distributed actor system implementations, if and when they come up. 170 | 171 | 172 | ## Discussion 173 | 174 | We are considering the naming of the package still. The focus of this package is to serve as a reference implementation of an actor system, but it also is a server-side cluster focused solution, thus we may still consider renaming the package to be more aligned with the primary purpose of this package rather than a more generic name which leaves more room for alternative actor system implementations to be offered from within the same SwiftPM package. 175 | -------------------------------------------------------------------------------- /proposals/0021-swift-cassandra-client.md: -------------------------------------------------------------------------------- 1 | # Swift Cassandra Client 2 | 3 | * Proposal: [SSWG-0021](0021-swift-cassandra-client.md) 4 | * Authors: [Yim Lee](https://github.com/yim-lee) 5 | * Review Manager: [Konrad 'ktoso' Malawski](https://github.com/ktoso) 6 | * Status: **Accepted** 7 | * Implementation: [swift-cassandra-client](https://github.com/apple/swift-cassandra-client) 8 | * Forum Threads: 9 | * [Pitch thread](https://forums.swift.org/t/pitch-swift-cassandra-client/62509) 10 | 11 | ## Package Description 12 | 13 | The Swift Cassandra Client package is a Cassandra database client based on [DataStax Cassandra C++ Driver](https://github.com/datastax/cpp-driver), wrapping it with Swift-friendly APIs and data structures. 14 | 15 | It offers API based on [SwiftNIO](https://github.com/apple/swift-nio) futures, as well as Swift Concurrency based API in Swift 5.5 and newer. 16 | 17 | | | | 18 | |--|--| 19 | | **Package Name** | `swift-cassandra-client` | 20 | | **Module Name** | `CassandraClient` | 21 | | **Proposed Maturity Level** | [Incubating](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 22 | | **License** | [Apache 2.0](https://github.com/swift-server/swift-cassandra-client/blob/master/LICENSE.txt) | 23 | | **Dependencies** | [swift-nio](https://github.com/apple/swift-nio), [swift-log](https://github.com/apple/swift-log) | 24 | 25 | 26 | 27 | ## Motivation 28 | 29 | Apple is an active contributor to the Apache Cassandra open source project and server teams at Apple use Cassandra to support various use-cases. The Swift Cassandra Client has been used in that capacity for several years and we believe the broader Swift community would find it useful as well. 30 | 31 | ## Usage 32 | 33 | ### Swift concurrency based API 34 | 35 | #### Creating a client instance 36 | 37 | ```swift 38 | var configuration = CassandraClient.Configuration(...) 39 | let cassandraClient = CassandraClient(configuration: configuration) 40 | ``` 41 | 42 | The client has a default session established (lazily) so that it can be used directly to perform 43 | queries on the configured keyspace: 44 | 45 | ```swift 46 | let result = try await cassandraClient.query(...) 47 | ``` 48 | 49 | The client must be explicitly shut down when no longer needed: 50 | 51 | ```swift 52 | try cassandraClient.shutdown() 53 | ``` 54 | 55 | #### Creating a session for a different keyspace 56 | 57 | ```swift 58 | let session = cassandraClient.makeSession(keyspace: <KEYSPACE>) 59 | let result = try await session.query(...) 60 | ``` 61 | 62 | The session must be explicitly shut down when no longer needed: 63 | 64 | ```swift 65 | try session.shutdown() 66 | ``` 67 | 68 | You can also create a session and pass in a closure, which will automatically release the resource when the closure exits: 69 | 70 | ```swift 71 | try await cassandraClient.withSession(keyspace: <KEYSPACE>) { session in 72 | ... 73 | } 74 | ``` 75 | 76 | #### Running result-less commands (e.g. insert, update, delete or DDL) 77 | 78 | ```swift 79 | try await cassandraClient.run("create table ...") 80 | ``` 81 | 82 | Or at session level: 83 | 84 | ```swift 85 | try await session.run("create table ...") 86 | ``` 87 | 88 | #### Running queries returning small datasets that fit in memory 89 | 90 | Returning a model object, having `Model: Codable`: 91 | 92 | ```swift 93 | let result: [Model] = try await cassandraClient.query("select * from table ...") 94 | ``` 95 | 96 | ```swift 97 | let result: [Model] = try await session.query("select * from table ...") 98 | ``` 99 | 100 | Or using free-form transformations on the row: 101 | 102 | ```swift 103 | let values = try await cassandraClient.query("select * from table ...") { row in 104 | row.column(<COLUMN_NAME>).int32 105 | } 106 | ``` 107 | 108 | ```swift 109 | let values = try await session.query("select * from table ...") { row in 110 | row.column(<COLUMN_NAME>).int32 111 | } 112 | ``` 113 | 114 | #### Running queries returning large datasets that do not fit in memory 115 | 116 | ```swift 117 | // `rows` is a sequence that one needs to iterate on 118 | let rows: Rows = try await cassandraClient.query("select * from table ...") 119 | ``` 120 | 121 | ```swift 122 | // `rows` is a sequence that one needs to iterate on 123 | let rows: Rows = try await session.query("select * from table ...") 124 | ``` 125 | 126 | ### SwiftNIO future based API 127 | 128 | #### Creating a client instance 129 | 130 | ```swift 131 | var configuration = CassandraClient.Configuration(...) 132 | let cassandraClient = CassandraClient(configuration: configuration) 133 | ``` 134 | 135 | The client has a default session established (lazily) so that it can be used directly to perform 136 | queries on the configured keyspace: 137 | 138 | ```swift 139 | let resultFuture = cassandraClient.query(...) 140 | ``` 141 | 142 | The client must be explicitly shut down when no longer needed: 143 | 144 | ```swift 145 | try cassandraClient.shutdown() 146 | ``` 147 | 148 | #### Creating a session for a different keyspace 149 | 150 | ```swift 151 | let session = cassandraClient.makeSession(keyspace: <KEYSPACE>) 152 | let resultFuture = session.query(...) 153 | ``` 154 | 155 | The session must be explicitly shut down when no longer needed: 156 | 157 | ```swift 158 | try session.shutdown() 159 | ``` 160 | 161 | You can also create a session and pass in a closure, which will automatically release the resource when the closure exits: 162 | 163 | ```swift 164 | try cassandraClient.withSession(keyspace: <KEYSPACE>) { session in 165 | ... 166 | } 167 | ``` 168 | 169 | #### Running result-less commands (e.g. insert, update, delete or DDL) 170 | 171 | ```swift 172 | let voidFuture = cassandraClient.run("create table ...") 173 | ``` 174 | 175 | Or at session level: 176 | 177 | ```swift 178 | let voidFuture = session.run("create table ...") 179 | ``` 180 | 181 | #### Running queries returning small datasets that fit in memory 182 | 183 | Returning a model object, having `Model: Codable`: 184 | 185 | ```swift 186 | cassandraClient.query("select * from table ...").map { (result: [Model]) in 187 | ... 188 | } 189 | ``` 190 | 191 | ```swift 192 | session.query("select * from table ...").map { (result: [Model]) in 193 | ... 194 | } 195 | ``` 196 | 197 | Or using free-form transformations on the row: 198 | 199 | ```swift 200 | cassandraClient.query("select * from table ...") { row in 201 | row.column(<COLUMN_NAME>).int32 202 | }.map { value in 203 | ... 204 | } 205 | ``` 206 | 207 | ```swift 208 | session.query("select * from table ...") { row in 209 | row.column(<COLUMN_NAME>).int32 210 | }.map { value in 211 | ... 212 | } 213 | ``` 214 | 215 | #### Running queries returning large datasets that do not fit in memory 216 | 217 | ```swift 218 | cassandraClient.query("select * from table ...").map { (rows: Rows) in 219 | // `rows` is a sequence that one needs to iterate on 220 | rows.map { row in 221 | ... 222 | } 223 | } 224 | ``` 225 | 226 | ```swift 227 | session.query("select * from table ...").map { (rows: Rows) in 228 | // `rows` is a sequence that one needs to iterate on 229 | rows.map { row in 230 | ... 231 | } 232 | } 233 | ``` 234 | 235 | ## Maturity justification 236 | 237 | As the library has been in use at Apple for several years, and we would like more time and exposure to collect user feedback to move towards its 1.0 release, we propose to include Swift Cassandra Client in **Incubating** level. 238 | -------------------------------------------------------------------------------- /proposals/0022-sqlite-nio.md: -------------------------------------------------------------------------------- 1 | # SQLiteNIO: An Swift-native driver for SQLite 2 | 3 | * Proposal: [SSWG-0022](https://github.com/swift-server/sswg/blob/main/proposals/0022-sqlite-nio.md) 4 | * Author(s): [Gwynne Raskind](https://github.com/gwynne) 5 | * Review Manager: [Konrad 'ktoso' Malawski](https://github.com/ktoso) 6 | * Status: **Accepted (Sandbox)** 7 | * Implementation: [vapor/sqlite-nio](https://github.com/vapor/sqlite-nio) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/sqlitenio/46664/25), Discussion (Pending) 9 | 10 | ## Package Description 11 | 12 | `SQLiteNIO` is a client package which provides an asynchronous Swift API for SQLite based on NIO's `EventLoopFuture`. 13 | 14 | ||| 15 | |--:|:--| 16 | | **Package Name** | sqlite-nio | 17 | | **Module Name** | SQLiteNIO | 18 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 19 | | **License** | [MIT](https://choosealicense.com/licenses/mit/) | 20 | | **Dependencies** | [swift-nio](https://github.com/apple/swift-nio), [swift-log](https://github.com/apple/swift-log) | 21 | 22 | ## Motiviation 23 | 24 | SQLite is a C API which does not import into Swift in an easily managed form, especially in terms of marshalling data types and correctly handling the underlying API's multithreading requirements. However, SQLite - unlike most relational databases - is a complete database management library unto itself with no separate client or server components, reimplementing it in pure Swift would be, at best, a massive undertaking (and of questionable wisdom as well, especially considering the almost unheard-of _extremely_ high quality of SQLite's source code). As such, SQLiteNIO concerns itself solely with providing a more Swift-friendly, asynchronous interface to the C implementation. (Currently SQLiteNIO only supports `EventLoopFuture`-style asynchronous usage; providing a Concurrency-based alternative is planned in the near future.) 25 | 26 | The underlying SQLite implementation is an embedded version of `libsqlite`, specifically the `sqlite3.c` amalgamation, built as a SwiftPM target and statically linked, with configuration flags chosen appropriately on a per-platform basis and vendor prefixes added to all APIs to avoid any collisions. The embedded source code is kept up to date with upstream releases (this is currently done by manually invoking a script when new releases are made; full automation is planned). 27 | 28 | ## Usage 29 | 30 | This section goes into detail on a few distinct types from this module to give an idea of how they work together and what using the package looks like. 31 | 32 | ### SQLiteConnection 33 | 34 | The base connection type, `SQLiteConnection`, provides the basic interface to an SQLite database stored on disk or in memory. 35 | 36 | ```swift 37 | import SQLiteNIO 38 | 39 | // create a new event loop group and thread pool 40 | let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) 41 | let pool = NIOThreadPool(numberOfThreads: 1) 42 | pool.start() 43 | defer { 44 | try! pool.syncShutdownGracefully() 45 | try! elg.syncShutdownGracefully() 46 | } 47 | 48 | // create a new connection to an in-memory database 49 | let conn = try await SQLiteConnection.open( 50 | storage: .memory, 51 | threadPool: pool, 52 | on: elg.any() 53 | ).get() 54 | 55 | // ready to query 56 | print(conn) // SQLiteConnection 57 | ``` 58 | 59 | #### Closing 60 | 61 | A connection _must_ be closed before it deinitializes. `SQLiteConnection` ensures this by asserting that it has been closed in its `deinit` method. This is meant to help developers implement proper graceful shutdown early and avoid leaking memory or sockets. 62 | 63 | ### Query 64 | 65 | Assuming we have an active `SQLiteConnection`, we can query the active database using SQLite's query command: 66 | 67 | ```swift 68 | import SQiteLNIO 69 | 70 | let conn = try await SQLiteConnection.open(...).get() 71 | defer { try! conn.close().wait() } 72 | 73 | // select the current version 74 | let rows = try await conn.query("SELECT sqlite_version() AS version").get() 75 | print(rows) // [SQLiteRow] 76 | 77 | // fetch the version column from the first row by attempting to 78 | // read it as a Swift string 79 | let version = rows.first?.column("version")?.string 80 | print(version) // Optional(3.39.5) 81 | ``` 82 | 83 | It is also possible to perform parameterized queries with an active `SQLiteConnection` by providing input parameter bindings. 84 | 85 | Input parameters are passed as an array of `SQLiteData` objects along with the SQL string. In the query string, input parameters are referenced by one of the supported placeholder markers, such as `?` ("next" bound parameter) or `?N` (Nth bound parameter). Named parameter bindings are not yet supported. 86 | 87 | ```swift 88 | import SQLiteNIO 89 | 90 | let conn = try await SQLiteConnection.open(...).get() 91 | defer { try! conn.close().wait() } 92 | 93 | // selects all planets where name is equal to the first bound parameter 94 | let rows = try await conn.query("SELECT * FROM planets WHERE name=?1", [.text("Earth")]).get() 95 | 96 | // fetch the "name" column from the first row as a string 97 | let foo = rows.first?.column("name")?.string 98 | print(foo) // Optional("Earth") 99 | ``` 100 | 101 | ### SQLiteData 102 | 103 | `SQLiteData` represents data both going to and coming from SQLite. Data is represented by one of the four "basic" data types supported by SQLite: integer, float, text, or blob. The "typeless" `null` value is also represented. 104 | 105 | ### SQLiteRow 106 | 107 | The `query()` method returns an array of `SQLiteRow`s. Each row can be thought of as a dictionary with column names as the key and data as the value. While the actual storage implementation is private, `SQLiteRow` provides a `columms` property for accessing all columns in a row as an array, and the `column(_:)` method for accessing an individual column's data by name: 108 | 109 | ```swift 110 | struct SQLiteColumn { 111 | let name: String 112 | let data: SQLiteData 113 | } 114 | 115 | struct SQLiteRow { 116 | var columns: [SQLiteColumn] 117 | func column(_ name: String) -> SQLiteData? 118 | } 119 | ``` 120 | 121 | If no column with the given name exists in the row, `column(_:)` returns `nil`. 122 | 123 | ### SQLiteError 124 | 125 | The `SQLiteError` type provides a convenient interface to the various "well-known" SQLite result code constants. 126 | 127 | ```swift 128 | enum SQLiteError: Error { 129 | let reason: Reason 130 | let message: String 131 | 132 | enum Reason { 133 | case error // SQLITE_ERROR 134 | case intern // SQLITE_INTERNAL 135 | case permission // SQLITE_PERM 136 | // etc. 137 | } 138 | } 139 | ``` 140 | 141 | ### Other features 142 | 143 | SQLiteNIO also provides APIs for working with prepared statements (`SQLiteStatement`) and registering custom scalar and aggregate functions (`SQLiteCustomFunction`) 144 | 145 | ### How to use 146 | 147 | To try this package out at home, add the following dependency to your `Package.swift` file: 148 | 149 | ```swift 150 | .package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.5.0"), 151 | ``` 152 | 153 | Then add `.product(name: "SQLiteNIO", package: "sqlite-nio")` to your module target's dependencies array. 154 | 155 | ### Seeking Feedback 156 | 157 | * If anything, what does this proposal *not cover* that you will definitely need? 158 | * If anything, what could we remove from this and still be happy? 159 | * API-wise: what do you like, what don't you like? 160 | 161 | Feel free to post feedback as response to this post and/or GitHub issues on [vapor/sqlite-nio](https://github.com/vapor/sqlite-nio). 162 | -------------------------------------------------------------------------------- /proposals/0024-service-context.md: -------------------------------------------------------------------------------- 1 | # Swift Service Context 2 | 3 | * Proposal: [SSWG-0024](0024-service-context.md) 4 | * Authors: [Konrad 'ktoso' Malawski](https://github.com/ktoso) / [Moritz Lang](https://github.com/slashmo) 5 | * Review Manager: Adam Fowler 6 | * Status: **Accepted (Incubating)** 7 | * Implementation: 8 | * [swift-service-context](https://github.com/apple/swift-service-context) 9 | * Forum Threads: 10 | * [Pitch](https://forums.swift.org/t/pitch-swift-service-context/66687) 11 | * [Proposal](https://forums.swift.org/t/sswg-0024-service-context/67121) 12 | 13 | ## Package Description 14 | 15 | ServiceContext is a minimal (zero-dependency) context propagation container, intended to "carry" items for purposes of cross-cutting tools to be built on top of it. 16 | 17 | It is modeled after the concepts explained in W3C Baggage and in the spirit of Tracing Plane's "Baggage Context" type, although by itself, it does not define a specific serialization format. 18 | 19 | See https://github.com/apple/swift-distributed-tracing for actual instrument types and implementations, which can be used to deploy various cross-cutting instruments, all reusing the same service context type. 20 | Refer to distributed tracing [documentation](https://swiftpackageindex.com/apple/swift-distributed-tracing/main/documentation/tracing) to learn more about how it is used in practice. 21 | 22 | | | | 23 | |--|--| 24 | | **Package Name** | `swift-service-context` | 25 | | **Module Name** | `ServiceContextModule` | 26 | | **Proposed Maturity Level** | [Incubating](https://www.swift.org/sswg/incubation-process.html#process-diagram) | 27 | | **License** | [Apache v2](https://github.com/apple/swift-distributed-tracing/blob/main/LICENSE.txt) | 28 | | **Dependencies** | - | 29 | 30 | ## Introduction 31 | 32 | Service context is a type-safe dictionary that is keyed using types and intended to be used for contextual information propagation across asynchronous code. 33 | 34 | It declares a well-known Task Local key for the service context, and patterns how to add keys. 35 | 36 | It is a core building block of [swift-distributed-tracing](https://github.com/apple/swift-distributed-tracing) which uses it to propagate trace information across swift concurrency tasks, though it can be used independently as well. 37 | 38 | ## Overview 39 | 40 | Service context's API surface is relatively small and is all based around the service context type which is a type-safe dictionary. 41 | 42 | One can create a service context using the `.topLevel` factory method which intends to clarify that this should not be used UNLESS intending to start a fresh "top level" context that does not inherit any values from the current context: 43 | 44 | ```swift 45 | var context = ServiceContext.topLevel 46 | ``` 47 | 48 | In most situations, developers should prefer to "pick up" the context from the current context: 49 | 50 | ```swift 51 | var context: ServiceContext? = ServiceContext.current 52 | ``` 53 | 54 | which inspects the task-local values for the presence of a service context. It is by design that it is possible to determine if no context was set, or if an empty context is set. When intending to "carry existing, or create a new" context, the following pattern is used: 55 | 56 | ```swift 57 | var context = ServiceContext.current ?? ServiceContext.topLevel 58 | ``` 59 | 60 | Once obtained, one can set values in by using defined keys, like this: 61 | 62 | ```swift 63 | /// Keys should be defined private and only exposed though accessors, as seen below. 64 | private enum FirstTestKey: ServiceContextKey { 65 | typealias Value = Int 66 | } 67 | 68 | /// Define convenience accessors, in order to keep the key type private. 69 | extension ServiceContext { 70 | public var firstTest: String? { 71 | set { 72 | self[FirstTestKey.self] = newValue 73 | } 74 | get { 75 | self[FirstTestKey.self] 76 | } 77 | } 78 | } 79 | 80 | var context = ServiceContext.topLevel 81 | context.firstTest = 42 82 | ``` 83 | 84 | Keys declared as types conforming to `ServiceContextKey` allow for future extension where we might want to configure specific keys using additional behavior, like for example defining the `static let nameOverride` of a key. It is also important that a Key type may be private to whomever declares it, allowing only such library to set these values. 85 | 86 | Service context is used primarily as a task-local value read and modified like this: 87 | 88 | ```swift 89 | func exampleFunction() async -> String { 90 | guard let context = ServiceContext.current { 91 | return "no-service-context" 92 | } 93 | guard let value = context.firstTest { 94 | return "no test value" 95 | } 96 | print("test value = \(value)") // test value = test-value 97 | return value 98 | } 99 | 100 | // ---------------------------------------- 101 | 102 | var context = ServiceContext.topLevel 103 | context.firstTest = "test-value" 104 | 105 | let ok = ServiceContext.withValue(context) { 106 | await exampleFunction() 107 | assert(ServiceContext.current?.firstTest == Optional("test-value")) 108 | return "ok" // withValue can return values 109 | } 110 | assert(ServiceContext.current?.firstTest == Optional.none) // value is not set outside withValue block 111 | assert(c == "ok") 112 | ``` 113 | 114 | Swift's [task local values](https://developer.apple.com/documentation/swift/tasklocal) are used to automatically propagate the context value to any child tasks that may be created from this code, such that context is automatically propagated through to them, e.g. like this: 115 | 116 | ```swift 117 | func testMeMore() -> String { 118 | guard let context = ServiceContext.current { 119 | return "no-service-context" 120 | } 121 | guard let value = context.firstTest { 122 | return "no test value" 123 | } 124 | return value 125 | } 126 | 127 | func test() async -> String { 128 | async let v = testMeMore() // read context from child task 129 | return await v 130 | } 131 | 132 | var context = ServiceContext.topLevel 133 | context.firstTest = "test-value" 134 | ServiceContext.withValue(context) { 135 | assert(test() == "test-value") 136 | } 137 | ``` 138 | 139 | It is recommended to follow this pattern to declare an accessor on the context, rather than exposing the `Key` type directly. This also allows to add validation and change the Key type used to persist the value in the future, in addition to being also a nicer user-experience for developers. 140 | 141 | ```swift 142 | extension ServiceContext { 143 | public var testValue: String? { 144 | set { 145 | self[TestKey.self] = newValue 146 | } 147 | get { 148 | self[TestKey.self] 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | ## Maturity Justification 155 | 156 | We are proposing this package at the "Incubation" level of maturity. We believe this package to be an important building block of the server ecosystem, in the same way [swift-log](https://github.com/apple/swift-log) and [swift-metrics](https://github.com/apple/swift-metrics) are adopted across many server and client libraries. 157 | 158 | The project has matured for over 3 years, has multiple active maintainers and fulfills adoption requirements in production. 159 | 160 | Minimum Requirements: 161 | 162 | * General 163 | * Has relevance to Swift on Server specifically: **Yes, and is planned to be adopted by core server and client libraries** 164 | * Publicly accessible source managed by an SCM such as github.com or similar: **Satisfied: Repository is stored on github.com** 165 | * Prefer to use `main` as the default branch name, in line with [Swift's guidelines](https://forums.swift.org/t/moving-default-branch-to-main/38515): **Satisfied** 166 | * Adopt the [Swift Code of Conduct](https://swift.org/community/#code-of-conduct): **Satisfied** 167 | * Ecosystem 168 | * Uses SwiftPM: **Satisfied** 169 | * Integrated with critical SSWG ecosystem building blocks, e.g., Logging and Metrics APIs, SwiftNIO for IO: **It is such a building block.** 170 | * Longevity 171 | * Must be from a team that has more than one public repository (or similar indication of experience): **Satisfied: hosted under Apple organization on GitHub; Active maintainers are @ktoso and @slashmo** 172 | * SSWG should have access / authorization to graduated repositories in case of emergency: **Satisfied (Konrad, Franz, Tomer)** 173 | * Adopt the [SSWG Security Best Practices](../security/README.md)): **Pending** 174 | * Testing, CI and Release 175 | * Have unit tests for Linux: **Satisfied** 176 | * CI setup, including testing PRs and the main branch: **Satisfied** 177 | * Follow semantic versioning, with at least one published pre-release (e.g. 0.1.0, 1.0.0-beta.1) or release (e.g. 1.0.0): **Satisfied** 178 | * Licensing 179 | * Apache 2, MIT, or BSD (Apache 2 recommended): **Satisfied: Apache 2** 180 | * Conventions and Style 181 | * Adopt [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/): **Satisfied** 182 | * Follow [SSWG Technical Best Practices](https://www.swift.org/sswg/incubation-process.html#technical-best-practices) when applicable: **Satisfied** 183 | * Prefer to adopt code formatting tools and integrate them into the CI: **Satisfied** 184 | Incubating Requirements: 185 | * Document that it is being used successfully in production by at least two independent end users which, in the SSWG judgment, are of adequate quality and scope. 186 | * **We are aware of 3+ production use-cases in large deployments using [swift-distributed-tracing](https://github.com/apple/swift-distributed-tracing).** 187 | * Must have 2+ maintainers and/or committers. 188 | * **Actively maintained by Moritz ([@slashmo](https://github.com/slashmo)) and Konrad ([@ktoso](https://github.com/ktoso))** 189 | * Packages must have more than one person with admin access. This is to avoid losing access to any packages. For packages hosted on GitHub and GitLab, the packages must live in an organization with at least two administrators. If you don't want to create an organization for the package, you can host them in the [Swift Server Community](https://github.com/swift-server-community) organization. 190 | * **SSWG members Franz, Konrad have admin rights, and the project is under the Apple organization which has multiple admins.** 191 | * Demonstrate an ongoing flow of commits and merged contributions, or issues addressed in timely manner, or similar indication of activity. 192 | * **Active development for years, see changelog. Project expected to have less changes now that a 1.0 has been announced.** 193 | 194 | ## Alternatives considered 195 | 196 | Not building a tracing API was considered but we see this as a fundamental building block for the server ecosystem, in the same style as swift-log and swift-metrics. 197 | -------------------------------------------------------------------------------- /proposals/0026-swift-openapi-generator.md: -------------------------------------------------------------------------------- 1 | # Swift OpenAPI Generator 2 | 3 | * Proposal: [SSWG-0026](0026-swift-openapi-generator.md) 4 | * Authors: [Honza Dvorsky](https://github.com/czechboy0) 5 | * Review Manager: [Joannis Orlandos](https://github.com/joannis) 6 | * Status: **Implemented** 7 | * Implementation: [apple/swift-openapi-generator](https://github.com/apple/swift-openapi-generator) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/pitch-sswg-incubation-swift-openapi-generator/66584), [Discussion](https://forums.swift.org/), [Review](https://forums.swift.org/) 9 | 10 | ## Package Description 11 | 12 | [OpenAPI](https://openapis.org/) is an open specification for documenting HTTP APIs. 13 | 14 | Swift OpenAPI Generator is a Swift package plugin that can generate the ceremony code required to make API calls, or implement API servers. 15 | 16 | | | | 17 | |--|--| 18 | | **Package Name** | [`swift-openapi-generator`](https://github.com/apple/swift-openapi-generator) | 19 | | **Module Name** | `OpenAPIGenerator` (build plugin), `swift-openapi-generator` (CLI) | 20 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 21 | | **License** | [Apache-2.0](https://github.com/apple/swift-openapi-generator/blob/main/LICENSE.txt) | 22 | | **Dependencies** | [OpenAPIKit](https://github.com/mattpolzin/OpenAPIKit.git) (v3.0.0-alpha.9), [Yams](https://github.com/jpsim/Yams.git) (v4, v5), [swift-argument-parser](https://github.com/apple/swift-argument-parser.git) (v1), [swift-algorithms](https://github.com/apple/swift-algorithms) (v1), [swift-syntax](https://github.com/apple/swift-syntax.git) (v508.0.1+), [swift-format](https://github.com/apple/swift-format.git) (v508.0.1+), [swift-docc-plugin](https://github.com/apple/swift-docc-plugin) (v1), [swift-openapi-runtime](https://github.com/apple/swift-openapi-runtime) (v0.1.0+) | 23 | 24 | Code generated by Swift OpenAPI Generator uses common abstractions from a runtime library, called `swift-openapi-runtime`: 25 | 26 | | | | 27 | |--|--| 28 | | **Package Name** | [`swift-openapi-runtime`](https://github.com/apple/swift-openapi-runtime) | 29 | | **Module Name** | `OpenAPIRuntime` | 30 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 31 | | **License** | [Apache-2.0](https://github.com/apple/swift-openapi-runtime/blob/main/LICENSE.txt) | 32 | | **Dependencies** | [swift-docc-plugin](https://github.com/apple/swift-docc-plugin) (v1) | 33 | 34 | And since Swift OpenAPI Generator decouples the concrete HTTP library from the generated code, and uses the "API package" approach that swift-log, swift-metrics, and swift-distributed-tracing follow, we are also proposing to include two concrete transport implementations maintained by the authors of Swift OpenAPI Generator. 35 | 36 | | | | 37 | |--|--| 38 | | **Package Name** | [`swift-openapi-urlsession`](https://github.com/apple/swift-openapi-urlsession) | 39 | | **Module Name** | `OpenAPIURLSession` | 40 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 41 | | **License** | [Apache-2.0](https://github.com/apple/swift-openapi-urlsession/blob/main/LICENSE.txt) | 42 | | **Dependencies** | [swift-docc-plugin](https://github.com/apple/swift-docc-plugin) (v1), [swift-openapi-runtime](https://github.com/apple/swift-openapi-runtime) (v0.1.0+) | 43 | 44 | | | | 45 | |--|--| 46 | | **Package Name** | [`swift-openapi-async-http-client`](https://github.com/swift-server/swift-openapi-async-http-client) | 47 | | **Module Name** | `OpenAPIAsyncHTTPClient` | 48 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 49 | | **License** | [Apache-2.0](https://github.com/swift-server/swift-openapi-async-http-client/blob/main/LICENSE.txt) | 50 | | **Dependencies** | [swift-docc-plugin](https://github.com/apple/swift-docc-plugin) (v1), [swift-openapi-runtime](https://github.com/apple/swift-openapi-runtime) (v0.1.0+), [async-http-client](https://github.com/swift-server/async-http-client) (v1), [swift-nio](https://github.com/apple/swift-nio) (v2) | 51 | 52 | We expect authors of other transports to also pitch their transport implementations for SSWG incubation. 53 | 54 | ## Introduction 55 | 56 | [OpenAPI](https://www.openapis.org/) is a specification for documenting HTTP services. An OpenAPI document is written in either YAML or JSON, and can be read by tools to help automate workflows, such as generating the necessary code to send and receive HTTP requests. 57 | 58 | [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator) is a SwiftPM plugin that takes an OpenAPI document and generates either the client code to perform HTTP calls or the server code to handle those calls. The generated code translates between a type‑safe representation of each operation’s input and output, and the underlying HTTP request and response. 59 | 60 | Swift OpenAPI Generator is not tied to a specific client or server HTTP library; it allows plugging in custom libraries that provide the bridging code between the abstract representation defined in our runtime library, and the specific HTTP library. To learn more, check out the [documentation](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator). 61 | 62 | Swift OpenAPI Generator was open sourced in May 2023 in a pre-1.0 state and continues development fully in the open. It was announced in a [blog post](https://www.swift.org/blog/introducing-swift-openapi-generator/) on Swift.org and presented in [Meet Swift OpenAPI Generator](https://developer.apple.com/wwdc23/10171) at WWDC 2023. 63 | 64 | ## Motivation 65 | 66 | OpenAPI is a widely adopted specification of describing HTTP services, and the Swift ecosystem should have a fully supported set of tools for generating client and server code from it. On the client side, it allows app developers to quickly bootstrap a Swift client to talk to any arbitrary service, and on the server side, it allows more Swift developers to write servers, as the glue code between the underlying server and the actual business logic is generated for them. 67 | 68 | The Swift server ecosystem already solved this problem for gRPC with [grpc-swift](https://github.com/grpc/grpc-swift), but any credible server language also requires support of HTTP REST services, which OpenAPI provides. 69 | 70 | One piece of evidence that such a tool was missing in the ecosystem was the not-so-uncommon pattern of using [grpc-swift](https://github.com/grpc/grpc-swift), but instead of vending its gRPC API to users, putting an HTTP-gRPC translating proxy in front of it, and actually provide an HTTP REST API to users. Providing first class OpenAPI support allows server developers to use OpenAPI tools for providing an HTTP REST API, and gRPC tools for providing a gRPC API. That's what each tool was designed to do well. 71 | 72 | ## Proposed solution 73 | 74 | For a concrete end-to-end example of using an OpenAPI document with a generated client and server, check out the Swift.org [announcement](https://www.swift.org/blog/introducing-swift-openapi-generator/) and the WWDC 2023 [session](https://developer.apple.com/wwdc23/10171). 75 | 76 | ## Detailed design 77 | 78 | - `swift-openapi-generator` ([docs](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation)) is the package plugin, which consumes an OpenAPI document, and generates Swift files. It uses a command-line tool of the same name under the hood, which can be used directly by adopters in cases where a build plugin isn't appropriate. 79 | - `swift-openapi-runtime` ([docs](https://swiftpackageindex.com/apple/swift-openapi-runtime/documentation)) is a library package that provides the HTTP currency types, the transport and middleware abstractions, and helper functions used by the generated code. 80 | - `swift-openapi-urlsession` ([docs](https://swiftpackageindex.com/apple/swift-openapi-urlsession/documentation)) is a library package that provides a concrete implementation of a `ClientTransport` that uses Foundation's `URLSession` API under the hood. 81 | - `swift-openapi-async-http-client` ([docs](https://swiftpackageindex.com/swift-server/swift-openapi-async-http-client/documentation)) is a library package that provides a concrete implementation of a `ClientTransport` that uses the SwiftNIO-based AsyncHTTPClient under the hood. 82 | 83 | ## Maturity Justification 84 | 85 | We are proposing this package at the "Sandbox" level of maturity. The project is aiming to release a 1.0 later this year, and we hope to solicit more feedback before we stabilize the API - especially since the spelling of the common abstractions affects all the transport and middleware implementations around the ecosystem. 86 | 87 | Minimum Requirements: 88 | 89 | * General 90 | * Has relevance to Swift on Server specifically: **Yes, it fills a gap in the ecosystem.** 91 | * Publicly accessible source managed by an SCM such as github.com or similar: **Satisfied: Repository is stored on github.com** 92 | * Prefer to use `main` as the default branch name, in line with [Swift's guidelines](https://forums.swift.org/t/moving-default-branch-to-main/38515): **Satisfied** 93 | * Adopt the [Swift Code of Conduct](https://swift.org/community/#code-of-conduct): **Satisfied** 94 | * Ecosystem 95 | * Uses SwiftPM: **Satisfied** 96 | * Integrated with critical SSWG ecosystem building blocks, e.g., Logging and Metrics APIs, SwiftNIO for IO: **It composes well with the existing ecosystem, and was architected for extensibility.** 97 | * Longevity 98 | * Must be from a team that has more than one public repository (or similar indication of experience): **Satisfied: hosted under Apple organization on GitHub; Active maintainers are @czechboy0, @simonjbeaumont, @glbrntt, and @gjcairo. ** 99 | * SSWG should have access / authorization to graduated repositories in case of emergency: **Satisfied (Konrad, Franz, Tomer)** 100 | * Adopt the [SSWG Security Best Practices](../security/README.md)): **Satisfied**, see [SECURITY.md](https://github.com/apple/swift-openapi-generator/blob/main/SECURITY.md). 101 | * Testing, CI and Release 102 | * Have unit tests for Linux: **Satisfied** 103 | * CI setup, including testing PRs and the main branch: **PRs only for now** 104 | * Follow semantic versioning, with at least one published pre-release (e.g. 0.1.0, 1.0.0-beta.1) or release (e.g. 1.0.0): **Satisfied, pre-1.0 semver for now** 105 | * Licensing 106 | * Apache 2, MIT, or BSD (Apache 2 recommended): **Satisfied: Apache 2** 107 | * Conventions and Style 108 | * Adopt [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/): **Satisfied** 109 | * Follow [SSWG Technical Best Practices](#technical-best-practices) when applicable: **Satisfied** 110 | * Prefer to adopt code formatting tools and integrate them into the CI: **Satisfied** 111 | 112 | ## Alternatives considered 113 | 114 | The alternative would be for Swift developers to continue to hand-write Swift client and server HTTP ceremony code, while in other language ecosystems they can get the same code generated in seconds. Long term, this would like work as a reason _not_ to adopt Swift, and especially Swift on server. 115 | -------------------------------------------------------------------------------- /proposals/0027-mongokitten.md: -------------------------------------------------------------------------------- 1 | # MongoKitten 2 | 3 | * Proposal: [SSWG-0027](0027-mongokitten.md) 4 | * Authors: [Joannis Orlandos](https://github.com/joannis) 5 | * Review Manager: [Sven A. Schmidt](https://github.com/finestructure) 6 | * Status: **Implemented** 7 | * Implementation: [MongoKitten](https://github.com/orlandos-nl/MongoKitten) 8 | * Forum Threads: None 9 | 10 | ## Package Description 11 | A MongoDB client library for Swift that embraces Codable and Swift's modern concurrency standards. 12 | 13 | | | | 14 | |--|--| 15 | | **Package Name** | `MongoKitten` | 16 | | **Module Name** | `MongoKitten` | 17 | | **Proposed Maturity Level** | [Sandbox or Incubating](https://www.swift.org/sswg/incubation-process.html#process-diagram) | 18 | | **License** | [MIT](https://github.com/orlandos-nl/MongoKitten/blob/main/LICENSE.md) | 19 | | **Dependencies** | [BSON 8.x](https://github.com/orlandos-nl/BSON), [swift-nio 2.43+](https://github.com/apple/swift-nio), [swift-log 1.0](https://github.com/apple/swift-log), [swift-metrics 1.0 or 2.0](https://github.com/apple/swift-metrics), [swift-atomics 1.0](https://github.com/apple/swift-atomics), [DNSClient 2.2](https://github.com/orlandos-nl/DNSClient), [swift-nio-ssl 2.x](https://github.com/apple/swift-nio-ssl) | 20 | 21 | ## Introduction 22 | 23 | MongoDB is a popular NoSQL database management system that uses a document-oriented data model. 24 | MongoKitten is a MongoDB client library that supports all non-enterprise features. It embraces Codable through [BSON](https://github.com/orlandos-nl/BSON)'s encoders and decoders, and vendors exclusively async/await based APIs. 25 | 26 | ## Motivation 27 | 28 | MongoKitten is a longstanding library for the Swift ecosystem, and has been in development since 2015. MongoDB has also created another database driver, which vendors a Swift API wrapping their internal C implementation. However, this driver is a relative newcomer to the ecosystem and has been discontinued since. 29 | 30 | Providing a solution for MongoDB is essential for the Swift ecosystem as it serves a significant portion of the database market. 31 | 32 | ## Proposed solution 33 | 34 | MongoKitten is layered in multiple modules. 35 | 36 | **MongoCore** contains a basic implementation of the MongoDB wire protocol. 37 | 38 | **MongoClient** contains a MongoDB client based on this protocol. It contains all the tools a user needs to communicate with MongoDB. It has the ability to send & receive messages and authenticate. In addition, MongoClient also has helpers for sending/reading messages sending and receiving Codable conforming messages on such a connection. Finally, MongoClient has a MongoDB Cluster client implementation, functioning as a connection pool, while also observing changes in cluster topology. 39 | 40 | **MongoKittenCore** is a module containing the most commonly used messages implemented as Codable types. 41 | 42 | The **MongoKitten** module itself imports all of the above, and provides a higher level API for interacting with MongoDB. This is the library that most users end up interacting with. 43 | 44 | Finally, the **Meow** module vendors ORM-like helpers for quickly storing and reading entities from MongoDB, by conforming your Codable type to the `Model` protocol. 45 | 46 | 47 | ## Detailed design 48 | 49 | The MongoKitten client can be initialized through either the `MongoDatabase` or `MongoCluster` objects. 50 | 51 | MongoCluster is the object that is able to observe changes in cluster topology, and is MongoKitten's driver. MongoDB allows a single connection to query multiple databases on the same database server. Since most users will often only need access to one database on the server at a time, the `MongoDatabase` object provides helper APIs to initialize a connection to a server and _then_ access a specific database. 52 | 53 | ```swift 54 | let mongodb = try await MongoDatabase.connect(to: connectionString) 55 | ``` 56 | 57 | From here, users can subscript the database to access a collection. MongoDB Collections do not need to be create before being accessed, nor does MongoDB require users to specify a schema. 58 | The operation of 'accessing' a collection is purely a form of API design. 59 | 60 | ```swift 61 | let users = mongodb["users"] 62 | ``` 63 | 64 | Users can insert entities through a MongoDB BSON `Document` type, a binary equivalent to JSON that supports more data types. 65 | 66 | ```swift 67 | try await users.insert([ 68 | "_id": ObjectId(), 69 | "username": "Joannis", 70 | "likesSwift": true 71 | ]) 72 | ``` 73 | 74 | APIs that support Document also support Codable variants: 75 | 76 | ```swift 77 | let user = User( 78 | username: "Joanis", 79 | likesSwift: true 80 | ) 81 | 82 | try await users.insertEncoded(user) 83 | ``` 84 | 85 | ### Reading Results 86 | 87 | When creating a **find** query using the `find()` function, users can provide MongoKitten with a query formed as a Document. Omitting this argument queries _all_ results, and is equivalent to SQL's `SELECT * FROM users`. 88 | 89 | ```swift 90 | let results = users.find() 91 | ``` 92 | 93 | Note that `find()` is not marked with `try await`. This is because `find()` uses a _builder pattern_, allowing users to chain modifiers to it such as `limit` and `skip`. 94 | 95 | ```swift 96 | let usersThirdPage = users.find() 97 | .skip(100) 98 | .limit(50) 99 | ``` 100 | 101 | Such builder objects exist for all cursor-based results, and will send the query as results are requested. Cursors conform to `AsyncSequence`, allowing users to iterate results in a for-loop. 102 | 103 | ```swift 104 | for try await user in users.find() { 105 | // Use the `user` Document 106 | } 107 | ``` 108 | 109 | Like other APIs, cursors also have a helper for decoding results: 110 | 111 | ```swift 112 | for try await user in users.find().decode(User.self) { 113 | // `user` is now an instance of `User` 114 | } 115 | ``` 116 | 117 | Should custom behaviour be preferred, users can implement their own lazy mutations of this sequence using the `cursor.map` helper. 118 | 119 | ### Aggregates 120 | 121 | MongoDB provides a powerful system for processing data using more complex queries through **aggregates**. This includes GeoJSON queries, Text Search, Graph Queries, Left Joins and many more features uniquely present in aggregates. 122 | 123 | Aggregates are designes a pipeline, whose input is a document from the collection queries. Each stage is triggered per inputted entity, which can then transform, add or omit results. For example, a **Match** stage will filter included entities, omitting results that do not meet the requested conditions. 124 | 125 | MongoKitten uses a result builder for designing these aggregate pipelines, making it easy for users to review their pipelines. 126 | 127 | ```swift 128 | // Find kids to invite to my party 129 | let partygoers = mongodb["kids"].buildAggregate { 130 | Match([ 131 | "age": [ 132 | "$lte": 10 133 | ] 134 | ]) 135 | 136 | // only 15 friends are invited to the party 137 | Limit(15) 138 | 139 | // Siblings of friends are also invited 140 | Lookup( 141 | from: "kids", 142 | localField: "siblings", 143 | foreignField: "_id", 144 | pipelinke: { 145 | // Siblings may not be too old either 146 | Match([ 147 | "age": [ 148 | "$lte": 10 149 | ] 150 | ]) 151 | }, 152 | as: "invitedSiblings" 153 | ) 154 | } 155 | ``` 156 | 157 | ## Maturity Justification 158 | 159 | Sandbox or Incubating. MongoKitten adheres to the minimal requirements of the [SSWG Incubation Process](https://github.com/swift-server/sswg/blob/master/process/incubation.md#minimal-requirements). It has a [second contributor](https://github.com/obbut) historically and currently involved with the library, however, the second contributor is not _publicly_ involved through contributing commits. Therefore it's unclear which is more suitable at this time. 160 | 161 | ## Alternatives considered 162 | 163 | The only alternative to MongoKitten during development was [SwiftMongoDB](https://github.com/Danappelxx/SwiftMongoDB), which was in very early stages and eventually discontinued as MongoKitten was further established. 164 | 165 | At the time of proposing, MongoDB has released an [official alternative](https://github.com/mongodb/mongo-swift-driver) which has since been [discontinued](https://www.mongodb.com/community/forums/t/updates-on-mongodb-swift-driver/240047). 166 | -------------------------------------------------------------------------------- /proposals/0028-oracle-nio.md: -------------------------------------------------------------------------------- 1 | # Solution name 2 | 3 | * Proposal: [SSWG-0028](0028-oracle-nio.md) 4 | * Authors: [Timo Zacherl](https://github.com/lovetodream) 5 | * Review Manager: [Sébastien Stormacq](https://github.com/sebsto) 6 | * Status: **Implementated** 7 | * Implementation: [oracle-nio](https://github.com/lovetodream/oracle-nio) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/pitch-oraclenio-oracle-db-driver-built-on-swiftnio/69088) 9 | 10 | ## Package Description 11 | A client library for Oracle databases based on swift-nio. It does neither require OCI nor ODPI. 12 | 13 | | | | 14 | |--|--| 15 | | **Package Name** | `oracle-nio` | 16 | | **Module Name** | `OracleNIO` | 17 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 18 | | **License** | [Apache 2.0](https://github.com/lovetodream/oracle-nio/blob/main/LICENSE) | 19 | | **Dependencies** | [swift-log 1.4.4+](https://github.com/apple/swift-log), [swift-nio 2.58.0+](https://github.com/apple/swift-nio), [swift-nio-transport-services](https://github.com/apple/swift-nio-transport-services), [swift-nio-ssl 2.25.0+](https://github.com/apple/swift-nio-ssl), [swift-crypto 3.2.0+](https://github.com/apple/swift-crypto), [postgres-nio 1.20.0](https://github.com/vapor/postgres-nio) | 20 | 21 | ## Introduction 22 | 23 | Oracle DB is a popular relational database management system by the Oracle Corporation. 24 | OracleNIO is a client library that allows users to connect, authorize and query these Oracle database servers, by using Swift's latest concurrency features. 25 | It uses Oracle's proprietary **T**ransparent **N**etwork **S**ubstrate (TNS) protocol to communicate with the database peer. 26 | 27 | ## Motivation 28 | 29 | Previously it was only possible to integrate with Oracle databases by wrapping C libraries, which required additional system dependencies such as the Oracle Call Interface (OCI) and/or others. 30 | The setup and usage was not ideal and error prone. 31 | 32 | Providing a driver for Oracle databases is a great addition to the Swift on the Server ecosystem as it is a very popular database. 33 | 34 | ## Proposed solution 35 | 36 | OracleNIO is a standalone, first-class Swift driver for Oracle databases. 37 | It supports all modern Oracle databases, starting with 12.1. 38 | The library allows user's to use Oracle databases in their Swift projects without requiring additional setup. 39 | 40 | ## Detailed design 41 | 42 | There are two ways of connecting to an Oracle database, namely [`OracleConnection`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oracleconnection) or [`OracleClient`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oracleclient). 43 | `OracleConnection` instanciates a single connection to the database, whereas `OracleClient` creates a pool of connections. 44 | Both take a [`OracleConnection.Configuration`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oracleconnection/configuration) parameter to configure the individual connections. 45 | 46 | ```swift 47 | let logger = Logger(label: "my-oracle-logger") 48 | 49 | let config = OracleConnection.Configuration( 50 | host: "127.0.0.1", 51 | port: 1521, 52 | service: .serviceName("my_service"), 53 | username: "my_username", 54 | password: "my_password" 55 | ) 56 | 57 | let connection = try await OracleConnection.connect( 58 | configuration: config, 59 | id: 1, 60 | logger: logger 61 | ) 62 | ``` 63 | 64 | With the now successfully established connection, one can start to query the database using the [`query(_:)`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oracleconnection/query(_:options:logger:file:line:)) method. 65 | This method allows the user to run a Query, DML, DDL or [PL/SQL](https://www.oracle.com/database/technologies/appdev/plsql.html). 66 | 67 | ```swift 68 | let rows = try await connection.query("SELECT id, username, birthday FROM users") 69 | ``` 70 | 71 | A query returns an [`OracleRowSequence`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oraclerowsequence), which is an AsyncSequence of [`OracleRow`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oraclerow)s. The rows can either be collected altogether or iterated one-by-one. 72 | It's possible to decode rows to a set of Swift types directly. 73 | 74 | ```swift 75 | for try await (id, username, birthday) in rows.decode((Int, String, Date).self) { 76 | // do something with the datatypes. 77 | } 78 | ``` 79 | 80 | A type must implement the [`OracleDecodable`](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio/oracledecodable) protocol in order to be decoded from a row. 81 | OracleNIO provides default implementations for most of Swift's builtin types, as well as some types provided by Foundation. 82 | Users of the library can implement their own `OracleDecodable` types, too. 83 | 84 | Once done, the database has to be closed. 85 | 86 | ```swift 87 | try await connection.close() 88 | ``` 89 | 90 | An up-to-date documentation of the public API can be found on the [Swift Package Index](https://swiftpackageindex.com/lovetodream/oracle-nio/main/documentation/oraclenio). 91 | 92 | It should be noted that the public API and many of the internals are very similar to [PostgresNIO](https://github.com/vapor/postgres-nio), which was the main inspiration in the process of creating OracleNIO. This should make adoption of OracleNIO easier to those already familiar with PostgresNIO. 93 | 94 | ### Dependency changes since the Pitch 95 | 96 | I stated in the [pitch](https://forums.swift.org/t/pitch-oraclenio-oracle-db-driver-built-on-swiftnio/69088), that I'd like to drop the dependency on CryptoSwift. 97 | I've already done so by using the newly added no-padding variant of CBC in swift-crypto. 98 | And implementing an internal `_PKBDF2` library as part of OracleNIO. 99 | This internal library is planned to be superseded by swift-crypto's implementation, if it will be added in the future. 100 | 101 | ## Maturity Justification 102 | 103 | OracleNIO adheres to the minimal requirements of the SSWG Incubation Process, therefor the Sandbox maturity level should be sufficient. 104 | 105 | ## Alternatives considered 106 | 107 | I don't know about any actively maintained alternatives to OracleNIO. 108 | 109 | There are various seemingly unmaintained forks of `SwiftOracle`, (the most promising one seemed to be [this once](https://github.com/iliasaz/SwiftOracle)) in existance, but they aren't as confinient to use as OracleNIO and they all require C dependencies. 110 | -------------------------------------------------------------------------------- /proposals/0029-jwsetkit.md: -------------------------------------------------------------------------------- 1 | # JWSETKit 2 | 3 | * Proposal: [SSWG-0029](0029-jwsetkit.md) 4 | * Authors: [Amir-Abbas Mousavian](https://github.com/amosavian) 5 | * Review Manager: [Konrad 'ktoso' Malawski](https://github.com/ktoso) 6 | * Status: **Implemented** 7 | * Implementation: [amosavian/JWSETKit](https://github.com/amosavian/JWSETKit) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/pitch-jwt-jws-jwt-support-kit/68113), [Discussion](https://forums.swift.org/), [Review](https://forums.swift.org/) 9 | 10 | ## Package Description 11 | 12 | Lightweight, modular, modern, extensible JWS/JWE/JWT framework written in Swift. 13 | 14 | | | | 15 | |--|--| 16 | | **Package Name** | `JWSETKit` | 17 | | **Module Name** | `JWSETKit` | 18 | | **Proposed Maturity Level** | [Sandbox](https://www.swift.org/sswg/incubation-process.html) | 19 | | **License** | [MIT](https://github.com/amosavian/JWSETKit/blob/main/LICENSE) | 20 | | **Dependencies** | [AnyCodable](https://github.com/Flight-School/AnyCodable), [CryptoSwift](https://github.com/krzyzanowskim/CryptoSwift), [swift-asn1](https://github.com/apple/swift-asn1), [swift-certificates](https://github.com/apple/swift-certificates), [swift-crypto](https://github.com/apple/swift-crypto) | 21 | 22 | ## Introduction 23 | 24 | We propose that the [JWSETKit](https://github.com/amosavian/JWSETKit) library be considered for incubation by the Swift Server Workgroup. JWSETKit is a Swift library that provides a comprehensive suite of tools for handling JSON Web Encryption (JWE), JSON Web Signatures (JWS), and JSON Web Tokens (JWT), which are crucial for secure data transmission and authentication in server-side Swift applications. 25 | 26 | ## Motivation 27 | 28 | Security is a paramount concern in server-side development. The JWSETKit library provides a robust and Swift-native solution for handling JWE, JWS, and JWT. These are widely used standards for securing data and managing user authentication and authorization. By incubating JWSETKit, the Swift Server Workgroup can help to ensure that server-side Swift developers have access to high-quality, community-vetted tools for secure data handling and user authentication. 29 | 30 | ## Detailed Design 31 | 32 | JWSETKit library provides following objectives according to RFCs: 33 | 34 | ### JWA 35 | 36 | All algorithms defined in [RFC7518](https://tools.ietf.org/html/rfc7518) and 37 | [RFC8037](https://tools.ietf.org/html/rfc8037) except `ECDH-ES` related 38 | algorithms are supported. Algorithms can be categorized into three types, 39 | which are supported by this library: 40 | 41 | * `JSONWebSignatureAlgorithm` for JWS algorithms. 42 | * `JSONWebKeyEncryptionAlgorithm` for JWE key encryption algorithms. 43 | * `JSONWebContentEncryptionAlgorithm` for JWE content encryption algorithms. 44 | 45 | All types conform to `JSONWebAlgorithm` and can be type-erased using `AnyJSONWebAlgorithm`. 46 | 47 | New algorithms can be defined and registered using a `register` method of each type by declaring public and private key classes and associated data. 48 | 49 | ### JWK 50 | 51 | All key types including CommonCrypto's `SecKey` and `SecCertificate` and 52 | CryptoKit's types including `P256`, `P384`, `P521` and `SymmetricKey` can be 53 | encoded to JWK using `JSONEncoder` or be deserialized from JWK using `JSONDecoder`. 54 | 55 | This library also provides custom key types: 56 | 1. To support other key types such as `AES-CBC-HMAC`, `AES-GCM` and `HMAC`. 57 | 2. To provide container types for `RSA` and `EC` private and public keys. 58 | 59 | #### JWK Functionality 60 | 61 | `JSONWebKey` is a generic type that defines general key structure. For functionality, it's divided into following protocols: 62 | 63 | * `JSONWebEncryptingKey` acts as a public key for encryption. 64 | * `JSONWebDecryptingKey` acts as a private key for decryption. 65 | * `JSONWebSealingKey` acts as a symmetric key for authenticated encryption. 66 | * `JSONWebValidatingKey` acts as a public key for signature verification. 67 | * `JSONWebSigningKey` acts as a private key for signing. 68 | 69 | #### JWK Set 70 | 71 | `JSONWebKeySet` is a container for `JSONWebKey` objects compliant to [RFC7517](https://tools.ietf.org/html/rfc7517). It conforms to `Collection` protocol. 72 | 73 | ### JWS 74 | 75 | `JSONWebSignature` is a generic type that can be used to sign and verify any 76 | payload type conforming to `JWSPayload` protocol. 77 | This library provides `ProtectedWebContainer` and `TypedProtectedWebContainer` 78 | protocols to support different payload types. 79 | 80 | These concrete implementations of Web Containers are provided: 81 | * `ProtectedDataWebContainer` for raw/untyped data. 82 | * `ProtectedJSONWebContainer` for payloads that should be encoded as JSON using `JSONWebContainer` as base protocol. 83 | 84 | `JSONWebContainer` has a storage property which contains data as dictionary. 85 | It's easy to extend conformed types using `@dynamicMemberLookup` to define various accessors to registered keys. 86 | 87 | ### JWT 88 | 89 | JWT is specialized `JSONWebSignature` with `JSONWebTokenClaims` as payload. 90 | Some convenience methods are provided to create, parse and verify JWTs. 91 | 92 | Default implementation in library contains following claims defined in [IANA JSON Web Token Claims Registry](https://www.iana.org/assignments/jwt/jwt.xhtml): 93 | * Claims registered in [RFC7519](https://tools.ietf.org/html/rfc7519) 94 | are defined in `JSONWebTokenClaimsRegisteredParameters`. 95 | * Claims registered in [RFC8693](https://tools.ietf.org/html/rfc8693) 96 | are defined in `JSONWebTokenClaimsOAuthParameters`. 97 | * Claims for `id_token` registered in [OpenID Connect Core Section 2](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) 98 | are defined in `JSONWebTokenClaimsPublicOIDCAuthParameters`. 99 | * Claims for `id_token` registered in [OpenID Connect Core Section 5.1](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) 100 | are defined in `JSONWebTokenClaimsPublicOIDCStandardParameters` with localization support. 101 | 102 | Other claims can easily be defined by declaring parameters using 103 | `JSONWebContainerParameters` and and extending `JSONWebTokenClaims` 104 | to implement dynamic member lookup for parameters. 105 | 106 | ### JWE 107 | 108 | JWE is supported using `JSONWebEncryption` type encryption and decryption of data using given keys. 109 | 110 | A new JWE can be created using `init(protected:content:additionalAuthenticatedData:keyEncryptingAlgorithm:keyEncryptionKey:contentEncryptionAlgorithm:contentEncryptionKey:)` method with given data and keys. 111 | 112 | An existing JWE can be decrypted `decrypt(using:keyId:)` method. 113 | 114 | ## Impact 115 | 116 | Incubating JWSETKit will have several positive impacts on the server-side Swift community: 117 | 118 | - It will provide developers with a robust, Swift-native solution for handling JWE, JWS, and JWT. 119 | - It will help to promote best practices for secure data handling and user authentication in server-side Swift development. 120 | - It will contribute to the ongoing growth and maturity of the server-side Swift ecosystem. 121 | 122 | ## Maturity Justification 123 | 124 | Requirements for maturity level "Sandbox": 125 | 126 | **General** 127 | - ✅ Has relevance to Swift on Server specifically 128 | - ✅ Publicly accessible source managed by an SCM such as github.com or similar 129 | - ✅ Prefer to use main as the default branch name, in line with Swift's guidelines 130 | - 🔳 Adopt the Swift Code of Conduct 131 | 132 | **Ecosystem** 133 | - ✅ Uses SwiftPM 134 | - ✅ Integrated with critical SSWG ecosystem building blocks, e.g., Logging and Metrics APIs, SwiftNIO for IO 135 | 136 | **Longevity** 137 | - 🔳 Must be from a team that has more than one public repository (or similar indication of experience) 138 | - 🔳 SSWG should have access / authorization to graduated repositories in case of emergency 139 | - 🔳 Adopt the SSWG Security Best Practices 140 | 141 | **Testing, CI and Release** 142 | - ✅ Have unit tests for Linux 143 | - ✅ CI setup, including testing PRs and the main branch 144 | - ✅ Follow semantic versioning, with at least one published pre-release (e.g. 0.1.0, 1.0.0-beta.1) or release (e.g. 1.0.0) 145 | 146 | **Licensing** 147 | - ✅ Apache 2, MIT, or BSD (Apache 2 recommended) 148 | 149 | **Conventions and Style** 150 | - ✅ Adopt Swift API Design Guidelines 151 | - ✅ Follow SSWG Technical Best Practices when applicable. 152 | - ✅ Prefer to adopt code formatting tools and integrate them into the CI 153 | 154 | ## Alternatives considered 155 | 156 | The only viable alternative which supports Linux is Vapor's [jwt-kit], which is a mature library that supports JWT. 157 | However, it does not support JWE and JWS. Also It does not provides interoperability with CommonCrypto and CryptoKit. 158 | -------------------------------------------------------------------------------- /proposals/0030-jwtkit.md: -------------------------------------------------------------------------------- 1 | # JWTKit SSWG Proposal 2 | 3 | * Proposal: [SSWG-0030](0030-jwtkit.md) 4 | * Authors: [Paul Toffoloni](https://github.com/ptoffy), [Tim Condon](https://github.com/0xTim), [Gwynne Raskind](https://github.com/gwynne) 5 | * Review Manager: [Konrad 'ktoso' Malawski](https://github.com/ktoso) 6 | * Status: **Graduated** 7 | * Implementation: [JWTKit](https://github.com/vapor/jwt-kit) 8 | * Forum Threads: [Initial pitch](https://forums.swift.org/t/pitch-jwtkit/70205) 9 | 10 | ## Package Description 11 | 12 | A JWT library that allows signing and verifying tokens with all modern algorithms. 13 | 14 | | **Package Name** | `jwt-kit` | 15 | | --------------------------- | ------------------------------------------------------------ | 16 | | **Module Name** | `JWTKit` | 17 | | **Proposed Maturity Level** | Incubating | 18 | | **License** | [MIT](https://choosealicense.com/licenses/mit/) | 19 | | **Dependencies** | [swift-crypto 3.0.0](https://github.com/apple/swift-crypto.git)<br />[swift-certificates 1.2.0](https://github.com/apple/swift-certificates.git)<br />[BigInt 5.3.0](https://github.com/attaswift/BigInt.git) | 20 | 21 | ## Introduction 22 | 23 | JWTs are an extremely powerful and popular tool used for safe data transfer between two parties in a distributed system and any modern server-side ecosystem provides its users with a JWT library. 24 | 25 | JWTKit aims to fill this gap in the server-side Swift world by providing users with every possible need regarding JWTs, all based on SwiftCrypto. 26 | 27 | ## Motivation 28 | 29 | JWTKit has been the go-to library for handling JWTs in a server-side Swift environment for quite some time. 30 | 31 | The library has recently undergone a major version upgrade which removed the use of BoringSSL and replaced it with SwiftCrypto, making it the perfect fit for filling the gap of a missing JWT library for the server-side Swift ecosystem. 32 | 33 | ## Proposed Solution 34 | 35 | JWTKit provides the user with most operations needed to process JWTs, and a customisation API for the user to add options if they're not present. 36 | 37 | As of version 5, JWTKit implements cryptographic operations using SwiftCrypto rather than BoringSSL, which is a significant improvement given the library being now Swift native. There are a couple of other JWT libraries for server-side Swift but none of them provides a completely platform-agnostic solution. JWTKit also implements `Sendable`, making it safe to use across concurrency-domains. 38 | 39 | JWTKit's API revolves around the `JWTKeyCollection` type, which, as the name suggests, is a collection of keys which can sign and verify tokens. It's an `actor`, which means that access to its state is `async`: 40 | 41 | ```swift 42 | let keys = JWTKeyCollection() 43 | try await keys.addES256(key: ES256PrivateKey(pem: yourPEMString)) 44 | ``` 45 | 46 | after creating a payload type: 47 | 48 | ```swift 49 | struct TestPayload: JWTPayload { ... } 50 | ``` 51 | 52 | this collection can both sign: 53 | 54 | ```swift 55 | let payload = TestPayload( ... ) 56 | let tokenStringRepresentation = try await keys.sign(payload) 57 | ``` 58 | 59 | and verify: 60 | 61 | ```swift 62 | let payload = try await keys.verify(tokenStringRepresentation, as: TestPayload.self) 63 | ``` 64 | 65 | ## Detailed Design 66 | 67 | As explained above, the API provides a `JWTKeyCollection` type which contains all keys used by the user which can be filtered using a `kid` (Key Identifier) provided when adding the key to the collection. 68 | 69 | ### Algorithms 70 | 71 | The key types try to follow SwiftCrypto's key structure, and the following algorithms are provided: 72 | 73 | | JWS | Algorithm | Description | 74 | | :---: | :-------: | :--------------------------------- | 75 | | HS256 | HMAC256 | HMAC with SHA-256 | 76 | | HS384 | HMAC384 | HMAC with SHA-384 | 77 | | HS512 | HMAC512 | HMAC with SHA-512 | 78 | | RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 | 79 | | RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 | 80 | | RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 | 81 | | PS256 | RSA256PSS | RSASSA-PSS with SHA-256 | 82 | | PS384 | RSA384PSS | RSASSA-PSS with SHA-384 | 83 | | PS512 | RSA512PSS | RSASSA-PSS with SHA-512 | 84 | | ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 | 85 | | ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 | 86 | | ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 | 87 | | EdDSA | EdDSA | EdDSA with Ed25519 | 88 | | none | None | No digital signature or MAC | 89 | 90 | > Note: 91 | > 92 | > While provided, the RSA key is gated behing an `Insecure` namespace to discourage new users from using it. 93 | 94 | Each of these algorithms has its own, type-safe method for adding keys to the key collection. 95 | 96 | ### Payload and Claims 97 | 98 | The package provides a `JWTPayload` protocol which can be conformed to when creating your own payload type: 99 | 100 | ```swift 101 | struct ExamplePayload: JWTPayload { 102 | var sub: SubjectClaim 103 | var exp: ExpirationClaim 104 | var admin: BoolClaim 105 | 106 | func verify(using key: JWTAlgorithm) throws { 107 | try self.exp.verifyNotExpired() 108 | } 109 | } 110 | ``` 111 | 112 | This type can then be used to sign and verify tokens. 113 | 114 | There are some default claims as listed here: 115 | 116 | | Claim | Type | Verify Method | 117 | | :---- | :---------------- | :---------------------------------- | 118 | | `aud` | `AudienceClaim` | `verifyIntendedAudience(includes:)` | 119 | | `exp` | `ExpirationClaim` | `verifyNotExpired(currentDate:)` | 120 | | `jti` | `IDClaim` | n/a | 121 | | `iat` | `IssuedAtClaim` | n/a | 122 | | `iss` | `IssuerClaim` | n/a | 123 | | `nbf` | `NotBeforeClaim` | `verifyNotBefore(currentDate:)` | 124 | | `sub` | `SubjectClaim` | n/a | 125 | 126 | but there's also the possibility to create custom claims using the `JWTClaim` type. 127 | 128 | ### JWKs 129 | 130 | JWTKit provides support for using JWKs, which are keys in a JSON format: 131 | 132 | ```swift 133 | let json = """ 134 | { 135 | "keys": [ 136 | {"kty": "RSA", "alg": "RS256", "kid": "a", "n": "\(rsaModulus)", "e": "AQAB"}, 137 | {"kty": "RSA", "alg": "RS512", "kid": "b", "n": "\(rsaModulus)", "e": "AQAB"}, 138 | ] 139 | } 140 | """ 141 | 142 | let keyCollection = try await JWTKeyCollection().use(jwksJSON: json) 143 | // then use the keyCollection as always 144 | ``` 145 | 146 | ### Customisation 147 | 148 | JWTKit aims to encompass all possible needs of users, not by implementing all of the specs defined in the JOSE standards, but providing an API to let users implement their _own_ customisations. 149 | 150 | #### Header 151 | 152 | For example, while some of the most common header fields are present as type-safe extensions, the header is a dictionary which uses `@_dynamicMemberLookup`, which means that the user can add any custom header. 153 | 154 | ```swift 155 | let token = try await keyCollection.sign(payload, header: ["b64": true]) 156 | ``` 157 | 158 | #### Parsing and Serialising 159 | 160 | Some header fields, like the `b64` one, require that the payload may be encoded some custom, way. In particular, when the `b64` header field is set to `false`, the payload should _not_ be base64URL encoded. JWTKit provides an API to customise this behaviour by creating a custom parser and serialiser: 161 | 162 | ```swift 163 | struct CustomSerializer: JWTSerializer { 164 | // Here you can set a custom encoder or just leave this as default 165 | var jsonEncoder: JWTJSONEncoder = .defaultForJWT 166 | 167 | // This method should return the payload in the way you want/need it 168 | func serialize(_ payload: some JWTPayload, header: JWTHeader) throws -> Data { 169 | // Check if the b64 header is set. If it is, base64URL encode the payload, don't otherwise 170 | if header.b64?.asBool == true { 171 | try Data(jsonEncoder.encode(payload).base64URLEncodedBytes()) 172 | } else { 173 | try jsonEncoder.encode(payload) 174 | } 175 | } 176 | } 177 | 178 | struct CustomParser: JWTParser { 179 | // Here you can set a custom decoder or just leave this as default 180 | var jsonDecoder: JWTJSONDecoder = .defaultForJWT 181 | 182 | // This method parses the token into a tuple containing the various token's elements 183 | func parse<Payload>(_ token: some DataProtocol, as: Payload.Type) throws -> (header: JWTHeader, payload: Payload, signature: Data) where Payload: JWTPayload { 184 | // A helper method is provided to split the token correctly 185 | let (encodedHeader, encodedPayload, encodedSignature) = try getTokenParts(token) 186 | 187 | // The header is usually always encoded the same way 188 | let header = try jsonDecoder.decode(JWTHeader.self, from: .init(encodedHeader.base64URLDecodedBytes())) 189 | 190 | // If the b64 header field is non present or true, base64URL decode the payload, don't otherwise 191 | let payload = if header.b64?.asBool ?? true { 192 | try jsonDecoder.decode(Payload.self, from: .init(encodedPayload.base64URLDecodedBytes())) 193 | } else { 194 | try jsonDecoder.decode(Payload.self, from: .init(encodedPayload)) 195 | } 196 | 197 | // The signature is usually also always encoded the same way 198 | let signature = Data(encodedSignature.base64URLDecodedBytes()) 199 | 200 | return (header: header, payload: payload, signature: signature) 201 | } 202 | } 203 | ``` 204 | 205 | These can be simply added to the key collection: 206 | 207 | ```swift 208 | let keyCollection = await JWTKeyCollection() 209 | .addHS256(key: "secret", parser: CustomParser(), serializer: CustomSerializer()) 210 | ``` 211 | 212 | ### Maturity Justification 213 | 214 | Incubating or Graduated. The package follows [minimum requirements](https://www.swift.org/sswg/incubation-process.html#minimal-requirements) and most of the [graduation ones](https://www.swift.org/sswg/incubation-process.html#graduation-requirements). 215 | 216 | ### Alternatives Considered 217 | 218 | Kitura's [Swift-JWT](https://github.com/Kitura/Swift-JWT), old and not maintained. 219 | 220 | [JWSETKit](https://github.com/amosavian/JWSETKit) not (yet) platform agnostic due to dependencies. 221 | -------------------------------------------------------------------------------- /proposals/0031-vapor.md: -------------------------------------------------------------------------------- 1 | # Vapor 2 | 3 | * Proposal: [SSWG-0031](0031-vapor.md) 4 | * Authors: [Tim Condon](https://github.com/0xTim), [Gwynne Raskind](https://github.com/gwynne) 5 | * Review Manager: Adam Fowler 6 | * Status: **Implemented** 7 | * Implementation: [vapor/vapor](https://github.com/vapor/vapor) 8 | * Forum Threads: [Pitch](https://forums.swift.org/t/pitch-vapor/70660) 9 | 10 | ## Package Description 11 | 12 | [Vapor](https://www.vapor.codes) is a framework for building server applications, APIs and backends in Swift. 13 | 14 | | | | 15 | |--|--| 16 | | **Package Name** | [`vapor`](https://github.com/vapor/vapor) | 17 | | **Module Name** | `Vapor` (Main library), `XCTVapor` (Testing Library) | 18 | | **Proposed Maturity Level** | [Graduated](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 19 | | **License** | [MIT](https://github.com/vapor/vapor/blob/main/LICENSE) | 20 | | **Dependencies** | [AsyncHTTPClient](https://github.com/swift-server/async-http-client.git) (v1), [AsyncKit](https://github.com/vapor/async-kit.git) (v1), [ConsoleKit](https://github.com/vapor/console-kit.git) (v4), [Swift Crypto](https://github.com/apple/swift-crypto.git) (v1, v2, v3, v4), [RoutingKit](https://github.com/vapor/routing-kit.git) (v4), [SwiftNIO](https://github.com/apple/swift-nio.git) (v2), [SwiftNIO SSL](https://github.com/apple/swift-nio-ssl.git) (v2), [SwiftNIO HTTP2](https://github.com/apple/swift-nio-http2.git) (v1), [SwiftNIO Extras](https://github.com/apple/swift-nio-extras.git) (v1), [Swift Log](https://github.com/apple/swift-log.git) (v1), [Swift Metrics](https://github.com/apple/swift-metrics.git) (v2), [Swift Algorithms](https://github.com/apple/swift-algorithms.git) (v1), [WebsocketKit](https://github.com/vapor/websocket-kit.git) (v2), [MultipartKit](https://github.com/vapor/multipart-kit.git) (v4), [Swift Atomics](https://github.com/apple/swift-atomics.git) (v1) | 21 | 22 | ## Introduction 23 | 24 | [Vapor](https://www.vapor.codes) is a full-featured framework for building server applications and API in Swift. It is a mature framework that has been around since 2016 and has build a large engaged community around it. Vapor provides everything you need to build a server-side Swift application. The main framework provides HTTP routing, middleware, sessions, authentication, websockets, and more. Vapor also provides a suite of packages for working with databases, templating, and other common tasks. 25 | 26 | ## Motivation 27 | 28 | HTTP libraries are a core component of any server-side Swift application. Vapor provides a high-level API for working with HTTP requests and responses to make it easy for developers to build full-featured server applications. It has been around since 2016 and built a large community around it. We believe it should be part of the incubation process to further integrate it into the ecosystem. 29 | 30 | ## Proposed solution 31 | 32 | We propose that the existing Vapor library be promoted to an officially supported package as is. 33 | 34 | ## Detailed design 35 | 36 | ### Basic Routing 37 | 38 | Routes can be defined on anything that conforms to `RoutesBuilder`, nominally the main `Application`: 39 | 40 | ```swift 41 | app.get("hello") { req in 42 | "Hello, world!" 43 | } 44 | ``` 45 | 46 | Routing supports all standard HTTP methods, such as a **POST** request: 47 | 48 | ```swift 49 | app.post("info") { req -> Info in 50 | let data = try req.content.decode(Info.self) 51 | return data 52 | } 53 | ``` 54 | 55 | Dynamic path parameters are supported as well: 56 | 57 | ```swift 58 | app.get("users", ":userID") { req -> User in 59 | let userID = req.parameters.get("userID")! 60 | return User(id: userID) 61 | } 62 | ``` 63 | 64 | These examples also demonstrate how to return responses and decode request bodies. Vapor has extensive [documentation](https://docs.vapor.codes) on all of the supported features and [API Documentation](https://api.vapor.codes) for Vapor's API. 65 | 66 | ## Maturity Justification 67 | 68 | We are proposing this package at the "Graduated" level of maturity. Vapor has a stable and mature API and ecosystem, and is widely used by many companies. It also has a large pool of contributors as well as an established Core Team that is responsible for the project's direction and stability. 69 | 70 | ## Alternatives considered 71 | 72 | There are a number of alternative frameworks for writing APIs in Swift and users should choose the framework that best suits their needs. 73 | -------------------------------------------------------------------------------- /proposals/0032-hummingbird.md: -------------------------------------------------------------------------------- 1 | # Hummingbird 2 | 3 | * Proposal: [SSWG-0032](0032-hummingbird.md) 4 | * Authors: [Adam Fowler](https://github.com/adam-fowler), [Joannis Orlandos](https://github.com/Joannis) 5 | * Review Manager: [Tim Condon](https://github.com/0xTim) 6 | * Status: **Implemented** 7 | * Implementation: [Hummingbird](https://github.com/hummingbird-project/hummingbird) 8 | * Forum Threads: 9 | * [Pitch](https://forums.swift.org/t/pitch-hummingbird/70700) 10 | 11 | <!-- *During the review process, add the following fields as needed:* 12 | 13 | * Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/) 14 | * Previous Revision(s): [1](https://github.com/swift-server/sswg/blob/...commit-ID.../proposals/NNNN-filename.md) 15 | * Previous Proposal(s): [SSWG-XXXX](XXXX-filename.md) 16 | --> 17 | 18 | ## Package Description 19 | Lightweight, modular, modern, extensible HTTP server framework written in Swift. 20 | 21 | | | | 22 | |--|--| 23 | | **Package Name** | `hummingbird` | 24 | | **Module Name** | `Hummingbird` | 25 | | **Proposed Maturity Level** | [Sandbox](https://github.com/swift-server/sswg/blob/main/process/incubation.md#process-diagram) | 26 | | **License** | [Apache 2.0](https://choosealicense.com/licenses/apache-2.0/) | 27 | | **Dependencies** | [swift-async-algorithms](https://github.com/apple/swift-async-algorithms), [swift-atomics](https://github.com/apple/swift-atomics), [swift-distributed-tracing](https://github.com/apple/swift-distributed-tracing), [swift-http-types](https://github.com/apple/swift-http-types), [swift-log](https://github.com/apple/swift-log), [swift-metrics](https://github.com/apple/swift-metrics), [swift-nio](https://github.com/apple/swift-nio), [swift-nio-http2](https://github.com/apple/swift-nio-http2), [swift-nio-extras](https://github.com/apple/swift-nio-extras), [swift-nio-ssl](https://github.com/apple/swift-nio-ssl), [swift-nio-transport-services](https://github.com/apple/swift-nio-transport-services), [swift-service-lifecycle](https://github.com/swift-server/swift-service-lifecycle) | 28 | 29 | ## Introduction 30 | 31 | Hummingbird is a HTTP server framework. It consists of a core HTTP server, web application framework with a router and middleware and various modules providing additional functionality such as authentication, websockets, and integration with Redis and Fluent. 32 | 33 | ## Motivation 34 | 35 | HTTP servers come in many forms, they can be simple HTTP1 servers with a few routes, HTTP proxies, file servers to complex servers supporting a number of protocols and talking to many other services. 36 | 37 | Providing a solution that covers all of these cases is desirable. But when you build your simple HTTP1 server do you want to be including code for a bunch of protocols and services you are never going to use? At the same time you want these available should you ever need them. 38 | 39 | ## Proposed solution 40 | 41 | Hummingbird is a lightweight, modular, flexible HTTP server framework. It provides a server framework to build lightweight servers but can be extended to support as complex a server as you require. Don't want TLS, HTTP2, WebSockets don't include them. But they are available if you require them. 42 | 43 | ## Detailed design 44 | 45 | Hummingbird v2.0 is currently under development and this is what we are proposing. Version 2.0 brings a complete Swift concurrency based solution based off SwiftNIO's `NIOAsyncChannel` bringing all the benefits of structured concurrency including task cancellation, task locals and local reasoning. It also uses the [new HTTP types](https://github.com/apple/swift-http-types) that Apple recently published and integrates with the recently released structured concurrency based version of [ServiceLifecycle](https://github.com/swift-server/swift-service-lifecycle). 46 | 47 | The Hummingbird web application framework is broken into three main components. The router `Router`, the server `Server` and the application framework `Application` which provides the glue between the server and the router. A simple Hummingbird application involves setting up a router and building an application from that router. 48 | 49 | ```swift 50 | import Hummingbird 51 | 52 | // construct router with one route that returns the string "Hello" in 53 | // its response 54 | let router = Router() 55 | router.get{"hello"} { request, context in 56 | "Hello" 57 | } 58 | // construct application which 59 | let application = Application( 60 | router: router, 61 | configuration: .init(address: .hostname("127.0.0.1", port: 8080)) 62 | ) 63 | try await application.runService() 64 | ``` 65 | 66 | ### Service Lifecycle 67 | 68 | `Application` conforms to the Swift Service Lifecycle protocol `Service` so can be included in the list of services controlled by a `ServiceGroup`. In actual fact `Application.runService` is a shortcut to setting up a `ServiceGroup` with just `Hummingbird` and running it. 69 | 70 | ### Router 71 | 72 | The router has functions to setup routes using the most common HTTP verbs 73 | 74 | ```swift 75 | router.put("todo") { _,_ in } 76 | router.post("login") { _,_ in } 77 | router.on("test", .connect) {_,_ in } 78 | ... 79 | ``` 80 | 81 | Parameters can be extracted from the URI path when matching routes. 82 | 83 | ```swift 84 | router.get("todo/{id}") { request, context in 85 | let id = try context.parameters.require("id", as: Int.self) 86 | return try await getTodo(id) 87 | } 88 | ``` 89 | 90 | By default the request body is a stream of NIO `ByteBuffers`. 91 | ```swift 92 | router.post("upload") { request,_ -> HTTPResponse.Status in 93 | for try await buffer in request.body { 94 | processBuffer(buffer) 95 | } 96 | return .ok 97 | } 98 | ``` 99 | If you need your request body to be collated into one ByteBuffer you can use `request.body.collect(upTo:)`. 100 | 101 | ### Request decoding, response encoding 102 | 103 | Any type that conforms to `ResponseGenerator` can be returned from a route. These include `String`, `ByteBuffer` and `HTTPResponse.Status`. Types that conform to `ResponseEncodable` automatically conform to `ResponseGenerator` and will use Codable to generate a response. 104 | 105 | ```swift 106 | struct User: ResponseEncodable { 107 | let name: String 108 | } 109 | router.get("user/{id}") { request, context in 110 | let id = try context.parameters.require("id", as: Int.self) 111 | let user = try await getUser(id) 112 | return User(name: user.name) 113 | } 114 | ``` 115 | 116 | Similarly any type that conforms to `Decodable` can be extracted from the request body. 117 | 118 | ```swift 119 | struct SignUpRequest: Decodable { 120 | let name: String 121 | } 122 | router.put("user") { request, context -> HTTPResponse.Status in 123 | let user = try await request.decode(as: SignUpRequest.self, using: context) 124 | try await createUser(user) 125 | return .created 126 | } 127 | ``` 128 | 129 | ### Middleware 130 | 131 | You can add middleware to the router that is run on all routes. The framework has a number of middleware already implemented 132 | - `LogRequestMiddleware`: Logging of requests 133 | - `MetricsMiddleware`: Provide metrics for each request 134 | - `TracingMiddleware`: Create distributed tracing spans for each request. 135 | - `FileMiddleware`: Serve static files 136 | - `CORSMiddleware`: Implementing Cross-Origin Resource Sharing (CORS) headers 137 | 138 | ```swift 139 | router.middlewares.add(LogRequestMiddleware(.debug)) 140 | ``` 141 | 142 | Middleware can also be applied to a group of routes instead of all routes 143 | 144 | ```swift 145 | router.group("todo") 146 | .add(middleware: MyMiddleware()) 147 | .get() { _,_ in 148 | return getTodo() 149 | } 150 | .put() { request,_ in 151 | return putTodo(request) 152 | } 153 | ``` 154 | 155 | ### Request context 156 | 157 | The route handler and middleware functions all include a second parameter. This provides contextual information that is passed along with the request type. This is a generic type and the user can set it to be any type they want as long as if conforms to the protocol `RequestContext`. The router defaults to using `BasicRequestContext` which is provided out of the box. 158 | 159 | If you create your own context type you can add additional information to it, or change its default values. For example the Hummingbird authentication framework requires that you use a custom request context that includes login information. 160 | 161 | Below is a request context that includes an additional `string`` value which can be edited in a middleware and passed on to the eventual route handler. 162 | 163 | ```swift 164 | public struct MyRequestContext: RequestContext { 165 | /// Initializer required by `RequestContext` 166 | public init(channel: Channel, logger: Logger) { 167 | self.coreContext = .init(allocator: channel.allocator, logger: logger) 168 | self.string = "" 169 | } 170 | 171 | /// member variable required by `RequestContext` 172 | public var coreContext: CoreRequestContext 173 | 174 | /// my additional data 175 | public var string: String 176 | } 177 | ``` 178 | 179 | To use this context you need to provide the type in the router initializer. 180 | 181 | ```swift 182 | let router = Router(context: MyRequestContext.self) 183 | ``` 184 | 185 | ### TLS/HTTP2 186 | 187 | When initializing an `Application` you can include a parameter to indicate the type of server you want. This defaults to `.http1()`. But servers with TLS, HTTP2 support are available and WebSocket support will be available soon. 188 | 189 | These are all added in similar ways. Import library and include `server` parameter in `Application.init` 190 | 191 | ```swift 192 | import HummingbirdHTTP2 193 | 194 | let app = Application( 195 | router: router 196 | server: .http2(tlsConfiguration: myTLSConfig) 197 | ) 198 | app.runService() 199 | ``` 200 | 201 | The TLS server type can be used to wrap any other server type. 202 | 203 | ```swift 204 | import HummingbirdTLS 205 | 206 | let app = Application( 207 | router: router 208 | server: .tls(.http1(), tlsConfiguration: myTLSConfig) 209 | ) 210 | ``` 211 | 212 | ### Additional support 213 | 214 | Hummingbird also comes along with a series of other packages. Providing an authentication framework, integration with [RediStack](https://github.com/swift-server/RediStack), WebSocket support (in development), integration with Vapor's [FluentKit](https://github.com/Vapor/fluent-kit), Mustache templating and a AWS Lambda framework. 215 | 216 | ## Maturity Justification 217 | 218 | Sandbox. While Hummingbird adheres to the minimal requirements of the [SSWG Incubation Process](https://github.com/swift-server/sswg/blob/master/process/incubation.md#minimal-requirements) the package is going through a major re-write and should be considered completely new. 219 | 220 | ## Alternatives considered 221 | 222 | Vapor: You cannot discuss server frameworks and Swift without mentioning Vapor. It is the defacto solution that most people will turn to. Vapor is a great solution and provides everything that most people will ever need but it does that in one package. Hummingbird was written as a response to this. I thought a more modular solution should also be available. 223 | -------------------------------------------------------------------------------- /proposals/0033-swift-asn1.md: -------------------------------------------------------------------------------- 1 | # Solution name 2 | 3 | * Proposal: [SSWG-0033](0033-swift-asn1.md) 4 | * Authors: [Cory Benfield](https://github.com/Lukasa), [David Nadoba](https://github.com/dnadoba) 5 | * Review Manager: Tim Condon 6 | * Status: **Implemented** 7 | * Implementation: [swift-asn1](https://github.com/apple/swift-asn1) 8 | * Forum Threads: https://forums.swift.org/t/sswg-0033-swift-asn-1/74063 9 | 10 | *During the review process, add the following fields as needed:* 11 | 12 | * Decision Notes: 13 | * Previous Revision(s): 14 | * Previous Proposal(s): 15 | 16 | ## Package Description 17 | An implementation of ASN.1 types and DER serialization. 18 | 19 | | | | 20 | |--|--| 21 | | **Package Name** | `swift-asn1` | 22 | | **Module Name** | `SwiftASN1` | 23 | | **Proposed Maturity Level** | [Sandbox](https://www.swift.org/sswg/incubation-process.html#process-diagram) | 24 | | **License** | [Apache 2.0](https://choosealicense.com/licenses/apache-2.0/) | 25 | | **Dependencies** | None | 26 | 27 | ## Introduction 28 | 29 | ASN.1, and the DER encoding scheme, is a commonly used object serialization format. ASN.1 can be used abstractly to describe essentially any kind of object. ASN.1 objects are made up of either primitive or composite (called "constructed") types. The ASN.1 object description does not define a specific encoding for these objects. Instead there are a wide range of possible ways to serialize or deserialize an ASN.1 object. This module provides an implementation of a number of ASN.1 types, as well as the DER serialization format for ASN.1. 30 | 31 | ## Motivation 32 | 33 | The most common use-cases for ASN.1 in general computing are in the cryptographic space, but there are a number of use-cases in a wide range of fields. Having a clean and safe way to serialise/deserialise for these use cases is highly desirable. 34 | 35 | ## Proposed solution 36 | 37 | The solution separates the concerns of data representation from that of encoding enabling multiple encoding methods to be supported. This solution does not parse ASN.1 documents - the user is expected to encode this information in the swift code directly by hand. 38 | 39 | There are examples of encoding and decoding both [DER](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/decodingasn1) and [PEM](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/pem) in the documentation. 40 | 41 | ## Detailed design 42 | 43 | There is a structure to represent each ASN.1 basic type - for example [ASN1Null](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/asn1null). 44 | 45 | ASN.1 documents are represented as an [ASN1node](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/asn1node). Which may contain either a collection of other nodes to form a tree structure ("constructed" type), or primative data. [ASN1Identifier](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/asn1identifier) represents an OID in the node heirarchy - which assigns meaning to the nodes within this tree struture. 46 | 47 | A set of DER functions allow parsing serialsed data. The parse function supplied takes a sequence of bytes and returns the decoded ASN1Node. Functions are given to allow parsing of structured types such and sets and sequences - these are done by calling a builder which each node in the collection. 48 | 49 | The [DER.Serialiser](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/der/serializer) structure contains an array a serialised bytes. This array is appended as nodes are serialised. This is achieved through member functions for serialising the various node types. 50 | 51 | Parsing ASN.1 description documents is not supported. The user implements their own data model. This is done by defining structures for the data and implementing [DERParsable](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/derparseable) and [DERSerializable](https://swiftpackageindex.com/apple/swift-asn1/1.1.0/documentation/swiftasn1/derserializable). When deserializing the framework checks the object identifer is as expected. 52 | 53 | ## Maturity Justification 54 | 55 | The project has been in use for a little over a year. It is used from swift-certificates and has proven robust. 56 | 57 | ## Alternatives considered 58 | 59 | A [Codable](https://developer.apple.com/documentation/swift/codable) based implementation was considered but rejected. Codable itself doesn’t have sufficient expressivity for tagging, which would require frustrating property wrappers or macro wrappers that would give a poor user experience. 60 | -------------------------------------------------------------------------------- /reviews/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swift-server/sswg/1eac5c50a530adc85fb306762f0c869b4e5447be/reviews/.gitkeep --------------------------------------------------------------------------------