HTTP API v0.1.0

We spent some time updating and validating the HTTP API, along with
creating a full set of API docs etc. In order to make it easier for the
wider community to build applications that use it to test out the API and
provide feedback, I've just tagged v0.1.0.

You can see that here:
        HTTP 0.1.0
https://github.com/swift-server/http/tree/0.1.0
I've also submitted a PR to add the HTTP API into the set
servers/frameworks being tested as part of the "Which is the fastest?"
benchmark.

In parallel, Gelareh Taban has been working on adding providing a TLS API,
and HTTPS support to the HTTP project. You can see her incubator work
here:
        HTTPS incubator: https://github.com/gtaban/http
        TLS incubator: https://github.com/gtaban/TLSService.git

The aim is to validate and include the TLS work in v0.2.0, along with
feedback from the wider community on the existing API surface, fixing
bugs, and working on implementing outstanding items like trailer support,
backpressure, etc.

Chris
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU

We spent some time updating and validating the HTTP API, along with creating a full set of API docs etc. In order to make it easier for the wider community to build applications that use it to test out the API and provide feedback, I've just tagged v0.1.0.

Thank you Chris and others for all of this job, I believe HTTP API is very important for Swift future.

Have to say that I'm not a server-side developer, just interested in this subject and periodically looking into the swift-server-dev@swift.org mailing list.

If it is possible, I'd like to clarify for myself some points and provide my non-authoritative opinion based on my first look on examples of 'Hello World' and echo servers.
For me they look a little non-intuitive, not easy to understand what is going on and why we need all of this code.

For example, the 'echo' server written in Node.js I googled fast:

var http = require('http');

http.createServer(function(request,response){
  response.writeHead(200);

  request.on('data',function(message){
      response.write(message);
  });

  request.on('end',function(){
      response.end();
    });
}).listen(8080);

All is clear from the first sight.
Is it possible/reasonable to compose the Swift code in similar way, for example something like as written below?

func hello(http: HTTPContext) {
  http.response.write(header: .status(.ok))
  http.response.write(body: "Hello, World")
  http.response.end()
  
  http.request.discard()
}

func echo(http: HTTPContext) {
  http.response.write(header: .status(.ok))

  http.request.process { message in
    switch message {
    case .chunk(let data):
      http.response.write(body: data) {
        http.request.nextMessage()
      }

// or this(don't know implementation details):
// case .chunk(let chunk):
// http.response.write(body: chunk.data) {
// chunk.next()
// }
    
    case .end:
      http.response.end()
    
    default:
      http.response.abort()
      http.request.abort() // notifies that processing finished
    }
  }
}

Sorry if my thoughts are naive/fool but thank you for reply :slight_smile:

Vladimir.

···

On 02.10.2017 19:30, Chris Bailey via swift-server-dev wrote:

You can see that here:
         HTTP 0.1.0 https://github.com/swift-server/http/tree/0.1.0
I've also submitted a PR to add the HTTP API into the set servers/frameworks being tested as part of the "Which is the fastest?" benchmark.

In parallel, Gelareh Taban has been working on adding providing a TLS API, and HTTPS support to the HTTP project. You can see her incubator work here:
         HTTPS incubator: https://github.com/gtaban/http
         TLS incubator: https://github.com/gtaban/TLSService.git

The aim is to validate and include the TLS work in v0.2.0, along with feedback from the wider community on the existing API surface, fixing bugs, and working on implementing outstanding items like trailer support, backpressure, etc.

Chris
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number 741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

We spent some time updating and validating the HTTP API, along with creating a full set of API docs etc. In order to make it easier for the wider community to build applications that use it to test out the API and provide feedback, I've just tagged v0.1.0.

In addition to my previous message, I'd like to raise some concrete questions. I understand that most likely they were answered at some point of time during the discussions. But currently there is 0.1.0 version, some checkpoint, and so it attracts more attention of developers who did not participate yet in discussions and I think it is worth to summarize answers in some reply. So :

