# FlyingFox: a lightweight HTTP server in Swift

> FlyingFox is an MIT licensed HTTP server written with Swift concurrency, using non blocking BSD sockets and a shared socket pool that suspends a task when a connection has nothing to say, with pattern matched routes and handlers for static files, directories, proxies, redirects and WebSockets. The judgement is that the design is small enough to read in one sitting, and that the socket layer is shipped as its own package because it is the part you might want without the server.

**swhitty/FlyingFox** — Lightweight, HTTP server written in Swift using async/await.

- Repository: https://github.com/swhitty/FlyingFox
- Stars: 691 · Forks: 64
- Language: Swift
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/swhitty-flyingfox

## An HTTP server built on Swift concurrency

FlyingFox is a lightweight HTTP server built using Swift concurrency, and the readme describes the mechanism in the second paragraph.

The server uses non blocking BSD sockets, and it handles each connection in a concurrent child Task. When a socket is blocked with no data, the tasks are suspended using a shared pool of sockets, which is the AsyncSocketPool, and that is the piece that makes the server cheap under load. A thousand idle connections cost a thousand suspended tasks rather than a thousand threads.

That design is why it runs on the platforms it does. The readme says it requires Swift 5.10 on Xcode 15.4 or newer, and that it runs on iOS 13 and newer, tvOS 13 and newer, watchOS 8 and newer, macOS 10.15 and newer, and Linux, with Android and Windows 10 support marked as experimental.

The size of the thing is the point. There is no framework, no middleware stack and no template engine, just a server, a routing mechanism and a set of handlers you can read.

The project is MIT licensed, its primary language is Swift, and the release history shows the 0.26 and 0.27 lines, with 0.27.0 in July 2026 bringing Swift 6, 0.27.1 shortly after adding an HTTP client, and 0.26.2 in January with a change to the socket address handling. The last push was on 2026-10-17, and the badges cover the build, the coverage, the platform list and the Swift version.

## The socket layer is a package of its own

The repository is two packages, and the split is worth understanding because it is the reason the server is as small as it is.

There is FlyingFox, which is the HTTP server, and FlyingSocks, which is the socket layer, and the readme devotes a whole section to the latter with the socket, the async socket, the AsyncSocketPool, the SocketPool and the SocketAddress as its parts.

The distinction between the two pools is the interesting design point. The SocketPool is the plain one, the one that owns sockets and hands them out. The AsyncSocketPool is the shared pool the server uses, and its job is to suspend a task whose socket has no data and resume it when something arrives. That is the mechanism behind the non blocking behaviour described in the introduction, and it is the part of the design that a reader who has written a server in a thread based model has to think hardest about.

The SocketAddress is the other piece, and the 0.26.2 release note mentions a change involving the sockaddr storage and a withSockAddr closure, which is the sort of change that happens when a platform abstraction is being tidied up.

Shipping the socket layer separately means a project that needs a non blocking TCP client or server in Swift can take FlyingSocks without the HTTP layer, and the repository reflects that with two separate podspec files, one for FlyingFox and one for FlyingSocks.

## Routes are patterns matched against a request

The routing model is the part of the design that makes the API feel small.

An HTTPRoute is designed to be pattern matched against an HTTPRequest, allowing a request to be identified by some or all of its properties, and the readme shows the matching operator in use:

```swift
let route = HTTPRoute("/hello/world")

route ~= HTTPRequest(method: .GET, path: "/hello/world") // true
```

The match is against the request as a whole rather than against a path string and a method string separately, and the route pattern carries the method, which is why the routes in the handler examples read as a single string with a method in front of it.

The dispatch rule is simple. Incoming requests are routed to the handler of the first matching route. A handler can throw HTTPUnhandledError if, after inspecting the request, it cannot handle it, and then the next matching route is used, so a handler can decline a request it does not understand rather than guessing. Requests that do not match any handled route receive an HTTP 404.

The RoutedHTTPHandler groups several handlers together and matches them against an HTTPRoute, which is how you build a group of routes and then attach the group to a prefix on the server.

The handler protocol itself is two lines, an async function that takes a request and returns a response, and that is the entire contract you implement when you write your own handler.

## The built-in handlers cover the routes everyone needs

The handler set is the part of the library you will use most, and it is worth going through what each one does.

The FileHTTPHandler serves a single file, with the route naming the method and the file, and it returns an HTTP 404 if the file does not exist. It also supports range requests, responding with HTTP 206 Partial Content, and the readme points at the MDN documentation for range requests, because the reason to support them is efficient streaming of media content, and a large video served without range support has to be sent whole.

The DirectoryHTTPHandler serves a directory, with a sub path and a server path, so a request for a path under one prefix is looked up under another on disk, and it returns 404 for a file that is not there.

The ProxyHTTPHandler forwards a request to a base URL, so a catch-all route becomes a reverse proxy, and the readme shows the mapping with a comment.

The RedirectHTTPHandler has two forms, a static redirect to a fixed URL and a dynamic one built from a base URL, and both answer with a 301 and a Location header. There is a server path option that removes a prefix before redirecting, which is what makes a mounted route redirect to the right place on the target.

So the library ships the four routes every HTTP server ends up needing, static files, directories, a proxy and a redirect, and you write the rest.

## WebSockets are two streams exchanged

The WebSocket support is expressed in the same shape as everything else, a protocol you implement and a route you attach it to.

