#APNS using HTTP/2 in C# using #AWS Lambda

By March 31st 2021, all users using the Apple Push Notification Service (APNS) must update to Apple’s new API, which is based on HTTP/2. If you don’t update, then it’s likely that your push notifications won’t get through.
If you are using C#, then you can use the dotAPNS NUGet package, which, from the client perspective is a tiny change, however, it does require .NET Core to run, which if your project is in .NET standard, then this could cause problems.
So, in this case, I decided to create a stand-alone .NET Core app using AWS Lambda and AWS API Gateway, which would be a middleware API between my main app and the APNS API.
My App -> AWS LAMBDA -> APNS
So, I created an AWS Lambda App in Visual Studio, and made sure I could publish to AWS Lambda. Which also gives you an API Gateway URL, in the format;
<ID>.execute-api.<Region>.amazonaws.com/Prod
I also needed to update the serverless.template file as follows
"Events": {
"RootGet": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "GET"
}
},
"RootPost": {
"Type": "Api",
"Properties": {
"Path": "/",
"Method": "POST"
}
}
And changed the Handler to
ApnsHttp2Serverless::ApnsHttp2Serverless.Functions::HttpRequest
(Where ApnsHttp2Serverless was the name of my project)
Now, my code to send the push notification is as follows;
private ApnsResponse Send(
byte[] certificate,
string certPassword,
string message,
string destination)
{
var x509 = new X509Certificate2(certificate, certPassword);
var applePushNotificationService = ApnsClient.CreateUsingCert(x509);
var push = new ApplePush(ApplePushType.Alert)
.AddAlert(message)
.AddToken(destination);
return applePushNotificationService.Send(push).Result;
}
Which is called from the lambda as follows;
public APIGatewayProxyResponse HttpRequest(APIGatewayProxyRequest request, ILambdaContext context)
{
context.Logger.LogLine(request.Body);
var jRequest = JObject.Parse(request.Body);
var certificate = jRequest["certificate"].ToObject();
var certPassword = jRequest["certPassword"].ToObject();
var message = jRequest["message"].ToObject();
var destination = jRequest["destination"].ToObject();
var sendResult = Send(certificate, certPassword, message, destination);
var response = new APIGatewayProxyResponse
{
StatusCode = (int)HttpStatusCode.OK,
Body = JsonConvert.SerializeObject(sendResult,Formatting.Indented),
Headers = new Dictionary { { "Content-Type", "application/json" } }
};
return response;
}
So, I pushed this code to AWS Lambda, and wrote the client as follows:
public static string Push(string destination, string message, string certFile, string certPassword)
{
var certificate = File.ReadAllBytes(certFile);
var oPayload = new
{
destination,
message,
certificate,
certPassword
};
var strPayload = JsonConvert.SerializeObject(oPayload, Formatting.Indented);
var wc = new WebClient();
var response = wc.UploadString("https://....execute-api.eu-west-1.amazonaws.com/Prod", strPayload);
return response;
}
So, Assuming you have the destination ID, and your certs set up correctly, this just works!
For anyone who is interested, here is the GitHub Repo of the AWS Lambda function:
https://github.com/infiniteloopltd/ApnsHttp2
And for those who happen to use PHP as the trigger to send push notifications, here is the client code in PHP:
<?php
$data = file_get_contents('cert.p12');
$cert64 = base64_encode($data);
$destination = "-apns-token-";
$password = "-cert-password-";
$url = "https://xxxxxx.execute-api.eu-west-1.amazonaws.com/Prod";
$ch = curl_init($url);
$oPayload = array(
'destination' => $destination,
'message' => 'hello there!',
'certificate' => $cert64,
'certPassword' => $password
);
$payload = json_encode($oPayload);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$content = curl_exec($ch);
if ($content === false) {
echo 'Curl error: ' . curl_error($ch);
}
curl_close($ch);
echo $content;
?>