SwiftNIO is a cross-platform asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
It's likeNetty,but written for Swift.
The SwiftNIO project is split across multiple repositories:
Repository | NIO 2 (Swift 5.7+) |
---|---|
https://github.com/apple/swift-nio SwiftNIO core |
from: "2.0.0" |
https://github.com/apple/swift-nio-ssl TLS (SSL) support |
from: "2.0.0" |
https://github.com/apple/swift-nio-http2 HTTP/2 support |
from: "1.0.0" |
https://github.com/apple/swift-nio-extras useful additions around SwiftNIO |
from: "1.0.0" |
https://github.com/apple/swift-nio-transport-services first-class support for macOS, iOS, tvOS, and watchOS |
from: "1.0.0" |
https://github.com/apple/swift-nio-ssh SSH support |
.upToNextMinor(from: "0.2.0" ) |
NIO 2.29.0 and older support Swift 5.0+, NIO 2.39.0 and older support Swift 5.2+.
Within this repository we have a number of products that provide different functionality. This package contains the following products:
NIO
.This is an umbrella module exportingNIOCore
,NIOEmbedded
andNIOPosix
.NIOCore
.This provides the core abstractions and types for using SwiftNIO (see"Conceptual Overview"for more details). Most NIO extension projects that provide things like newEventLoop
sandChannel
sor new protocol implementations should only need to depend onNIOCore
.NIOPosix
.This provides the primary [EventLoopGroup
],EventLoop
,andChannel
sfor use on POSIX-based systems. This is our high performance core I/O layer. In general, this should only be imported by projects that plan to do some actual I/O, such as high-level protocol implementations or applications.NIOEmbedded
.This providesEmbeddedChannel
andEmbeddedEventLoop
,implementations of theNIOCore
abstractions that provide fine-grained control over their execution. These are most often used for testing, but can also be used to drive protocol implementations in a way that is decoupled from networking altogether.NIOConcurrencyHelpers
.This provides a few low-level concurrency primitives that are used by NIO implementations, such as locks and atomics.NIOFoundationCompat
.This extends a number of NIO types for better interoperation with Foundation data types. If you are working with Foundation data types such asData
,you should import this.NIOTLS
.This provides a few common abstraction types for working with multiple TLS implementations. Note that this doesn't provide TLS itself: please investigateswift-nio-sslandswift-nio-transport-servicesfor concrete implementations.NIOHTTP1
.This provides a low-level HTTP/1.1 protocol implementation.NIOWebSocket
.This provides a low-level WebSocket protocol implementation.NIOTestUtils
.This provides a number of helpers for testing projects that use SwiftNIO.NIOFileSystem
.This providesasync
APIs for interacting with the file system.
Below you can find a list of a few protocol implementations that are done with SwiftNIO. This is a non-exhaustive list of protocols that are either part of the SwiftNIO project or are accepted into theSSWG's incubation process. All of the libraries listed below do all of their I/O in a non-blocking fashion using SwiftNIO.
Low-level protocol implementations are often a collection ofChannelHandler
s that implement a protocol but still require the user to have a good understanding of SwiftNIO. Often, low-level protocol implementations will then be wrapped in high-level libraries with a nicer, more user-friendly API.
Protocol | Client (Sends requests) |
Server (Responds to requests) |
Repository | Module | Comment |
---|---|---|---|---|---|
HTTP/1 | ✅ | ✅ | apple/swift-nio | NIOHTTP1 |
official NIO project |
HTTP/2 | ✅ | ✅ | apple/swift-nio-http2 | NIOHTTP2 |
official NIO project |
WebSocket | ✅ | ✅ | apple/swift-nio | NIOWebSocket |
official NIO project |
TLS | ✅ | ✅ | apple/swift-nio-ssl | NIOSSL |
official NIO project |
SSH | ✅ | ✅ | apple/swift-nio-ssh | NIOSSH |
official NIO project |
High-level implementations are usually libraries that come with an API that doesn't expose SwiftNIO'sChannelPipeline
and can therefore be used with very little (or no) SwiftNIO-specific knowledge. The implementations listed below do still do all of their I/O in SwiftNIO and integrate really well with the SwiftNIO ecosystem.
Protocol | Client (Sends requests) |
Server (Responds to requests) |
Repository | Module | Comment |
---|---|---|---|---|---|
HTTP | ✅ | ❌ | swift-server/async-http-client | AsyncHTTPClient |
SSWG community project |
gRPC | ✅ | ✅ | grpc/grpc-swift | GRPC |
also offers a low-level API; SSWG community project |
APNS | ✅ | ❌ | swift-server-community/APNSwift | APNSwift |
SSWG community project |
PostgreSQL | ✅ | ❌ | vapor/postgres-nio | PostgresNIO |
SSWG community project |
Redis | ✅ | ❌ | swift-server/RediStack | RediStack |
SSWG community project |
This is the current version of SwiftNIO and will be supported for the foreseeable future.
We commit to support the most recently released swift version (currently 5.10) and the last two minor releases before that unless this is impossible to do in one codebase. In addition checks are run against the latest beta release (if any) as well as the nightly swift builds and the intent is that these should pass.
The most recent versions of SwiftNIO support Swift 5.8 and newer. The minimum Swift version supported by SwiftNIO releases are detailed below:
SwiftNIO | Minimum Swift Version |
---|---|
2.0.0..< 2.30.0 |
5.0 |
2.30.0..< 2.40.0 |
5.2 |
2.40.0..< 2.43.0 |
5.4 |
2.43.0..< 2.51.0 |
5.5.2 |
2.51.0..< 2.60.0 |
5.6 |
2.60.0..< 2.65.0 |
5.7 |
2.65.0... |
5.8 |
SwiftNIO 1 is considered end of life - it is strongly recommended that you move to a newer version. The Core NIO team does not actively work on this version. No new features will be added to this version but PRs which fix bugs or security vulnerabilities will be accepted until the end of May 2022.
If you have a SwiftNIO 1 application or library that you would like to migrate to SwiftNIO 2, please check out themigration guidewe prepared for you.
The latest released SwiftNIO 1 version supports Swift 4.0, 4.1, 4.2, and 5.0.
SwiftNIO aims to support all of the platforms where Swift is supported. Currently, it is developed and tested on macOS and Linux, and is known to support the following operating system versions:
- Ubuntu 18.04+
- macOS 10.9+, iOS 7+; (macOS 10.14+, iOS 12+, tvOS 12+ or watchOS 6+ withswift-nio-transport-services)
SwiftNIO followsSemVer 2.0.0with a separate document declaringSwiftNIO's Public API.
What this means for you is that you should depend on SwiftNIO with a version range that covers everything from the minimum SwiftNIO version you require up to the next major version.
In SwiftPM that can be easily done specifying for examplefrom: "2.0.0"
meaning that you support SwiftNIO in every version starting from 2.0.0 up to (excluding) 3.0.0.
SemVer and SwiftNIO's Public API guarantees should result in a working program without having to worry about testing every single version for compatibility.
SwiftNIO is fundamentally a low-level tool for building high-performance networking applications in Swift. It particularly targets those use-cases where using a "thread-per-connection" model of concurrency is inefficient or untenable. This is a common limitation when building servers that use a large number of relatively low-utilization connections, such as HTTP servers.
To achieve its goals SwiftNIO extensively uses "non-blocking I/O": hence the name! Non-blocking I/O differs from the more common blocking I/O model because the application does not wait for data to be sent to or received from the network: instead, SwiftNIO asks for the kernel to notify it when I/O operations can be performed without waiting.
SwiftNIO does not aim to provide high-level solutions like, for example, web frameworks do. Instead, SwiftNIO is focused on providing the low-level building blocks for these higher-level applications. When it comes to building a web application, most users will not want to use SwiftNIO directly: instead, they'll want to use one of the many great web frameworks available in the Swift ecosystem. Those web frameworks, however, may choose to use SwiftNIO under the covers to provide their networking support.
The following sections will describe the low-level tools that SwiftNIO provides, and provide a quick overview of how to work with them. If you feel comfortable with these concepts, then you can skip right ahead to the other sections of this README.
The basic building blocks of SwiftNIO are the following 8 types of objects:
EventLoopGroup
,a protocol, provided byNIOCore
.EventLoop
,a protocol, provided byNIOCore
.Channel
,a protocol, provided byNIOCore
.ChannelHandler
,a protocol, provided byNIOCore
.Bootstrap
,several related structures, provided byNIOCore
.ByteBuffer
,a struct, provided byNIOCore
.EventLoopFuture
,a generic class, provided byNIOCore
.EventLoopPromise
,a generic struct, provided byNIOCore
.
All SwiftNIO applications are ultimately constructed of these various components.
The basic I/O primitive of SwiftNIO is the event loop. The event loop is an object that waits for events (usually I/O related events, such as "data received" ) to happen and then fires some kind of callback when they do. In almost all SwiftNIO applications there will be relatively few event loops: usually only one or two per CPU core the application wants to use. Generally speaking event loops run for the entire lifetime of your application, spinning in an endless loop dispatching events.
Event loops are gathered together into event loopgroups.These groups provide a mechanism to distribute work around the event loops. For example, when listening for inbound connections the listening socket will be registered on one event loop. However, we don't want all connections that are accepted on that listening socket to be registered with the same event loop, as that would potentially overload one event loop while leaving the others empty. For that reason, the event loop group provides the ability to spread load across multiple event loops.
In SwiftNIO today there is oneEventLoopGroup
implementation, and twoEventLoop
implementations. For production applications there is theMultiThreadedEventLoopGroup
,anEventLoopGroup
that creates a number of threads (using the POSIXpthreads
library) and places oneSelectableEventLoop
on each one. TheSelectableEventLoop
is an event loop that uses a selector (eitherkqueue
orepoll
depending on the target system) to manage I/O events from file descriptors and to dispatch work. TheseEventLoop
sandEventLoopGroup
sare provided by theNIOPosix
module. Additionally, there is theEmbeddedEventLoop
,which is a dummy event loop that is used primarily for testing purposes, provided by theNIOEmbedded
module.
EventLoop
s have a number of important properties. Most vitally, they are the way all work gets done in SwiftNIO applications. In order to ensure thread-safety, any work that wants to be done on almost any of the other objects in SwiftNIO must be dispatched via anEventLoop
.EventLoop
objects own almost all the other objects in a SwiftNIO application, and understanding their execution model is critical for building high-performance SwiftNIO applications.
WhileEventLoop
s are critical to the way SwiftNIO works, most users will not interact with them substantially beyond asking them to createEventLoopPromise
s and to schedule work. The parts of a SwiftNIO application most users will spend the most time interacting with areChannel
s andChannelHandler
s.
Almost every file descriptor that a user interacts with in a SwiftNIO program is associated with a singleChannel
.TheChannel
owns this file descriptor, and is responsible for managing its lifetime. It is also responsible for processing inbound and outbound events on that file descriptor: whenever the event loop has an event that corresponds to a file descriptor, it will notify theChannel
that owns that file descriptor.
Channel
s by themselves, however, are not useful. After all, it is a rare application that doesn't want to do anything with the data it sends or receives on a socket! So the other important part of theChannel
is theChannelPipeline
.
AChannelPipeline
is a sequence of objects, calledChannelHandler
s, that process events on aChannel
.TheChannelHandler
s process these events one after another, in order, mutating and transforming events as they go. This can be thought of as a data processing pipeline; hence the nameChannelPipeline
.
AllChannelHandler
s are either Inbound or Outbound handlers, or both. Inbound handlers process "inbound" events: events like reading data from a socket, reading socket close, or other kinds of events initiated by remote peers. Outbound handlers process "outbound" events, such as writes, connection attempts, and local socket closes.
Each handler processes the events in order. For example, read events are passed from the front of the pipeline to the back, one handler at a time, while write events are passed from the back of the pipeline to the front. Each handler may, at any time, generate either inbound or outbound events that will be sent to the next handler in whichever direction is appropriate. This allows handlers to split up reads, coalesce writes, delay connection attempts, and generally perform arbitrary transformations of events.
In general,ChannelHandler
s are designed to be highly re-usable components. This means they tend to be designed to be as small as possible, performing one specific data transformation. This allows handlers to be composed together in novel and flexible ways, which helps with code reuse and encapsulation.
ChannelHandler
s are able to keep track of where they are in aChannelPipeline
by using aChannelHandlerContext
.These objects contain references to the previous and next channel handler in the pipeline, ensuring that it is always possible for aChannelHandler
to emit events while it remains in a pipeline.
SwiftNIO ships with manyChannelHandler
s built in that provide useful functionality, such as HTTP parsing. In addition, high-performance applications will want to provide as much of their logic as possible inChannelHandler
s, as it helps avoid problems with context switching.
Additionally, SwiftNIO ships with a fewChannel
implementations. In particular, it ships withServerSocketChannel
,aChannel
for sockets that accept inbound connections;SocketChannel
,aChannel
for TCP connections; andDatagramChannel
,aChannel
for UDP sockets. All of these are provided by theNIOPosix
module. It also providesEmbeddedChannel
,aChannel
primarily used for testing, provided by theNIOEmbedded
module.
One of the important notes aboutChannelPipeline
s is that they are thread-safe. This is very important for writing SwiftNIO applications, as it allows you to write much simplerChannelHandler
s in the knowledge that they will not require synchronization.
However, this is achieved by dispatching all code on theChannelPipeline
on the same thread as theEventLoop
.This means that, as a general rule,ChannelHandler
smust notcall blocking code without dispatching it to a background thread. If aChannelHandler
blocks for any reason, allChannel
s attached to the parentEventLoop
will be unable to progress until the blocking call completes.
This is a common concern while writing SwiftNIO applications. If it is useful to write code in a blocking style, it is highly recommended that you dispatch work to a different thread when you're done with it in your pipeline.
While it is possible to configure and registerChannel
s withEventLoop
s directly, it is generally more useful to have a higher-level abstraction to handle this work.
For this reason, SwiftNIO ships a number ofBootstrap
objects whose purpose is to streamline the creation of channels. SomeBootstrap
objects also provide other functionality, such as support for Happy Eyeballs for making TCP connection attempts.
Currently SwiftNIO ships with threeBootstrap
objects in theNIOPosix
module:ServerBootstrap
,for bootstrapping listening channels;ClientBootstrap
,for bootstrapping client TCP channels; andDatagramBootstrap
for bootstrapping UDP channels.
The majority of the work in a SwiftNIO application involves shuffling buffers of bytes around. At the very least, data is sent and received to and from the network in the form of buffers of bytes. For this reason it's very important to have a high-performance data structure that is optimized for the kind of work SwiftNIO applications perform.
For this reason, SwiftNIO providesByteBuffer
,a fast copy-on-write byte buffer that forms a key building block of most SwiftNIO applications. This type is provided by theNIOCore
module.
ByteBuffer
provides a number of useful features, and in addition provides a number of hooks to use it in an "unsafe" mode. This turns off bounds checking for improved performance, at the cost of potentially opening your application up to memory correctness problems.
In general, it is highly recommended that you use theByteBuffer
in its safe mode at all times.
For more details on the API ofByteBuffer
,please see our API documentation, linked below.
One major difference between writing concurrent code and writing synchronous code is that not all actions will complete immediately. For example, when you write data on a channel, it is possible that the event loop will not be able to immediately flush that write out to the network. For this reason, SwiftNIO providesEventLoopPromise<T>
andEventLoopFuture<T>
to manage operations that completeasynchronously.These types are provided by theNIOCore
module.
AnEventLoopFuture<T>
is essentially a container for the return value of a function that will be populatedat some time in the future.EachEventLoopFuture<T>
has a correspondingEventLoopPromise<T>
,which is the object that the result will be put into. When the promise is succeeded, the future will be fulfilled.
If you had to poll the future to detect when it completed that would be quite inefficient, soEventLoopFuture<T>
is designed to have managed callbacks. Essentially, you can chain callbacks off the future that will be executed when a result is available. TheEventLoopFuture<T>
will even carefully arrange the scheduling to ensure that these callbacks always execute on the event loop that initially created the promise, which helps ensure that you don't need too much synchronization aroundEventLoopFuture<T>
callbacks.
Another important topic for consideration is the difference between how the promise passed toclose
works as opposed tocloseFuture
on aChannel
.For example, the promise passed intoclose
will succeed after theChannel
is closed down but before theChannelPipeline
is completely cleared out. This will allow you to take action on theChannelPipeline
before it is completely cleared out, if needed. If it is desired to wait for theChannel
to close down and theChannelPipeline
to be cleared out without any further action, then the better option would be to wait for thecloseFuture
to succeed.
There are several functions for applying callbacks toEventLoopFuture<T>
,depending on how and when you want them to execute. Details of these functions is left to the API documentation.
SwiftNIO is designed to be a powerful tool for building networked applications and frameworks, but it is not intended to be the perfect solution for all levels of abstraction. SwiftNIO is tightly focused on providing the basic I/O primitives and protocol implementations at low levels of abstraction, leaving more expressive but slower abstractions to the wider community to build. The intention is that SwiftNIO will be a building block for server-side applications, not necessarily the framework those applications will use directly.
Applications that need extremely high performance from their networking stack may choose to use SwiftNIO directly in order to reduce the overhead of their abstractions. These applications should be able to maintain extremely high performance with relatively little maintenance cost. SwiftNIO also focuses on providing useful abstractions for this use-case, such that extremely high performance network servers can be built directly.
The core SwiftNIO repository will contain a few extremely important protocol implementations, such as HTTP, directly in tree. However, we believe that most protocol implementations should be decoupled from the release cycle of the underlying networking stack, as the release cadence is likely to be very different (either much faster or much slower). For this reason, we actively encourage the community to develop and maintain their protocol implementations out-of-tree. Indeed, some first-party SwiftNIO protocol implementations, including our TLS and HTTP/2 bindings, are developed out-of-tree!
There are currently several example projects that demonstrate how to use SwiftNIO.
- chat clienthttps://github.com/apple/swift-nio/tree/main/Sources/NIOChatClient
- chat serverhttps://github.com/apple/swift-nio/tree/main/Sources/NIOChatServer
- echo clienthttps://github.com/apple/swift-nio/tree/main/Sources/NIOEchoClient
- echo serverhttps://github.com/apple/swift-nio/tree/main/Sources/NIOEchoServer
- UDP echo clienthttps://github.com/apple/swift-nio/tree/main/Sources/NIOUDPEchoClient
- UDP echo serverhttps://github.com/apple/swift-nio/tree/main/Sources/NIOUDPEchoServer
- HTTP clienthttps://github.com/apple/swift-nio/tree/main/Sources/NIOHTTP1Client
- HTTP serverhttps://github.com/apple/swift-nio/tree/main/Sources/NIOHTTP1Server
- WebSocket clienthttps://github.com/apple/swift-nio/tree/main/Sources/NIOWebSocketClient
- WebSocket serverhttps://github.com/apple/swift-nio/tree/main/Sources/NIOWebSocketServer
To build & run them, run following command, replace TARGET_NAME with the folder name under./Sources
swift run TARGET_NAME
For example, to run NIOHTTP1Server, run following command:
swift run NIOHTTP1Server
SwiftNIO primarily usesSwiftPMas its build tool, so we recommend using that as well. If you want to depend on SwiftNIO in your own project, it's as simple as adding adependencies
clause to yourPackage.swift
:
dependencies:[
.package(url:"https://github.com/apple/swift-nio.git",from:"2.0.0")
]
and then adding the appropriate SwiftNIO module(s) to your target dependencies.
The syntax for adding target dependencies differs slightly between Swift
versions. For example, if you want to depend on theNIOCore
,NIOPosix
and
NIOHTTP1
modules, specify the following dependencies:
dependencies: [.product(name: "NIOCore", package: "swift-nio" ),
.product(name: "NIOPosix", package: "swift-nio" ),
.product(name: "NIOHTTP1", package: "swift-nio" )]
If your project is set up as an Xcode project and you're using Xcode 11+, you can add SwiftNIO as a dependency to your
Xcode project by clicking File -> Swift Packages -> Add Package Dependency. In the upcoming dialog, please enter
https://github.com/apple/swift-nio.git
and click Next twice. Finally, select the targets you are planning to use (for
exampleNIOCore
,NIOHTTP1
,andNIOFoundationCompat
) and click finish. Now will be able toimport NIOCore
(as well as all
the other targets you have selected) in your project.
To work on SwiftNIO itself, or to investigate some of the demonstration applications, you can clone the repository directly and use SwiftPM to help build it. For example, you can run the following commands to compile and run the example echo server:
swift build
swifttest
swift run NIOEchoServer
To verify that it is working, you can use another shell to attempt to connect to it:
echo"Hello SwiftNIO"|nc localhost 9999
If all goes well, you'll see the message echoed back to you.
To work on SwiftNIO in Xcode, you can just open thePackage.swift
file in Xcode and use Xcode's support for SwiftPM Packages.
Alternatively, you may want to develop or test withdocker-compose
.
First make sure you haveDockerinstalled, next run the following commands:
-
docker-compose -f docker/docker-compose.yaml run test
Will create a base image with Swift runtime and other build and test dependencies, compile SwiftNIO and run the unit and integration tests
-
docker-compose -f docker/docker-compose.yaml up echo
Will create a base image, compile SwiftNIO, and run a sample
NIOEchoServer
onlocalhost:9999
.Test it byecho Hello SwiftNIO | nc localhost 9999
. -
docker-compose -f docker/docker-compose.yaml up http
Will create a base image, compile SwiftNIO, and run a sample
NIOHTTP1Server
onlocalhost:8888
.Test it bycurl http://localhost:8888
-
docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.57.yaml run test
Will create a base image using Ubuntu 22.04 and Swift 5.7, compile SwiftNIO and run the unit and integration tests. Files exist for other Ubuntu and swift versions in the docker directory.
Note:This section is only relevant if you would like to develop SwiftNIO yourself. You can ignore the information here if you just want to use SwiftNIO as a SwiftPM package.
For the most part, SwiftNIO development is as straightforward as any other SwiftPM project. With that said, we do have a few processes that are worth understanding before you contribute. For details, please seeCONTRIBUTING.md
in this repository.
SwiftNIO'smain
branch is the development branch for the next releases of SwiftNIO 2, it's Swift 5-only.
To be able to compile and run SwiftNIO and the integration tests, you need to have a few prerequisites installed on your system.
- Xcode 11.4 or newer, Xcode 12 recommended.
- Swift 5.7 or newer fromswift.org/download.We always recommend to use the latest released version.
- netcat (for integration tests only)
- lsof (for integration tests only)
- shasum (for integration tests only)
# install swift tarball from https://swift.org/downloads
apt-get install -y git curl libatomic1 libxml2 netcat-openbsd lsof perl
dnf install swift-lang /usr/bin/nc /usr/bin/lsof /usr/bin/shasum
Benchmarks forswift-nio
are in a separate Swift Package in theBenchmarks
subfolder of this repository.
They use thepackage-benchmark
plugin.
Benchmarks depends on thejemalloc
memory allocation library, which is used bypackage-benchmark
to capture memory allocation statistics.
An installation guide can be found in theGetting Started articleofpackage-benchmark
.
Afterwards you can run the benchmarks from CLI by going to theBenchmarks
subfolder (e.g.cd Benchmarks
) and invoking:
swift package benchmark
For more information please refer toswift package benchmark --help
or thedocumentation ofpackage-benchmark
.