In a recent Swift Server Work Group (SSWG) meeting we discussed the possibility of creating a shared High Level HTTP Server type. The aim of this post is to socialise this proposal and gather the requirements for such a type.
The primary audience for this proposed type would be a Server Side developer who wants to start with a http server and build from there. Currently the starting point for this use case is not ideal - either pulling in an entire framework to use its existing http server or getting down into SwiftNIO to understand the complexities of building an optimised and robust server.
Looking at existing http server implementations - including those exploring some of the recent Swift-concurrency bridges being developed by the SwiftNIO team - we believe there is sufficient commonality between the requirements of different use cases that a shared implementation would be possible and that this logic is sufficiently complex to warrant a shared type.
Having a well-socialised shared High Level HTTP Server type that demonstrates the best practice patterns for managing SwiftNIO's Channels should provide a more natural starting point for these use cases, allowing developers to implement their custom business logic quickly and with confidence. This is also important because this experience might be the first a developer has of the Swift Server Side ecosystem.
The goal here though is not to build an even higher web framework that comes with mechanisms such as routing, middleware (which the SSWG is tracking as a separate initiative), request/response (de)serialisation/manipulation, authorisation, error handling (outside of that related to the channel itself) or HTTP header handling such as session or cookie management. The Swift ecosystem already has solutions for these use cases.
Finally, we believe understanding the requirements of such a shared type are more important than specific implementation details at this point so that is what we are hoping to focus on in this thread.
So after all that, please share requirements that you think a shared High Level HTTP Server type should fulfil!
If you are interested in Node+Swift, I have two implementations of this, the older GCD based Noze.io and the newer NIO based Macro.swift.
The strength of Node IMO is the streaming system (which Macro only implements partially, Noze is reimplementing it more completely on top of GCD). Not sure how channels and such would fit into this.
I think @FranzBusch has covered most of what I would consider requirements for a modern HTTP server written in Swift.
In my view there are a couple of things missing though.
Support for pipeline upgrades such as WebSocket or gRPC. I don't expect these to be implemented but I do expect to be given the ability to tear down the HTTP pipeline and replace with a new pipeline based off an upgrade request. Once the HTTP server is implemented maybe we could look at a generic WebSocket server implementation, but that's another discussion.
Low level support for custom Channel initialisation which allows for custom HTTP pipelines.
This could be used to aid implementation of the previous point.
It will allow us to modularise HTTP features. An HTTP2 upgrade feature could be placed in a separate target/library if the Channel setup can be separated from the core server.
It allows for support of custom pipelines specific to individual projects.
Other configuration settings may include
Max request payload size (although this could possibly be done at a higher level)
While I prefer an easy to use API like @FranzBusch mentioned, I have to add that for a package on this level I really don't like to see it as a primary goal. Frameworks building on top of this should be able to consume the exposed APIs provided by this server, and vend their own flavour towards their end users.
It has to lean on a standardised set of HTTP types, which are practical for frameworks to expose 1:1, or wrap into their own helpers.
Collecting the body into a big ByteBuffer is a big no, first of all.
I think that exposing incoming bodies an AsyncSequence of ByteBuffer is necessary here.
A nice to have would be providing a set of helpers for processing, for example, Chunked Transofer Encoding. Possibly in an -extras package/module.
If reasonably achievable in 1.0, I'd definitely be interested in keeping the system open to a future HTTP/3 implementation.
A huge problem with modelling this as teardown is that it prevents us from supporting protocols that don't model their upgrade this way. For example, Websocket in H2 is modelled within a H2 stream. I agree that we should find a way to support this kind of use-case, but I don't think we want to model it exactly this way.
mind elaborating why? i personally have not had a lot of success doing “streaming” things directly with sequences of ByteBuffer, this seems like something better handled with something like ByteToMessageDecoder.
In general, the new HTTPServer should expose its fundamental request handler APIs in terms of an AsyncSequence of incoming bytes. It must do this since streaming is a fundamental thing in the various HTTP specs; however, a very common thing is to collect the whole body and decode a JSON from it. We should make this as easy as possible. In the AsyncHTTPClient our body sequences have a collect(upTo:) method which is aimed at exactly that use-case.
Franz covered most of it, I'll just call out a few specific configurations that might not be very common, but are important to support:
HTTP/2 without TLS, required in environments where a secure socket proxy is used
limiting total memory used by unconsumed request body chunks, which is related to the back-pressure requirement that Franz mentioned, but often you don't want to limit the memory consumed per-request, but rather per-server, to allow maximum use of memory without going over the memory limit
It's possible that some of the features mentioned would not be implemented in this new server package, but on top of it, which is fine, we just need to make sure that the new API doesn't prevent this level of control to be wielded by a package built on top.
That's really great, the current usability of NIO is too low, I need to learn more deeply in order to apply it well to my projects.Currently, like swift-server/async-http-client, SwiftNIO , I need to pay special attention to eventLoopGroupProvider , which sometimes confuses me, why can't it be as convenient as URLSession ?
a singleton URLSession instance at URLSession.shared which uses the above two
Libraries like SwiftNIO aren't/shouldn't normally in the business of creating globally singletons because that can be done in a higher-level library. Vapor is a great example, their Application just creates the EventLoopGroup and hands it to the HTTP client without the user noticing. Perfect.
But as things panned out in the Swift on Server ecosystem, there isn't really another library on top of SwiftNIO that everything shares that could host these singletons. That's why SwiftNIO has literally yesterday taken the step to actually host a singleton EventLoopGroup for everybody: https://github.com/apple/swift-nio/pull/2471 . Having global singletons isn't great but confusing the community even less so.