-
-
Notifications
You must be signed in to change notification settings - Fork 65
http server does not support pipelined requests #318
Comments
If you enable the // configure the pipeline
return channel.pipeline.configureHTTPServerPipeline(
withPipeliningAssistance: true, /*false*/
withServerUpgrade: upgrade,
withErrorHandling: false |
Here is a test case that shows this problem: func testPipelining() throws {
struct SlowResponder: HTTPServerResponder {
func respond(to request: HTTPRequest, on worker: Worker) -> EventLoopFuture<HTTPResponse> {
let timeout : Int = 5 - Int(request.url.lastPathComponent)!
let scheduled = worker.eventLoop.scheduleTask(in: .milliseconds(timeout * 100)) { () -> HTTPResponse in
let res = HTTPResponse(
status: .ok,
body: request.url.lastPathComponent
)
return res
}
return scheduled.futureResult
}
}
let worker = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let server = try HTTPServer.start(
hostname: "localhost",
port: 8080,
responder: SlowResponder(),
on: worker
) { error in
XCTFail("\(error)")
}.wait()
let client = try HTTPClient.connect(hostname: "localhost", port: 8080, on: worker).wait()
var responses = [String]()
var futures : [Future<()>] = []
for i in 0..<5 {
var req = HTTPRequest(method: .GET, url: "/\(i)")
req.headers.replaceOrAdd(name: .connection, value: "keep-alive")
let resFuture = client.send(req).map({ res in
let body = String(data: res.body.data!, encoding: .utf8)!
responses.append(body)
})
futures.append(resFuture)
}
try Future<()>.andAll(futures, eventLoop: worker.eventLoop).wait()
XCTAssertEqual([ "0", "1", "2", "3", "4" ], responses)
try server.close().wait()
try server.onClose.wait()
}
|
Hello, is anybody acknowledging this issue? Am I missing something? It seems pretty serious to me. Especially with a service under load. |
The reasoning behind not enabling pipelining assistance is that it has been mostly abandoned due to issues. See the wikipedia page for HTTP pipelining:
A much better alternative to HTTP/1 pipelining is to use HTTP/2 which supports proper multiplexing. That said, I think we could improve the current state of the HTTPServer by:
Given the non-trivial amount of work this would require, I think this is out of scope for vapor/http 3.x. Until this is resolved in the next version, HTTP pipelining should be considered undefined behavior. |
For a practical workaround, I think you should forget that HTTPClient exists, and instead stick with URLSession – or it's Vapor wrapper |
@vzsg I renamed the issue since it was misleading. The issue is actually with |
Thanks @tanner0101 for the write-up. I'm still a bit worried about my micro service running on the current Vapor 3 HTTP server without pipelining. As it is a micro service, it receives requests from other micro services written in other languages and using all kinds of HTTP clients. I cannot guarantee that none of them expects pipelining to work correctly. Have you reached out to swift-nio and clarified why you might have chosen to opt-out of their pipelining implementation? I would be super happy about a patch release of Vapor that exposes the option to enable pipelining in |
@t089 after discussing with the NIO team, I think it might actually not be too hard to implement in a minor release. I'm going to take a crack at it today and I'll update you here. |
@t089 I just put up PRs to add support to this package (#320) and vapor (vapor/vapor#1852). I'd love if you could test these out and verify they fix the issue you are having. Update your .package(url: "https://github.com/vapor/vapor.git", .branch("http-pipelining"), You may also need to specify the .package(url: "https://github.com/vapor/http.git", .branch("http-pipelining"), Update and regenerate your Xcode project. swift package update
swift package generate-xcodeproj Register a custom services.register(NIOServerConfig.self) { c in
var config = NIOServerConfig.default()
config.supportPipelining = true
return config
} |
Maybe I don't use
HTTPClient
correctly, but from the docs it seems that the idea is, to reuse a single client for multiple requests in order to reuse the existing connection.However, there is an issue if you send out multiple requests one after another and/or if the server takes some undefined time to process the request(s). The underlying
QueueHandler
will associate responses from the server with responses from the client in the order they arrive. Yet if the server takes different time to process the events this assumption is not true anymore. In the end you will get unexpected responses for your requests.In the description of
QueueHandler
you write:Edit: I previously assumed that HTTP pipelining does not exists for HTTP/1.1. Of course, it does indeed. However it notes:
I don't think that
HTTPClient
andHTTPServer
handle this very well.So the problem is actually in the implementation of
HTTPServer
: it should obey the order of requests and only respond in the same order.Example
See the following example:
Here we send out 5 requests in a loop to a path
/hello/:number
. The server is expected to respond with:Hello, :number
.But because the server is a bit lazy, it waits a random number of seconds before it actually
sends out the response. This completely messes up the client. One output of this program is:
The text was updated successfully, but these errors were encountered: