[swift-server-dev] Prototype of the discussed HTTP API Spec

Michael Chiu hatsuneyuji at icloud.com
Tue May 30 15:03:32 CDT 2017


Hi Johannes,

By synchronous/asynchronous I am referring to the natural of the API, something like

```sync
func read(fd:, to buffer:) throws 
```
```async
func read(fd:, handiler: (buffer)->R) throws -> R 
```
> Not really, what you do when you use kqueue/(e)poll/select is that only said calls are blocking and you set your file descriptors to non-blocking.

Despite kqueue is a blocking, it really only blocks when there’s nothing to do. So semantic-wise, the thread will never block as long as there’s work to do.

> if you use kqueue/(e)poll/select you get inversion of control, ie. you block only one (or a few) threads in kqueue/epoll/select and then invoke code to handle the event (which can be 'file descriptor XYZ can be read/written').
> 
> So the echo server becomes more like this (this is extreme pseudo code and ignores most of the real challenges)
> 
> func echoServer(socket: Socket) {
>    socket.whenReadable { bytes in
>        socket.whenWritable {
>            socket.write(bytes)
>        }
>    }
> }
> 

That’s not quite true, the gold of kqueue and event notification apis is that you can pick when you want to read from the socket (in fact you don’t necessarily have to set then non-block), which is in fact more like coroutine.

This also say that there’s read available, they know exactly which thread will execute on and the sequence of execution, so no external mechanism required to synchronize resources.

while im_feeling_lucky {
  if (feeling_good) {
    kevent(…)
      for ev in events_i_can_do {
        if (happy(ev)) {
          read(ev,….)
        }
      }
    }
}

In fact in kqueue you can even temporarily disable events, or simply not call kevent when the server is under pressure. 

With a Synchronous IO API, the user have 100% control on when read/write occur, on which thread it occur, how many partial bytes to read and synchronize shared resources without lock etc.

If we only provide a asynchronous API, some existing server-side framework, say Perfect, very hard to integrate with the official one.

> If you want a more realistic example check out DispatchSources and DispatchIO from Dispatch. The important bit is that if you use these eventing solutions, you'll get inversion of control and that's commonly referred to as an asynchronous API as you can't do anything in the current thread but have to do it when the eventing library tells you to. Cf. also the HTTP API that I was initially proposing and a HTTP echo server for that:

There’s timeout option on both api, and usually these api only block when there’s nothing to do.
You don’t have to handle those event if you don’t want to as well, you can always not to set EPOLLET and not handle the event, which is totally fine, and handle it next time you call kevent.

> --- SNIP ---
> serve { (req, res) in
>  if req.target == "/echo" {
>      guard req.httpVersion == (1, 1) else {
>          /* HTTP/1.0 doesn't support chunked encoding */
>          res.writeResponse(HTTPResponse(version: req.version,
>                                         status: .httpVersionNotSupported,
>                                         transferEncoding: .identity(contentLength: 0)))
>          res.done()
>          return .discardBody
>      }
>      res.writeResponse(HTTPResponse(version: req.version,
>                                     status: .ok,
>                                     transferEncoding: .chunked,
>                                     headers: SomeConcreteHTTPHeaders([("X-foo": "bar")])))
>      return .processBody { (chunk, stop) in
>          switch chunk {
>              case .chunk(let data, let finishedProcessing):
>                  res.writeBody(data: data) { _ in
>                      finishedProcessing()
>                  }
>              case .end:
>                  res.done()
>              default:
>                  stop = true /* don't call us anymore */
>                  res.abort()
>          }
>      }
>  } else { ... }
> }
> --- SNAP ---
> 
> You'll see that we return a closure to process the individual chunks of an HTTP body (`return .processBody { ... }`) and register a write of the response when that closure got invoked by the eventing library.
> 

Parsing is another story, tho the synchronous api can be something like

parser.processBody =  { (chunk, stop) in
         switch chunk {
             case .chunk(let data, let finishedProcessing):
                 res.writeBody(data: data) { _ in
                     finishedProcessing()
                 }
             case .end:
                 res.done()
             default:
                 stop = true /* don't call us anymore */
                 res.abort()
         }
     }

typealias Done = Bool
extension Parser {
func feed(data: AnyCollection<UnsafeBufferPointer>) -> Done
}

> can you expand on this? What you get with kqueue/epoll is an asynchronous API so I reckon there's just some misunderstanding in terminology.

As mentioned as above, one can choose which thread, when to read, where is the buffer and how to synchronous resources.

Assuming I’m somehow writing a server that somehow have some strange requirement,
since the asynchronous approach is that the event library calling preset handler whenever payload arrives, which with a mix with kqueue I can choose not to handle any (by simply not to call kevent) and call (kevent) from my code when I’m ready.

It also let me handle the req one by one without locking resources for synchronization and I can even allocate a single buffer in the stack for all connections. 

None of these can easily done if only asynchronous API is provided. (People can always fall back to C API, but it doesn’t help server side swift much).

Cheers,
Michael.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-server-dev/attachments/20170530/669d88c2/attachment.html>


More information about the swift-server-dev mailing list