The handler takes a WSMessageHandler, where a pair of AsyncStream values of WSMessage are exchanged, and the protocol is:

```swift
protocol WSMessageHandler {
  func makeMessages(for client: AsyncStream<WSMessage>) async throws -> AsyncStream<WSMessage>
}

enum WSMessage {
  case text(String)
  case data(Data)
  case close(WSCloseCode)
}
```

So the client sends a stream of messages and the server returns a stream of messages, and the two run concurrently. The message type is an enum with three cases, text, data and close with a close code, which means a normal WebSocket application deals with two value types and a termination case rather than with frames.

The readme also notes that raw WebSocket frames can be provided, so the streaming interface is the convenient one and the frame level interface is there for the cases where you need to control the encoding yourself.

The route is attached the same way as the others, with a webSocket case on the handler, and the readme's example is an echo handler, which is the smallest thing that proves the plumbing works.

## Run, cancel, stop, and the iOS suspension caveat

The lifecycle of the server is four operations, and the readme is precise about the difference between them.

The server is created with a port and run with an await inside the current task, and the note says that the server runs within the current task. To stop it immediately, terminating all connections, you cancel that task. To stop it gracefully, so that the requests in flight complete before the connections are closed, there is a stop method with a timeout, and the example passes three seconds, after which the connections are closed forcefully.

Two more conveniences are there for a caller that needs to know the server is ready. There is a method that waits until the server is listening, and there is a property that returns the current listening address, which is the one to use when the port was chosen by the system.

The caveat is the one that will bite an iOS developer, and the readme gives it its own note. iOS hangs up the listening socket when an app is suspended in the background. Once the app returns to the foreground, the run method detects this and throws a SocketError, and the server then has to be started once more.

That is an honest constraint of running a listening socket inside a mobile application, and it is exactly the kind of thing a framework would hide and this library chooses to state.

## Three ways to package it, and a Linux directory of its own

The distribution story is broader than a single Swift package manager entry point.

Installation is through Swift Package Manager, with a dependency line that names the version range, and the readme repeats the requirement of Swift 5.10 on Xcode 15.4 or newer.

The repository also carries two podspec files, one for FlyingFox and one for FlyingSocks, so the packages can be consumed through CocoaPods as well, which matters for a project that is not on Swift Package Manager.

The platform coverage is the part worth noting. There is a CSystemLinux directory in the tree, which is the usual way to add the Linux specific parts of a Swift package, so the socket code has a home for the platform where the Darwin specific parts do not apply. And there is a container-run-tests.sh script at the root, so the Linux build is exercised in a container rather than only on a developer machine.

The manifest is otherwise conventional for a Swift package, with a Package.swift at the root, the codecov configuration, and the badges that report the platforms and the Swift version the package index sees. The 0.27.0 release, which is the Swift 6 one, is the line to pin if you are on a modern toolchain, and the readme's phrase about the 0.27.1 version in the dependency example tells you that the minor version matters, because the API moved under you when Swift 6 arrived.

## Conclusion

FlyingFox fits a Swift developer who needs an HTTP endpoint inside an app, a service, or a command line tool, and who would rather not add a Node or a Python process to the system, because the server is a Swift package with three dependencies on the socket layer and nothing else. It is the wrong choice if you need a production grade server with TLS termination, a framework, a router and middleware, because FlyingFox is deliberately the small layer underneath that. It is also the wrong choice if you need to run the same server on Windows, because the readme says that support is experimental. Before you adopt it, read the iOS note about the listening socket, because an app that gets suspended in the background loses the socket and has to be started again, and check the version, since the 0.27 line is the Swift 6 era and the API moved under you in that release.

## FAQ

### What is FlyingFox?

It is a lightweight HTTP server written with Swift concurrency, MIT licensed, that uses non blocking BSD sockets and handles each connection in a concurrent child task, suspending tasks on a shared socket pool when a socket has no data. It runs on iOS, tvOS, watchOS, macOS and Linux, with Android and Windows support marked as experimental.

### How do I add a route to the FlyingFox server?

With a route string and a handler, for example appendRoute for a path, optionally with a method, and the handler is either a type conforming to the HTTPHandler protocol, which takes a request and returns a response asynchronously, or a closure. Requests go to the first matching route, and a handler that throws HTTPUnhandledError lets the next matching route try.

### What handlers does FlyingFox provide?

A file handler for a single file, which supports range requests with a 206 response, a directory handler for a folder, a proxy handler that forwards to a base URL, a redirect handler that answers with a 301 and can strip a prefix before redirecting, a WebSocket handler built on a pair of message streams, and a routed handler that groups several of them.

### How do I stop the FlyingFox server?

Cancelling the task the server runs in terminates all connections immediately, while the stop method with a timeout lets the requests in flight complete first and closes the connections forcefully after the timeout. There is also a call that waits until the server is listening and a property that returns the listening address.

## Sources

- [Issues](https://github.com/swhitty/FlyingFox/issues)
- [License: MIT](https://github.com/swhitty/FlyingFox/blob/main/LICENSE)
- [README](https://github.com/swhitty/FlyingFox/blob/main/README.md)
- [Releases](https://github.com/swhitty/FlyingFox/releases)
- [swhitty/FlyingFox on GitHub](https://github.com/swhitty/FlyingFox)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/swhitty-flyingfox