(I based my questions on examples of 'echo' and 'hello world' on https://github.com/swift-server/http and on documentation on https://swift-server.github.io/http/ )

1. What about change of writeHeader()/writeBody() to write() ? I see that PR was raised, but 0.1.0 is still uses the first form.

2. At the moment, when we enter into echo()/hello(), is _body_ of HTTP request already *downloaded*(received from socket) or not?
I assume it is not downloaded, and this is why we need this HTTPBodyProcessing return value, right?
What is the logic/strategy of reading a very large bodies of HTTP request from socket? Via chunks in HTTPBodyProcessing?

3. HTTPRequest is described as "A structure representing the headers from a HTTP request, without the body of the request.". If it is so, why not HTTPRequestHeaders, or HTTPRequestInfo, or HTTPRequestDescription etc ?
IMO HTTPRequest mentally includes all parts of request, including content. I was very confused by the name when trying to understand the examples.

4. Returning of HTTPBodyProcessing from handler function.

4.a. If the body 'ignored', does this mean that the body content of the request will not be read from socket at all?
I.e. what is the purpose and logic behind .ignored?

4.b. As was said, HTTPBodyProcessing is long and non-meaning name. First it is not clear *which* body it will process and why we need this process at all and why some processing closure is a return of our handler function.
Was it discussed(and if so, why declined) to implement this in some another form, not as result value?
For example:
func echo(http: HTTPContext, request: HTTPRequest, response: HTTPResponse) {
  response.writeHeader(status: .ok)
  http.serve { (chunk, stop) in ..
    ...
  }
}

5. HTTPResponseWriter ('response' variable) has a methods to write data. But for reading the data we use HTTPBodyProcessing, for which processing closure returned as associated value for enum instance.
Did you think about introducing of a HTTPRequestReader, which encapsulates Request details(request's headers) and methods to read the body?
Then, we probably can have a very nice and Swifty handler code with clear naming and purpose :
func echo(request: HTTPRequestReader, response: HTTPResponseWriter) {
  response.writeHeader(status: .ok)
  request.read { chunk in
    switch chunk {
           case .chunk(let data):
                 response.writeBody(data) { result in
        switch result {
                     case .ok:
          request.next()
        case .error:
          response.abort()
          request.abort()
        }
                 }
           case .end:
                 response.done()
           default:
                 response.abort()
      request.abort()
  }
}
and
func hello(request: HTTPRequestReader, response: HTTPResponseWriter) {
  response.writeHeader(status: .ok)
  response.writeBody("Hello, World!")
  response.done()

  request.discard()
}
, then current HTTPRequest instance could be accessed via request.info property or similar.
The logic behind the scene could be the same : check if request contains a closure assigned in read() method and then use it.
In client, this will be RequestWriter and ResponseReader accordingly.

6. HTTPBodyChunk has .failed case. Why not .error like in Result in completion handler. I think they should be the same for better uniform of code.

7. HTTPResponseWriter.done() and HTTPBodyChunk.end. I believe it will be nice to call them both the same(done or end) for symmetry, so we can have in code:
switch chunk {
case .done:
  response.done()
..
or
switch chunk {
case .end:
  response.end()
..

8. Why we need convenience functions that just hides completion handler and so make it easy to not process the .error case in Result of completion handler? (I.e. make it easy to skip error checking). Shouldn't they at least trow exceptions?
If I understand correctly, the 'hello world' example should looks at least like this:
func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
  try? response.writeHeader(status: .ok)
  try? response.writeBody("Hello, World!")
  try? response.done()

  return .discardBody
}

9. write*(..) functions in HTTPResponseWriter. Are they writing directly to socket or they are buffered?

10. What is the purpose of HTTPResponseWriter.done() function?

Thank you for your time and for answers.
Vladimir.

···

On 02.10.2017 19:30, Chris Bailey via swift-server-dev wrote:

You can see that here:
         HTTP 0.1.0 https://github.com/swift-server/http/tree/0.1.0
I've also submitted a PR to add the HTTP API into the set servers/frameworks being tested as part of the "Which is the fastest?" benchmark.

In parallel, Gelareh Taban has been working on adding providing a TLS API, and HTTPS support to the HTTP project. You can see her incubator work here:
         HTTPS incubator: https://github.com/gtaban/http
         TLS incubator: https://github.com/gtaban/TLSService.git

The aim is to validate and include the TLS work in v0.2.0, along with feedback from the wider community on the existing API surface, fixing bugs, and working on implementing outstanding items like trailer support, backpressure, etc.

Chris
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number 741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

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 :wink:

...

  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 :wink:

···

On 3. Oct 2017, at 19:55, Vladimir.S via swift-server-dev <swift-server-dev@swift.org> wrote:

On 3. Oct 2017, at 21:20, Georgios Moschovitis via swift-server-dev <swift-server-dev@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 :->

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?

-g.

···

For me they look a little non-intuitive, not easy to understand what is going on and why we need all of this code.

Hi,

We spent some time updating and validating the HTTP API, along with creating a full set of API docs etc. In order to make it easier for the wider community to build applications that use it to test out the API and provide feedback, I've just tagged v0.1.0.

In addition to my previous message, I'd like to raise some concrete questions. I understand that most likely they were answered at some point of time during the discussions. But currently there is 0.1.0 version, some checkpoint, and so it attracts more attention of developers who did not participate yet in discussions and I think it is worth to summarize answers in some reply. So :

(I based my questions on examples of 'echo' and 'hello world' on https://github.com/swift-server/http and on documentation on https://swift-server.github.io/http/ )

1. What about change of writeHeader()/writeBody() to write() ? I see that PR was raised, but 0.1.0 is still uses the first form.

2. At the moment, when we enter into echo()/hello(), is _body_ of HTTP request already *downloaded*(received from socket) or not?
I assume it is not downloaded, and this is why we need this HTTPBodyProcessing return value, right?
What is the logic/strategy of reading a very large bodies of HTTP request from socket? Via chunks in HTTPBodyProcessing?

technically this is an implementation detail. However every sensible implementation will be asynchronous and will only buffer a bounded number of bytes per request. Therefore, for implementations that satisfy this constraint, the body will not necessarily be downloaded in full at the time you enter `echo`.

That's the reason you're supposed to return a closure as the library can call it whenever there's new data available.

And the `finishedProcessing` callback tells the library that you're finished processing the last chunk. That's used to establish back-pressure to the client. In other words if the client sends data quicker than the server can process it the library will need to stop reading after its bounded buffer is exhausted. That will then fill up the receive buffer in the kernel which will then use TCP flow control to communicate to the client that it should stop sending more.

3. HTTPRequest is described as "A structure representing the headers from a HTTP request, without the body of the request.". If it is so, why not HTTPRequestHeaders, or HTTPRequestInfo, or HTTPRequestDescription etc ?
IMO HTTPRequest mentally includes all parts of request, including content. I was very confused by the name when trying to understand the examples.

Even though I named the original HTTPRequest I agree with you. It should be HTTPRequestHead or something I think.

4. Returning of HTTPBodyProcessing from handler function.

4.a. If the body 'ignored', does this mean that the body content of the request will not be read from socket at all?
I.e. what is the purpose and logic behind .ignored?

it will need to be read but discarded as efficiently as the library can. If you don't read it you'll break keep-alive.

4.b. As was said, HTTPBodyProcessing is long and non-meaning name. First it is not clear *which* body it will process and why we need this process at all and why some processing closure is a return of our handler function.

Very happy to change the name, maybe HTTPRequestBodyProcessing.

The reason that we return it from the handler is that at this point the library will need to know what to do with the body because the client will not start sending it (if there's a body). If you have an idea where to better put it, please suggest something.

Was it discussed(and if so, why declined) to implement this in some another form, not as result value?
For example:
func echo(http: HTTPContext, request: HTTPRequest, response: HTTPResponse) {
  response.writeHeader(status: .ok)
  http.serve { (chunk, stop) in ..
    ...
  }
}

that's also a viable API, there's slightly more room for programmer error here as the programmer isn't forced to do anything with the `HTTPContext`.

5. HTTPResponseWriter ('response' variable) has a methods to write data. But for reading the data we use HTTPBodyProcessing, for which processing closure returned as associated value for enum instance.
Did you think about introducing of a HTTPRequestReader, which encapsulates Request details(request's headers) and methods to read the body?
Then, we probably can have a very nice and Swifty handler code with clear naming and purpose :
func echo(request: HTTPRequestReader, response: HTTPResponseWriter) {
  response.writeHeader(status: .ok)
  request.read { chunk in
    switch chunk {
         case .chunk(let data):
               response.writeBody(data) { result in
        switch result {
                   case .ok:
          request.next()
        case .error:
          response.abort()
          request.abort()
        }
               }
         case .end:
               response.done()
         default:
               response.abort()
      request.abort()
  }
}

again more room for error for questionable benefit. But please note that this is supposed to be a low-level HTTP API. If you prefer what you proposed above, there's absolutely nothing stopping you from wrapping the low-level API and exposing it like you do.

and
func hello(request: HTTPRequestReader, response: HTTPResponseWriter) {
  response.writeHeader(status: .ok)
  response.writeBody("Hello, World!")
  response.done()

  request.discard()
}
, then current HTTPRequest instance could be accessed via request.info property or similar.
The logic behind the scene could be the same : check if request contains a closure assigned in read() method and then use it.
In client, this will be RequestWriter and ResponseReader accordingly.

6. HTTPBodyChunk has .failed case. Why not .error like in Result in completion handler. I think they should be the same for better uniform of code.

sure, I'm personally not fussed about the names. But I agree we should be consistent.

7. HTTPResponseWriter.done() and HTTPBodyChunk.end. I believe it will be nice to call them both the same(done or end) for

well, `done` is an action (has side-effects) and `.end` is a value that tell's you this is the end. They're not quite the same but I see your point. Not too fussed about the names.

symmetry, so we can have in code:
switch chunk {
case .done:
  response.done()
..
or
switch chunk {
case .end:
  response.end()
..

definitely makes sense :slight_smile:

8. Why we need convenience functions that just hides completion handler and so make it easy to not process the .error case in Result of completion handler? (I.e. make it easy to skip error checking). Shouldn't they at least trow exceptions?
If I understand correctly, the 'hello world' example should looks at least like this:
func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
  try? response.writeHeader(status: .ok)
  try? response.writeBody("Hello, World!")
  try? response.done()

  return .discardBody
}

you can't throw here as we only asynchronously know if `writeWhatever` and `done` succeeded.

9. write*(..) functions in HTTPResponseWriter. Are they writing directly to socket or they are buffered?

implementation detail of the library.

10. What is the purpose of HTTPResponseWriter.done() function?

how else do you tell the library that you're done writing the body? It could be (and will often be) chunked encoding so there's no way the library could know. Also the result of the `done` function really tells you if the overall thing was successfully written and sent.

For many application (most that don't stream data for very long) it's useful to ignore the result of each write and only care about the overall result (which you get from `done`). For other applications the `write` callbacks are crucial. Imagine a long-poll server where the client might hold a connection for hours. The server really must know if a write failed as it then wants to stop everything related to that connection and tear it down.

-- Johannes

···

On 4 Oct 2017, at 6:55 pm, Vladimir.S via swift-server-dev <swift-server-dev@swift.org> wrote:
On 02.10.2017 19:30, Chris Bailey via swift-server-dev wrote:

Thank you for your time and for answers.
Vladimir.

You can see that here:
        HTTP 0.1.0 https://github.com/swift-server/http/tree/0.1.0
I've also submitted a PR to add the HTTP API into the set servers/frameworks being tested as part of the "Which is the fastest?" benchmark.
In parallel, Gelareh Taban has been working on adding providing a TLS API, and HTTPS support to the HTTP project. You can see her incubator work here:
        HTTPS incubator: https://github.com/gtaban/http
        TLS incubator: https://github.com/gtaban/TLSService.git
The aim is to validate and include the TLS work in v0.2.0, along with feedback from the wider community on the existing API surface, fixing bugs, and working on implementing outstanding items like trailer support, backpressure, etc.
Chris
Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number 741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU
_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

WELL SAID!

It’s my impression that this API will be the metaphorical engine and drivetrain for the cars we build.

···

Sent from my iPhone

On Oct 3, 2017, at 15:59, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

On 3. Oct 2017, at 19:55, Vladimir.S via swift-server-dev <swift-server-dev@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 :wink:

...

   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 :wink:

On 3. Oct 2017, at 21:20, Georgios Moschovitis via swift-server-dev <swift-server-dev@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

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@swift.org> wrote:

On 3. Oct 2017, at 19:55, Vladimir.S via swift-server-dev <swift-server-dev@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 :wink:

...

  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 :wink:

On 3. Oct 2017, at 21:20, Georgios Moschovitis via swift-server-dev <swift-server-dev@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Hi,

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.

agreed.

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.

I agree with your points. I haven't checked the mailing list but didn't we anyway agree that we wanted to only have a 'writeResponseHead` which you pass a full `HTTPResponseHead` value which contains the whole head (status, headers, ...)? If you have a good suggestion how to improve this here without being less versatile of an API I'm sure people would listen.

For us the current API works quite well as it's only used as the lower level. We have nicer to use (albeit less versatile) high-level APIs for actual user code. In the OSS world, that'd be Vapor, Kitura etc.

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.

our implementation `preconditionFailure`s if

- you lose the last reference to the response writer and haven't call done
- you call the functions out of order (`writeHeaders` after `writeBody`, `writeBody` after `done`, ...)
- ...

Whilst that is not ideal, with the lack of a fancy type system (linear types or so) I failed to encode all that into the type system without sacrificing performance or versatility. I'm not questioning we could encode a little more in the type system though (and I think we should).

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.

That seems infeasible unfortunately for the sending side (the HTTP response). Imagine a long-poll server. The client makes a request and maybe an hour later the server responds with a state change. Say you have an API for football (:soccer:️) scores, nothing happens for a while and then someone scores. The server will then 'send' (as a chunk in Chunked-Encoding HTTP body) a bit of JSON to the client which then changes the UI. The request will stay alive and when the next goal happens, the server will send another bit of JSON.

So if the framework were to ask the user for data there's two options:
1) a call into user code that (synchronously) returns what we want to send
2) a call into the user code with a callback that the user is supposed to call asynchronously when there's data to be sent

(1) doesn't work as you'd block a kernel thread per request which is a non-starter
(2) suffers from the same problem as the current API: the user might just never call that callback :\

I'm all ears if you have ideas though.

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.

can you expand here? A general streaming API would be very nice indeed but there isn't one that we can/want to depend on. The networking/streams part of the Swift Server APIs workgroup is looking into this. Unfortunately, as it's less concrete than a low-level HTTP/1.1 server API, there's even more opinions and it's even harder to settle on one design :).

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.

no, `writeBody` is the only way to write a HTTP response body. `HTTPBodyProcessing` is there to process the HTTP request body. It'll be called whenever there are bytes available from the client. Pleas note it might take any length of time for the client to finish sending its request body. And the server may start streaming the response body any amount of time before the client finishes sending its request body.

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?

`stop` was introduced because of discussions on this list. `stop` means "I'm no longer interested in any further request body chunks sent from the client". That allows the library to efficiently discard them and facilitates the development of server apps (less state management). The `response.done()` means I'm done writing the HTTP response body.

It's never mandatory to do anything with `stop` but it is mandatory to call `response.done()` when you're done. `done`'s callback will be called whenever everything has been sent (or failed to send) and you get an overall result if it was successful.

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

That needs to be documented. `done()` should finish the HTTP response. `abort()` will finish it ASAP (without trying to send anything outstanding) and close the connection. `done()` will leave it open (for the next keep-alive request).

, but then write some body data?

our implementation crashes if you violate the state machine.

What happens if I forget to call finishProcessing?

well, you'll never get a new body chunk from the client. Our implementation crashes if you leak (or call more than once) the `finishProcessing` callback.

What if I want to throw an error?

you can't as it's an asynchronous API and Swift doesn't have support for that yet (will probably come with async-await).

Generators, iterators, and streams again provide good examples of using the language’s control flow rather than creating your own stateful system.

Swift doesn't have language support for that, as mentioned above we didn't want to design our own stream abstraction. That's also not trivial as you will need to establish back pressure from the client to the server. Ie. not buffer data if the client sends quicker than the server can consume.

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?

IIRC only the `HTTPResponseWriter` is class-constrained that is because the `HTTPResponseWriter` has identity and therefore should be a `class` in Swift. It has reference semantics and not value semantics as it has state and (at least transitively) holds a file descriptor.

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.

the idea is that you usually wrap that API to make it more user-friendly. But please go ahead and propose a protocol based one.

I hope that makes sense. Please don't get me wrong, this is all not perfect and not set in stone.

-- Johannes

···

On 4 Oct 2017, at 11:33 pm, Jack Lawrence via swift-server-dev <swift-server-dev@swift.org> wrote:

Jack

On Oct 3, 2017, at 12:59 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

On 3. Oct 2017, at 19:55, Vladimir.S via swift-server-dev <swift-server-dev@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 :wink:

...

  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 :wink:

On 3. Oct 2017, at 21:20, Georgios Moschovitis via swift-server-dev <swift-server-dev@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

FYI: I updated my Apache implementation of the HTTP API:

  https://github.com/modswift/http/tree/implementation/mod_swift

and wrote up a small blog post on how to use HTTP 0.1.0 with the included server, as well as the above:

  http://www.alwaysrightinstitute.com/http-010/

Have fun!

hh

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

that's really cool, thanks for putting that together and sharing it! And that sounds great about the GCD implementation, by the way based on DispatchSources or DispatchIO?

···

On 30 Oct 2017, at 3:08 am, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

FYI: I updated my Apache implementation of the HTTP API:

https://github.com/modswift/http/tree/implementation/mod_swift

and wrote up a small blog post on how to use HTTP 0.1.0 with the included server, as well as the above:

http://www.alwaysrightinstitute.com/http-010/

Have fun!

hh

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Thanks for the little example, ver helpful. A few naming suggestions stuck me though:

From:

import Foundation
import HTTP

func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
	response.writeHeader(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.done()
	return .discardBody
} 

to:

import Foundation
import HTTPServer

func hello(request: HTTPRequestHead, response: HTTPResponseWriter ) -> HTTPRequestBodyProcessing {
	response.writeHead(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.close()
	return .discardBody
} 

what do you think?

···

On 30 Oct 2017, at 12:08 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

FYI: I updated my Apache implementation of the HTTP API:

https://github.com/modswift/http/tree/implementation/mod_swift

and wrote up a small blog post on how to use HTTP 0.1.0 with the included server, as well as the above:

http://www.alwaysrightinstitute.com/http-010/

Have fun!

hh

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

Channels. (source for listener of course) I mean, it doesn’t really matter that much. With the current API sources may be more efficient as you may be able to avoid copying to DispatchData objects.

I think if we really want async, we need a few API adjustments to make async efficient enough. E.g. maybe pass queues around (probably not a straight DispatchQueue if we don’t want to tie it to GCD, but a context which ensures synchronization - that would be efficient for sync too).

My current async imp has a lot of dispatch overhead because the callbacks can be called from essentially any queue in the current API (affecting every read & write). There are a few models that can be used for doing async:

a) the single ‘worker queue’ model done by Node.js / Noze.io. In Swift this is not as bad as on Node.js because you can dispatch work on a different queue and then back to the single ‘main queue’ which ensures the IO stack synchronization (like shown in here http://noze.io/noze4nonnode/). When calling done callbacks, you dispatch back to a single queue.

This can congest because of the global main queue. In Node they deal w/ this by forking servers. Which is kinda fine and lame at the same time.

b) A complete ‘queue free’ model. I’m doing this in my current approach. It is kinda lock free, but has a lot of async dispatching. The base performance overhead is/should-be pretty high, but scalability is kinda like to optimal (theoretically making use of as many CPUs as possible).

Not sure how well this goes in Linux. Are DispatchQueue’s also cheap on Linux or does the current implementation create a new thread for each?

c) Something like a), but with multiple worker queues. Kinda like the Node resolution, but w/o the different processes. This needs an API change, all the callbacks need get passed ‘their’ main queue (because it is not a global anymore).

I don’t know. a) is the easiest and maybe good enough. Scalability should be way better than the threaded sync setup.
My more complex b) version can be easily changed to a).

While working on this I’m kinda wondering whether we should indeed have multiple implementations that can be used for the specific purposes. E.g. for performance benchmarks (which people seem to like) you would want to have the sync implementation for best raw throughput. For a scalability benchmark a) is probably best for regular setups, and maybe b) for 256 CPU servers.
c) is a sweet spot, but complicates the API (though I think UIKit etc also have the model of passing queues alongside delegates/callbacks).

