Apple Push Notification Service implementation

push
apns

(Ian Partridge) #45

Hi Kyle,

I know of 3 existing SPM packages implementing JWT:



I think ideally the owner of one of those would pitch that we start from their existing project and work from there.

There is a good introduction to the Vapor package at https://docs.vapor.codes/3.0/jwt/overview/ and the Kitura package in the README.md

It would be great if you (or anyone else!) could take a look at what's currently there and see what the best approach might be. It would be best to look from the perspective of the public API rather than the underlying crypto implementation as that could be changed.


(Tobias) #46
  • In the current design what is the role of APNSStreamHandler?
  • Why do you need to maintain this queue?

(Ian Partridge) #47

This is now available at https://github.com/IBM-Swift/BlueECC :tada:

Support for ES256 in our JWT package will follow soon.


(Kyle Browning) #48

It is what lets us write our APNSRequestContext to the channel. basically converting it to an outgoing request, and stores the promise to be fulfilled later.


(Tobias) #49

I see, but why does keep a list of promises? Don’t you have one request, one response? The next request will create a new client channel together with a brand new APNSRequestContext, won’t it?


(Tobias) #50

In APNSConnnection, is it safe to share a single HTTP2StreamMultiplexer between channels?


(Kyle Browning) #51

The queue is for pipelining so you can do

let res1 = conn.send(req)
let res2 = conn.send(req2)

res1.wait()
res2.wait()

(Kyle Browning) #52

Not sure I fully understand that but channelInitializer should only be called once.

We're waiting for a better way of handling that upstream in NIO, but for now that's the way we got it to work based on example code and @tanner0101 chatting with @lukasa


(Tobias) #53

Hm I thought that the preferred way of doing HTTP/2 is to use a new client stream per request. So no pipelining on a single stream. In fact this is exactly what you are doing, that is why I don’t think you need the array.


(Tobias) #54

Ah yes you are right, because you don’t reuse the bootstrap. If the api would allow to reuse the bootstrap to create multiple connections to apns, only then it would be a problem.


(Kyle Browning) #55

Are you testing things out or just reviewing? Hoping someone else starts testing this besides me!
:wink:


(Tanner) #56

@t089 ah, I think you are right actually. Good catch. We call HTTP2StreamMultiplexer.createStreamChannel on each APNSConnection.send call anyway, so there is no need for the stream handler to have an array. The HTTP2 multiplexer takes care of pipelining. We should be able to get away with just storing an optional there, although the array doesn't necessarily hurt.


(Tobias) #57

Fair point, I‘ll try to give it proper test run next week. :slight_smile:


(Johannes Weiss) #58

In a ClientBootstrap, the channelInitializer can be invoked multiple times (because of happy eyeballs racing resolution) but all but one of these Channels will be closed by NIO itself. Also check the documentation. We will improve this but not right now.

Sorry, only skimmed the thread here but couldn't quite figure out what exactly you're waiting for NIO do improve on.


(Johannes Weiss) #59

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


(Kyle Browning) #60

Ill have to defer to @tanner0101 here because they had the discussion with @lukasa, but my understanding was that there would be a better way to client bootstrap for HTTP2 in the future, or it would be some improvement to the client bootstrap itself.

I can't tell you which though cause I didn't have that conversation :frowning:


(Cory Benfield) #61

There are two issues that I think we need to address here.

The first is that NIO has established the "bootstrap" as the primary configuration notion for new connections, whether that is inbound or outbound. However, NIO provides no way to say what a bootstrap actually is. NIO has 3 bootstraps in the core, and 2 bootstraps in swift-nio-transport-services, and while all of those have bootstrap in the name and do roughly similar jobs, there is no core common thing that all these bootstraps do.

Anyone who wants to write code that can "set up a connection" without worrying about exactly what that looks like needs to define their own protocol that indicates what they need and then conform the bootstraps they want to it. This isn't exactly ideal, and in the NIO 2 cycle I want us to put some time into defining a common notion of what a "bootstrap" really is, and work on users being able to write code that can operate generically on bootstraps.

The second problem is that HTTP2StreamMultiplexer, from some perspective, looks a lot like a bootstrap. In fact, it looks like a bootstrap and a bootstrap factory: it has a channelInitializer for inbound streams, and createStreamChannel which accepts a channelInitializer for outbound streams. This is pretty awkward, because it sorta walks and quacks a bit like a bootstrap, but without 100% buying into the idea.

So I think we should grow a wrapper around this type that actually gives you a bootstrap that conforms to the above notion of what a bootstrap is.

To be clear, however, none of this is likely to happen imminently: we're still a decent distance off from that. You gotta crawl before you can walk.