In HTTP/2, the only way is to use a fresh stream per request. HTTP/2 doesn't allow for multiple requests be sent through one stream. Note however that one connection can have many streams in HTTP/2 so you can do many many requests in one connection, each using their own stream (that solves the head of line blocking from HTTP/1.1).
Note that NIO organises this the following way:
/------ another HTTP/2 stream, a Channel [c1]
/--- one HTTP/2 stream, also a Channel [c2]
--- network connection, a Channel [c]
\--- yet another HTTP/2 stream, a Channel [c3]
\----- and a final HTTP/2 stream, also a Channel [c4]
So both the actual network connection (named [c]
above) as well as all the HTTP/2 streams (named cN
above) are all Channel
s. The 'child channels' will have channel.parentChannel
set to the 'parent channel' which is the actual network connection. Example: c3.parentChannel == c
.
Hope that makes sense.
Feel free to skip the rest because this is not relevant for APNS because HTTP/2 doesn't do pipelining but the much better multiplexing instead.
But because it was discussed above: If you have a protocol that doesn't do multiplexing (HTTP/1.1, many database protocols, redis, ...) then swift-nio-extras
contains a great helper: RequestResponseHandler
which solves a problem that many are running into providing functions like
func send(request: MyRequest) -> EventLoopFuture<MyResponse>
essentially what you're doing here is terminating the ChannelPipeline
and adapting a request/response model on top of futures. If you support pipelining you will then always need to hold an array (or better CircularBuffer
) of promises to be fulfilled when you receive the response.
But there's no point for repeating this over and over again, so with this
someChannel.pipeline.addHandler(RequestResponseHandler<MyRequest, MyResponse>()) // needs to be the last handler
you will be able to write
// send a request and a fresh promise for the result
func send(request: MyRequest) -> EventLoopFuture<MyResponse> {
let promise = self.channel.makePromise(of: MyResponse.self)
self.channel.writeAndFlush((MyRequest(...), promise))
return promise.futureResult
}
and everything will be handled automatically. The request will be sent pipelined straight away and when the corresponding response comes in, the promise will be fulfilled. So I'd recommend do adopt this for all pipelined protocols where you have a func send(request: MyRequest) -> EventLoopFuture<MyResponse>
on your connection object which holds the Channel
.