Oh, and we have those:

  func doSomething(onDone cb : ()->())

This would be way better

  func doSomething(onDone cb : (()->())?)

for the case where cb is nil (no empty block needs to be synchronized).

Maybe I manage to finish it up on the weekend, not sure yet.

hh

···

On Nov 2, 2017, at 1:21 AM, Johannes Weiß <johannesweiss@apple.com> wrote:

that's really cool, thanks for putting that together and sharing it! And that sounds great about the GCD implementation, by the way based on DispatchSources or DispatchIO?

Hi Georgios,

to summarise your suggestions

a) Module HTTP => HTTPServer
b) HTTPRequest => HTTPRequestHead
c) HTTPBodyProcessing => HTTPRequestBodyProcessing
d) response.done() => response.close()

a) I don’t think a) flies, because we might also want to add a client

b) I suggested that a long time ago and I’m all in for that. Maybe open a PR, I will definitely vote for it (as if I could vote :wink:

c) this makes no sense to me, because the body processing is the same, whether it would be done in a server or a client.

d) close() sounds to harsh to me (as if you are forcefully closing the connections, which I think is what `abort` is for).
I think Node uses `end()`, but I’m also fine w/ .done()

hh

···

On 7. Nov 2017, at 12:30, Georgios Moschovitis <george.moschovitis@icloud.com> wrote:

Thanks for the little example, ver helpful. A few naming suggestions stuck me though:

From:

import Foundation
import HTTP

func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
	response.writeHeader(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.done()
	return .discardBody
}

to:

import Foundation
import HTTPServer

func hello(request: HTTPRequestHead, response: HTTPResponseWriter ) -> HTTPRequestBodyProcessing {
	response.writeHead(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.close()
	return .discardBody
}

what do you think?

On 30 Oct 2017, at 12:08 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

FYI: I updated my Apache implementation of the HTTP API:

https://github.com/modswift/http/tree/implementation/mod_swift

and wrote up a small blog post on how to use HTTP 0.1.0 with the included server, as well as the above:

http://www.alwaysrightinstitute.com/http-010/

Have fun!

hh

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

There is also

e) writeHeader => writeHead

···

On 7 Nov 2017, at 2:19 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

Hi Georgios,

to summarise your suggestions

a) Module HTTP => HTTPServer
b) HTTPRequest => HTTPRequestHead
c) HTTPBodyProcessing => HTTPRequestBodyProcessing
d) response.done() => response.close()

a) I don’t think a) flies, because we might also want to add a client

b) I suggested that a long time ago and I’m all in for that. Maybe open a PR, I will definitely vote for it (as if I could vote :wink:

c) this makes no sense to me, because the body processing is the same, whether it would be done in a server or a client.

