httpswift / swifter

Tiny http server engine written in Swift programming language.
BSD 3-Clause "New" or "Revised" License
3.89k stars 540 forks source link

Asynchronous handlers #66

Open vittoriom opened 8 years ago

vittoriom commented 8 years ago

Hi, we're planning to use your server instead of GCDWebServer, but we need to asynchronously handle requests. This means that, when specifying a request handler, we don't want to immediately return a HttpResponse but be able to pass this to some kind of completion handler. Do you think this could be a welcome improvement to the swifter project? Also, would you be able to do it?

Thanks!

VFUC commented 8 years ago

How about just writing your own handler, which then deals with the HttpResponse any way you like?

vittoriom commented 8 years ago

Because if I understand correctly how it works, even if I write my own handler I have to return a response in the closure and I don't have one yet. Can you elaborate some more on how I could do that?

VFUC commented 8 years ago

Well if you're saying you don't have the necessary information to return a response yet, could you not perform the steps to obtain that information in the handler? I'm thinking about something like the following, in this scenario the missing information would be a String:

func customHandler( informationGetter: () -> String ) -> ( HttpRequest -> HttpResponse) {
 return { request in    
   let externalInformation = informationGetter()
   return HttpResponse.OK(HttpResponseBody.Text("Response: \(externalInformation)"))
  }
}
vittoriom commented 8 years ago

Maybe I didn't make myself clear enough, but that's what I meant with "asynchronous". When I get a request, I have to perform some stuff before I know what I will have to return.

In the scenario you wrote, there is no asynchronous stuff going on, it should be more like:

func customHandler( informationGetter: () -> String ) -> ( HttpRequest -> HttpResponse) {
 return { request in    
   let externalInformation = informationGetter().onCompletion { informationNeeded in
      //what now? The following line wouldn't work of course
      //return HttpResponse.OK(HttpResponseBody.Text("Response: \(externalInformation)"))
   }
   return //return what?
  }
}

while I expect a server library to support something more similar to:

func customHandler( informationGetter: () -> String ) -> ( HttpRequest -> HttpResponse) {
 return { (request, completion) in    
   let externalInformation = informationGetter().onCompletion { informationNeeded in
      //perform some stuff
      completion(myResponse)
   }
  }
}
VFUC commented 8 years ago

Oh, I see what you mean. You're right, my proposal is still synchronous.

damian-kolakowski commented 8 years ago

@vittoriom hi man

For now you could use dispatch_semaphore to wait for the end of informationGetter().onCompletion : https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW24

Swifter should expose some kind of promises chaining.

best dk

vittoriom commented 8 years ago

Hi @glock45 I appreciate your reply, but writing semaphore-based request handlers is not an option at the moment. Thanks anyway!

C0deH4cker commented 8 years ago

This sounds like the perfect use case for promises, since you don't yet have the response body but return a promise that you will create it later

julien-c commented 8 years ago

I think it is pretty reasonable for an HTTP server today to be asynchronous-first, given that every non-trivial server is going to hit some kind of persistence engine or make network calls itself.

What would you guys think about something like:

server["/hello"] = { request, response in
    DB.ExampleAsyncCall.find {
        response.status = .OK
        response.body = .HTML("You asked for " + request.url)
        response.send()
    }
}

or

server["/hello"] = { request, response in
    DB.ExampleAsyncCall.find {
        response.status = .OK
        response.send(.HTML("You asked for " + request.url))
    }
}
Francescu commented 8 years ago

I agree with @julien-c

damian-kolakowski commented 8 years ago

:+1: ...also.. with @julien-c proposition we will solve https://github.com/glock45/swifter/issues/1

vittoriom commented 8 years ago

Not sure what DB.ExampleAsyncCall.find is supposed to be, but I would use an established pattern like Promises and Futures, so the handler closure only takes the request and returns a response future.

julien-c commented 8 years ago

My DB.ExampleAsyncCall.find() is your informationGetter().onCompletion() :grin:

It's way simpler to do completion callbacks, à la Express.js – but nothing prevents you from dropping a Promise implementation there if you need (I know of FutureKit, PromiseKit, BrightFutures). He who can do more, can do less.

Laban1 commented 8 years ago

Any ETA on this?

fdstevex commented 8 years ago

I ran into this as well, looking to implement a simple proxy server. I want to have Swifter listen and handle the connection, and then my handler will make a connection to somewhere else (different protocol) and stream the data back to the Swifter connection.

I've done this now by creating my own HttpHandlers extension. The reading I'm doing from the other side is asynchronous, so I have to run it asynchronously.

I've implemented this by handing off the socket to the stream handler, similar to what the WebSocket handler does, but then HttpServerIO.handleConnection closes the socket as soon as the handler is set up. I hacked it so that if the socket is passed off to the handler it is not closed in handleConnection. That works for me, but it'd be nice to have support for this baked in.

yipsang commented 7 years ago

@julien-c Is your suggestion implemented in Swifter so far? I am doing similar proxy server as @fdstevex 's in my project. If asynchronous handler is not modified yet, I may switch to @fdstevex 's approach. Anyway, thanks for you guys' contribution for making this easy to use httpserver for swift

nodeful commented 6 years ago

First of all, hats off to the creator of this library. This is the only Swift "client-side server" (I want to run a small API server on my user's machine) I was able to find. But it desperately needs some async support, especially as a majority of workflows when writing servers are async and most users using a library will have limited knowledge of how to extend it to fit their needs :)

chaitanyabaranwal commented 4 years ago

Any update on async support? I think this library is more lightweight than GCDWebServer but would be really great if we could get async support baked in.

nodeful commented 4 years ago

I ended up switching to https://github.com/thecatalinstan/Criollo

chaitanyabaranwal commented 4 years ago

I ended up switching to https://github.com/thecatalinstan/Criollo

Hey! Thanks for this, I will check this out :D

isaced commented 1 year ago

I finally wrote an asynchronous, lightweight HTTP Server based on Socket in pure Swift, which can run well on iOS/macOS/tvOS: https://github.com/isaced/Aoxiang