over the weekend, i ported the swiftinit server to the new async interfaces, and as a result i’ve gained a lot of real-world experience using these APIs in production, which i’m now writing up as a case study in the hopes it will inform future work on this library.
since it doesn’t fit neatly into a category, i also want to say the new APIs are a joy to use, and despite some early bumps in the road, my experience with them has been overwhelmingly positive.
no performance regression
one major concern i had before adopting the new interfaces was that moving logic out of channel handlers and into the async/await
world would cause a performance regression.
this concern was unfounded. as you can see from this CPU plot of the swiftinit production server, there has been no noticeable performance regression after deploying the update, which is demarcated by the discontinuity in the graph.
if you operate a NIO server that currently uses channel handlers, i strongly recommend porting it to use the new async API.
improved security
by itself the new API doesn’t introduce any new security features. but at a higher level, there are always a lot of good security ideas that are hard to implement with state machines, and so just weren’t practical to add to a channel handler-based server implementation.
the async
/await
API is a lot better for security, because you can easily enforce security procedures in an imperative manner using NIOAsyncChannelInboundStream.AsyncIterator
, you don’t end up shipping insecure servers because good ideas are backlogged on implementation.
more atomics
the natural pattern for synchronization in channel handlers is to schedule things on event loops and assume the mutable state of the channel handler is protected that way, like an almost-actor.
i find that the new API is unintentionally pushing me away from scheduling tasks, and pushing me towards synchronizing things using atomics. this is because the legacy APIs had a lot of grandfathered @preconcurrency
, @unchecked Sendable
, etc., and the analogous modern constructs, like actors, make it a lot more obvious how often you are suspending.
i don’t know yet if this is a good thing or a bad thing.
HTTP/1.1 is actively harmful
unlike the old interface, the new interface has separate paths for HTTP/1.1 and HTTP/2 connections. even though common abstractions are available for the new API, they weren’t released until a couple days after the release of the core feature set in swift-nio and swift-nio-http2, so a serendipitous consequence of this was we had an opportunity to collect a lot of data over the weekend about what protocols swiftinit users are connecting with.
barbies
a barbie is a human user or a well-cloaked robot that doesn’t display immediately-detectable signs of automation. barbie is a term i invented to make it easier to understand the site’s usage patterns. the term is useful because many barbies aren’t actually humans and do display telltale signs of automation (such as requesting the same page at the same time each day), but nevertheless make it through swiftinit’s initial filters.
barbies often browse with caching enabled, and rarely encounter errors.
barbies request the site in a variety of languages, and almost always use HTTP/2. this is plausible, because many barbies are humans, and humans upgrade their browsers aggressively compared to robots.
bratz
bratz are cloaked robots that impersonate human users but exhibit behaviors that get instantly flagged by swiftinit’s filters. most commercial analytics software (e.g. Cloudfront) classifies them as human, but there are services¹ that specialize in detecting them.
bratz often exhibit malicious behaviors, and usually appear in waves when the site is being DoS’d. they frequently encounter errors because they probe the site for endpoints that don’t exist, or request pages deep in documentation bundles that were delisted months ago and couldn’t possibly be discovered by a client that doesn’t maintain a database of historical URLs.
bratz rarely use caching, and almost always request the site in english.
in the roughly 48 hours since we deployed the update, only a handful of bratz used HTTP/2.
search engines
search engines are benign crawlers like googlebot or bingbot, or one of the dozens of minor search engine crawlers. the category also includes malicious scripts that impersonate these crawlers in order to exploit a site’s whitelist.
from our data, about half of these crawlers are using HTTP/2. but i suspect that a lot of the ones using HTTP/1.1 are not real search engine crawlers. according to Cloudflare, googlebot and friends are perfectly capable of using HTTP/2, and my suspicion is that googlebot only falls back on HTTP/1.1 when a site does not support HTTP/2 or newer.
this is an area that requires more investigation.
research bots
research bots are crawlers like ahrefs that do not declare any relationship to a search engine, but also do not try to impersonate anyone else. research bots are considered harmful (but not malicious), and many sites such as wikipedia attempt to blacklist them in robots.txt
.
research bots are an enormous drain on the swiftinit server, both in terms of CPU and bandwidth.
research bots overwhelmingly use HTTP/1.1.
implications for SwiftNIO
a lot of work in SwiftNIO has been devoted to trying to dual-boot HTTP/1.1 and HTTP/2 on the same server. this effort has both a human cost - from having to design common abstractions - and a machine cost, from cycles needed to translate HTTP/2 requests into a common format.
in my opinion this effort is misplaced, and at swiftinit, we are seriously considering deprecating HTTP/1.1 entirely and making the site HTTP/2-only. at worst HTTP/1.1 is a security hazard, and at best it is a hilarious waste of resources since it facilitates spam and automated scraping.
EDIT: see update below
[1] not an ad! i don’t work for them.