d) close() sounds to harsh to me (as if you are forcefully closing the connections, which I think is what `abort` is for).
I think Node uses `end()`, but I’m also fine w/ .done()

hh

On 7. Nov 2017, at 12:30, Georgios Moschovitis <george.moschovitis@icloud.com> wrote:

Thanks for the little example, ver helpful. A few naming suggestions stuck me though:

From:

import Foundation
import HTTP

func hello(request: HTTPRequest, response: HTTPResponseWriter ) -> HTTPBodyProcessing {
	response.writeHeader(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.done()
	return .discardBody
}

to:

import Foundation
import HTTPServer

func hello(request: HTTPRequestHead, response: HTTPResponseWriter ) -> HTTPRequestBodyProcessing {
	response.writeHead(
		status: .ok,
		headers: [ "Content-Type": "text/html" ])
	)
	response.writeBody("Hello, World!\n")
	response.close()
	return .discardBody
}

what do you think?

On 30 Oct 2017, at 12:08 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

FYI: I updated my Apache implementation of the HTTP API:

https://github.com/modswift/http/tree/implementation/mod_swift

and wrote up a small blog post on how to use HTTP 0.1.0 with the included server, as well as the above:

http://www.alwaysrightinstitute.com/http-010/

Have fun!

hh

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

OK, I cleaned up my GCD implementation and submitted it as a PR. Have fun, feedback is welcome:

  https://github.com/swift-server/http/pull/96

