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

Johannes Weiss johannesweiss at apple.com
Tue May 30 12:02:16 CDT 2017


Hi Michael,

> On 30 May 2017, at 5:39 pm, Michael Chiu <hatsuneyuji at icloud.com> wrote:
> 
> 
>> I don't quite see why we need (or should even offer) a synchronous API for anything that involves IO (disk, network, ...). Sure, many of us would like to have a synchronous and non-blocking programming model, that'd be great. Today in Swift unfortunately we can only go asynchronous&non-blocking or synchronous&blocking (causing undefined behaviour [1] with setjmp/longjmp to get synchronous&non-blocking is cool but certainly not supported today).
> 
> I’m a bit confused here,  synchronous&non-blocking can be done by kqueue/epoll/select/poll in swift.

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.

A synchronous IO programming model would support this (pseudo-code) without blocking a thread:

func echoServer(socket: Socket) {
    while true {
        let data = read(socket)
        /* break the loop if socket is closed */
        write(socket, data)
    }
}

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)
        }
    }
}

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:

--- 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.


> It is true that kqueue and epoll are some low level C API but not having synchronous API basically screwed everyone who prefer to do their own scheduling for whatever reason.

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.

Cheers,
  Johannes



More information about the swift-server-dev mailing list