TLDR; Execute a cURL command from within Swift
A couple weeks ago I decided to learn the Vapor 3 framework. I wanted to be capable of developing a backend API for future projects without having to rely on my goto, Parse Server. This is a quick how-to guide to show you that its possible! All the code is available in my GitHub repo linked at the bottom.
To get some experience with the framework and see some use case examples I purchased the “Server Side Swift with Vapor” e-book from Ray Wenderlich’s website. I read through the book to learn the basics. The next step was to begin trying to port functionality I was used to with Parse Server to a Vapor 3 app. When it came tome to implement push notifications, I ran into a roadblock.
Apple’s APNs requires connections to be made using HTTP/2, but SwiftNIO (Vapor 3’s core networking dependency) does not yet support the HTTP/2 protocol. I thought I might have to use some 3rd party solution but as I didn’t want that kind of dependency. I decided to look through Apple’s developer documentation (APNs — Apple Developer) to see what would be required to use cURL to connect with APNs.
To start, you will need a paid Apple Developer account and a demo app that you have configured push notifications for. You will need to run the demo app and get the device token that will be sent later for testing APNs.
An HTTP/2 cURL request will required a certificate. The first step is to generate the required certificates from your Apple developer account. I won’t go through how to do this as there are already several tutorials. The .cer file and .p12 file will need to be merged. To do so, follow this bash script:
# Convert the .cer file into a .pem file:
openssl x509 -in aps_development.cer -inform der -out cert.pem
# Convert the private key’s .p12 file into a .pem file:
openssl pkcs12 -nocerts -in aps_development.p12 -out key.pem
# Finally, combine the certificate and key into a single .pem file
cat cert.pem key.pem > aps_development.pem
With a new public/private combined certificate we can now try using cURL to communicate with APNs.
curl -d <apns_payload> -H "apns-topic:<bundleId> -H "apns-expiration: 1" -H "apns-priority: 10" -http2-prior-knowledge -cert aps_development.pem:<password> https://api.development.push.apple.com/3/device/<device_token>
SUCCESS! I was able to get a notification on the demo app I created earlier. The last step was to make a convenient Service I could inject into my Vapor 3 app that would start a cURL request in the background. You can find it here:
This service can execute bash commands with provided arguements. The arguments are what we used previously in our testing. From a Vapor 3 request simply
let shell = try req.make(Shell.self)let arguments = [“-d”, jsonAPNSPayloadString, “-H”, “apns-topic:\(bundleId)”, “-H”, “apns-expiration: 1”, “-H”, “apns-priority: 10”, “ — http2-prior-knowledge”, “ — cert”, “\(certPath):\(password)”, apnsURL + token]try shell.execute(commandName: “curl”, arguments: arguments)
Remember that in your API you probably want to record each of your users installations so your database can store their device tokens!
This was just a quick summary that I hope shows you were you can get started! In time, SwiftNIO will support HTTP/2 and when they do so will Vapor 3. A great repo (https://github.com/vapor-community/apns)already exists in the community for when it does. I use it in my app to make encoding/decoding of the APNs requests easy.
If you would like to see the full source code that I use to make push notifications possible with my Vapor 3 app, please checkout and leave a star on the repo listed below!