There is also a tinsy test server:

  https://github.com/ZeeZide/http-testserver

hh

···

On 30 Oct 2017, at 3:08 am, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

P.S.: I also have a GCD based async implementation of the API almost ready. Stay tuned.

On 2. Nov 2017, at 01:21, Johannes Weiß <johannesweiss@apple.com> wrote:

that's really cool, thanks for putting that together and sharing it! And that sounds great about the GCD implementation, by the way based on DispatchSources or DispatchIO?

Hi Helge,

that's really cool, thanks for putting that together and sharing it! And that sounds great about the GCD implementation, by the way based on DispatchSources or DispatchIO?

Channels. (source for listener of course) I mean, it doesn’t really matter that much. With the current API sources may be more efficient as you may be able to avoid copying to DispatchData objects.

I think if we really want async, we need a few API adjustments to make async efficient enough. E.g. maybe pass queues around (probably not a straight DispatchQueue if we don’t want to tie it to GCD, but a context which ensures synchronization - that would be efficient for sync too).

do you have suggestions how that could look? In our internal implementation I have bits of that but never got to the point to actually profiling stuff and I didn't go all the way.

My current async imp has a lot of dispatch overhead because the callbacks can be called from essentially any queue in the current API (affecting every read & write). There are a few models that can be used for doing async:

