[swift-server-dev] HTTP API v0.1.0
Jack Lawrence
jackl at apple.com
Wed Oct 4 17:33:18 CDT 2017
I disagree that we should hold public API to a lower standard when we expect the number of clients of the API to be small.
In particular, this API is the foundation for server-side Swift. It is especially important for this API to be simple, clear, and unambiguous. Confusing API leads to bugs which leads to security vulnerabilities.
API contract enforcement
One major concern I have with the API as it stands is that it’s too easy to write incorrect code. Whenever possible, the API contract should be defined in the type system rather than implicitly (in documentation or through runtime errors). In this case, the write* functions must be called in a particular order. For example, if you write body data before header data, or write header data then body data then some more header data, the header data is silently dropped—the implementation of writeHeaders just early returns without any indication that you did something wrong. Surprisingly, the completion handler isn’t invoked either which could lead to memory that’s never cleaned up or file descriptors that are left open.
This is the equivalent of Objective-C’s runtime behavior when a method is called on nil. It leads to difficult to find bugs and vulnerabilities, and it’s why Swift defines away the problem using the type system and runtime assertions.
When you encounter an API design issue like this, there are two general approaches:
A) Runtime reporting of programmer error, via error handling or some other mechanism.
B) API design that codifies the pattern.
In this case, I would strongly advocate for an API design that codifies the pattern in the type system. I’m not sure exactly what the right answer is here because I’m not super familiar with server-side development, but fundamentally I think the framework should *ask* for data rather than being directed to write it. I could imagine a streaming API or a set of callbacks that are called in the correct order with a mechanism to e.g. tell the framework that you’re “done” providing header or body data. I disagree that this should be left up to a higher level API.
Patterns
The pattern used to write data leads to code that’s difficult to read. In particular, there seem to be two ways of writing data—using the writeBody function and/or returning a closure using HTTPBodyProcessing. Having two ways to do the same thing but one has extra information available to it leads to code that’s hard to read (data writing/execution is linear, and then suddenly there’s code that’s executed later, one or more times). It also makes the code harder to refactor. It’s also not clear what the difference is between finishedProcessing(), stop = true, and response.done(). Why do I need to call any of those?
Patterns that encode control flow in the API lead to hard to debug errors and complicated implicit control flow. For example, what happens if I call done() or abort(), but then write some body data? What happens if I forget to call finishProcessing?What if I want to throw an error? Generators, iterators, and streams again provide good examples of using the language’s control flow rather than creating your own stateful system.
Extensibility/Flexibility
It’s not clear to me how one actually replaces one or more of the built-in types with their own that conform to the protocols. Can you provide an example or explain how you expect the protocols to be used? Why are all the protocols class-constrained?
Rather than take closures directly, could the API instead use protocols with requirements that match the closure signature? APIs that directly ask for closures tend to encourage large functions and large types, whereas protocol-based APIs encourage small, isolated types. For example, HTTPServer.start could take an object that defines a handler function, rather than taking the handler directly (this is especially important for `handler`, which could contain a lot of logic). Another example is HTTPBodyProcessing, which takes a processing function rather than a type that knows how to process.
Jack
> On Oct 3, 2017, at 12:59 PM, Helge Heß via swift-server-dev <swift-server-dev at swift.org> wrote:
>
> On 3. Oct 2017, at 19:55, Vladimir.S via swift-server-dev <swift-server-dev at swift.org> wrote:
>> Thank you Chris and others for all of this job, I believe HTTP API is very important for Swift future.
> ...
>> For example, the 'echo' server written in Node.js I googled fast:
>
> Googling fast is not always Googling well ;-)
>
> ...
>> request.on('data',function(message){
>> response.write(message);
>> });
>
> (Note that this is not the suggested Node.js style anymore either. To support back pressure, you are supposed to use readable events. This is a good intro on the relevant stuff: https://github.com/substack/stream-handbook)
>
>
> Anyways, I think this effort is not about streams at all, it is about providing a basic HTTP server library, not a complete framework like Node.js.
> The intention is that you would build an actual developer-facing framework on top of that. And while I somewhat dislike the proposed API, I think it is good enough as a basis to layer another API on top of it ;-)
>
>
> On 3. Oct 2017, at 21:20, Georgios Moschovitis via swift-server-dev <swift-server-dev at swift.org> wrote:
>> Indeed, I also find the HTMLBodyProcessing interface peculiar.
>> A ‘stream’ based interface like NodeJS looks more familiar to me.
>> Maybe we should revisit the API after the outcome of the concurrency/async-await/reactive-streams
>> design process?
>
> I think that the interface is weird is not necessarily a blocker, as long as you can build an async stream based interface on top of it, and that seems to be a given.
>
> The concurrency/async-await may make stuff nicer, but it wouldn’t change the actual implementation?
> What I read about `reactive-streams` may not be (very well) suitable for I/O. To me it sounds a little like Node streams v1 vs v3. Do you have a link explaining this stuff? I’m interested in learning more about this (is this a Swift 5 proposal or something?)
>
>
> hh
>
> P.S.: If you want Node.js like (v3) streams, you may consider having a look at my Noze.io project (http://noze.io). It is an attempt to reimplement the Node streaming features in Swift, while preserving type safety. Well, and enhance it to streams of arbitrary objects (Node also supports that, kinda, but you loose all the batch processing/buffering features, making it kinda non-sensical)
>
> P.S.2.: I didn’t look through it, but the demo implementation looks very weird to me. It seems to use DispatchQueues as if they are threads, and in combination w/ that, locks?! I’m probably unfair, I just had a cursory look :->
>
> _______________________________________________
> swift-server-dev mailing list
> swift-server-dev at swift.org
> https://lists.swift.org/mailman/listinfo/swift-server-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-server-dev/attachments/20171004/e334dc2c/attachment.html>
More information about the swift-server-dev
mailing list