yes, we have that too

a) the single ‘worker queue’ model done by Node.js / Noze.io. In Swift this is not as bad as on Node.js because you can dispatch work on a different queue and then back to the single ‘main queue’ which ensures the IO stack synchronization (like shown in here http://noze.io/noze4nonnode/). When calling done callbacks, you dispatch back to a single queue.

This can congest because of the global main queue. In Node they deal w/ this by forking servers. Which is kinda fine and lame at the same time.

b) A complete ‘queue free’ model. I’m doing this in my current approach. It is kinda lock free, but has a lot of async dispatching. The base performance overhead is/should-be pretty high, but scalability is kinda like to optimal (theoretically making use of as many CPUs as possible).

there's indeed quite a few probably better models but I always thought of that as part of the 'networking/streams' track of the server APIs work group. We have a few ideas here, will follow up with that as soon as we can.

For libdispatch I believe the following model should work very well:

- create a few number of 'base' queues, probably equal to the number of CPUs stored in an array 'baseQueues'
- for every request create a new DispatchQueue(label: "...", target: baseQueues[requestNo % baseQueues.count]) (where requestNo is a global atomic (oops) integer of the overall requests)

the base queues will end up on different (kernel) threads and the request queues will be round-robin scheduled onto the base queues. That way we make sure we don't randomly spawn new threads which isn't good.

That model obviously only works iff the application code is either non-blocking or dispatches itself off the request queue if it needs to do blocking work. Needless to say we should aim for non-blocking but the reality of today's code in Swift doesn't entirely look like that :wink:

Not sure how well this goes in Linux. Are DispatchQueue’s also cheap on Linux or does the current implementation create a new thread for each?

the queues themselves are cheap but the Linux implementation AFAIK behaves quite weirdly if it needs to spawn threads. IIRC there's one global thread which every 100ms evaluates if the existing threads are all blocked and if they are, it'll spawn a new thread. Haven't checked the code in a while, maybe someone knows better.

That's obviously not a great GCD implementation, the real GCD on macOS has kernel support to make that work much better. The same sadly applies to the eventing mechanism (DispatchSources) which are much more efficient and reduce thread hopping a lot on macOS.

But even on Linux I think not having many 'base' queues (which are queues that do not target other queues) should really give the best performance. Needless to say one has to be very careful not to ever block one of these base queues.

c) Something like a), but with multiple worker queues. Kinda like the Node resolution, but w/o the different processes. This needs an API change, all the callbacks need get passed ‘their’ main queue (because it is not a global anymore).

Sorry, should've read the whole email before writing above. That sounds pretty much like what I wrote above, right? If you agree that sounds like the best model on GCD to me.

I don’t know. a) is the easiest and maybe good enough. Scalability should be way better than the threaded sync setup.
My more complex b) version can be easily changed to a).

While working on this I’m kinda wondering whether we should indeed have multiple implementations that can be used for the specific purposes. E.g. for performance benchmarks (which people seem to like) you would want to have the sync implementation for best raw throughput. For a scalability benchmark a) is probably best for regular setups, and maybe b) for 256 CPU servers.
c) is a sweet spot, but complicates the API (though I think UIKit etc also have the model of passing queues alongside delegates/callbacks).

agreed. For best performance we probably need to do that. In the internal implementation you get your request queue with the request.

Oh, and we have those:

func doSomething(onDone cb : ()->())

This would be way better

func doSomething(onDone cb : (()->())?)

for the case where cb is nil (no empty block needs to be synchronized).

Maybe I manage to finish it up on the weekend, not sure yet.

:+1:

-- Johannes

···

On 1 Nov 2017, at 6:13 pm, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:
On Nov 2, 2017, at 1:21 AM, Johannes Weiß <johannesweiss@apple.com> wrote:

hh

_______________________________________________
swift-server-dev mailing list
swift-server-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-server-dev

From what I've seen, they're pretty expensive on Linux.

I had a previous implementation (prior to https://github.com/carlbrown/HTTPSketch/commit/fd083cd30 ) that created a new `DispatchQueue` for every `accept()` (i.e. each new TCP connection), and it performed horribly on Linux. It got much, much faster once I created a fixed pool of queues and reused/shared them between connections.

-Carl

···

On Nov 1, 2017, at 8:13 PM, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:
b) A complete ‘queue free’ model. I’m doing this in my current approach. It is kinda lock free, but has a lot of async dispatching. The base performance overhead is/should-be pretty high, but scalability is kinda like to optimal (theoretically making use of as many CPUs as possible).

Not sure how well this goes in Linux. Are DispatchQueue’s also cheap on Linux or does the current implementation create a new thread for each?

If you want to be really pedantic, you are writing one response header section. The individual elements of the header section are the response line and zero or more header *fields*.

Of course, and I can not stress this enough, these are all details that should not particularly factor in to your decision. They’re absurdly pedantic.

···

On 7 Nov 2017, at 15:56, Helge Heß via swift-server-dev <swift-server-dev@swift.org> wrote:

On 7. Nov 2017, at 16:51, Georgios Moschovitis <george.moschovitis@icloud.com> wrote:

There is also

e) writeHeader => writeHead

The current API uses writeHeader for all headers. (writeHead sounds more like the one single response header). Though I agree it is in line with HTTPRequestHead :thinking:

The current API uses writeHeader for all headers. (writeHead sounds more like the one single response header). Though I agree it is in line with HTTPRequestHead :thinking:

(I mean *I am* very much for distinct `writeHead`, `writeContinue` methods, but I think there is no common agreement on this, don’t know. Maybe someone should just do another PR on this)

hh

···

On 7. Nov 2017, at 16:51, Georgios Moschovitis <george.moschovitis@icloud.com> wrote:

There is also

e) writeHeader => writeHead

Terms of Service

Privacy Policy

Cookie Policy