Sending Push Notification with HTTP2 (and PHP)

pushesnotifhttp2

In order to send a push notification to an iOS device we must contact Apple servers and delegate them to do the work (go to this page to find more details on the entire process).

Some months ago Apple published a new  protocol that we can use to contact its servers. The previous protocol (the one we used for years) had in fact some problems.  In particular after sending a push, we had to wait at least 1 second for a (possible) Apple response. This is ok if we must send a limited number of pushes, but it is not acceptable when we need to send thousands of that (to send a push to 10.000 users we would wait almost 3 hours, spent almost entirely … waiting and idling!) .

The new way to communicate with Apple servers is (finally) fast and efficient and it uses the HTTP/2 protocol.

In this post I assume that you have already prepared your app  to receive Push Notification. There are many tutorials that explain this  process and I will not repeat them (this seems to me a good one).

Requirements

Before writing our code we need some libraries installed on the system.

The library we will use to send data to the network is curl (the library name is libcurl). The minimum version of libcurl supporting HTTP2 is 7.38.0 and must be compiled with the flag –with-nghttp2.

To see the version of your curl library open a terminal and digit the command:

(this is the output on my machine OSX 10.11.4).

Note that curl must be compiled with openssl version >=1.0.2 to fully support http/2, otherwise you will get the error: “?@@?HTTP/2 client preface string missing or corrupt…“.

The process of install the correct libcurl version is not straightforward. On MacOSX I found very useful the Homebrew tool.

Sending the push from the terminal

A  test to check the installed software (before entering the PHP part) is to try to send a push from the terminal.

The command is:

For example this command sends a push notification with message “Hi!” to my SamplePush app (bundle “it.tabasoft.samplepush”) to my device (token “dbdaearrea6aaaaww61859fb4rr074c1c388eftt348987447”).

I f the test is successfull the command prints nothing. If it prints something … the debug begins.

For example this is the response if I mistype the path of the certificate:

The PHP code

We need a version of PHP >= 5.5.24 that uses the correct version of libcurl.

I installed php 7 with Homebrew with the command:

We can verify the correct version of curl typing in the terminal the command

that will create a file “phpinfo.txt” in the current directory containing some php infos. Open the file and verify the lines:

OK!

So the following is the PHP function that sends the push notification:

Possible codes returned from Apple are:

Here are listed all the codes together with the descriptions (reason). In case of error the variable $result in the previous php code contains more details.

We call the function with the code:

The server Apple to connect to depends on the version of the app (debug or production).

Note that we open the connection, send the push and close it. But when you  send many pushes, be sure to open and close the connection only one time, as in:

This is important for two reasons:

  • the open and close are time consuming, calling them every time will heavily slow our code
  • we will get our IP banned if Apple considers this as a DOS attack

On my Mac (and a not so good network at this time: ~6Mb download, ~0.7Mb upload) my sendHTTP2Push takes an average 120ms to complete.

You can find here the code of this post here. (remember to set the parameters to that of your app).

Feel free to experiment the sendHTTP2Push code and let me know if all is clear (and working).

In the next post we will refactor the code and use Composer and Symfony/Console to build a command line tool.

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Valerio Ferrucci

Valerio Ferrucci (valfer) develops software on Apple Macintosh since the 1990s until today for MacOS, OSX and, since some years, iOS. He is also a Web (PHP/MySQL/JS/CSS) and Android Developer.

More Posts - Website

Follow Me:
TwitterFacebookLinkedIn

23 thoughts on “Sending Push Notification with HTTP2 (and PHP)”

  1. I have my server all set up correctly and I have tested this code without the “for” loop which sends one request and it works just fine and I receive it on my device. But, when I try to send multiple through the same connection I get an error about an unknown HTTP2 protocol. Has anything changed with the newer version of CURL that causes multiple requests during the same connection to throw an error?

    Does this code still work for you?

    I just tried your code directly and it will finish the first loop and send the message, but when it tries to send the second message, I get this error:

    PHP Fatal error: Uncaught exception ‘Exception’ with message ‘Curl failed with error: Unknown SSL protocol error in connection to api.development.push.apple.com:443

    1. Valerio, thank you very much for answering. I have also posted a detailed version of the problem with version information, sample code and curl verbose output on stack overflow at http://stackoverflow.com/questions/36611384/apns-provider-api-http-2-using-php-curl-causes-error-on-multiple-push-notificat

      I will try to look into your suggested link and see if I can figure out the problem. I am running the php script from the command line (not from apache) and it does send the first message successfully, but when it tries the second one, it fails.

    2. Also to add from phpinfo()

      PHP Version => 7.0.5-2+deb.sury.org~wily+1

      cURL support => enabled
      cURL Information => 7.48.0
      HTTP2 => Yes

  2. Hello there! Thanks for your post.

    I am trying to get my push notifications working but It’s seems that I am doing something wrong:

    when I try to execute command line like this:

    /usr/local/Cellar/curl/7.49.0/bin/curl -d ‘{“aps”:{“alert”:”[MESSAGE]”,”sound”:”default”}}’ –cert “/path/to/file.pem”:”” -H “apns-topic: [bundle.id]” –http2 https://api.development.push.apple.com/3/device/bdd07cc28c5c088cf5279200ef060c0a10397b4fc7b4ece1c4da99c67e82e12a

    I obtain {“reason”:”BadDeviceToken”}.

    I don’t know what the problem is beacuse de deviceToken it’s seems to be right.

    Any ideas?

    Thanks!

  3. I’ve change my environment and now the device token seems to be right.

    Problem with APNs over HTTP/2

    I’ve used the phonegap-push-plugin with phonegap bulid cli 5.4.1

    In Android seems to be alright and the push notifications are delivered sucessfully to the devices.

    In iOS, I am trying to send push notification from commad line with this order:

    curl -d ‘{“aps”:{“alert”:”Hi”,”sound”:”default”}}’ –cert “mycert.pem”:”” -H “apns-topic: es.mypush.example” –http2 https://api.push.apple.com/3/device/bdd07cc28c5c088cf5279200ef060c0a10397b4fc7b4ece1c4da99c67e82e12a

    But I am always receiving this error:

    {“reason”:”DeviceTokenNotForTopic”}

    In my config.xml I’ve used this bundle id <widget id="es.mypush.example" …

    with this command from the command line:

    Not sure which is the topic is registering the device in apns service, I suposed that the phonegap cli was sending the widget id, but it does not seems that.

    Can you help me please?

    Best Regards

      1. Thanks Valerio for your reply.

        I am not sure what’s is wrong, it’s obvius that the device token is not registered with the bundle.id

        However, this is de log of the curl command:

        $ /usr/local/bin/curl -v -d ‘{“aps”:{“alert”:”Test Push”,”sound”:”default”}}’ \
        > –cert /isotoolsmobiledev.pem:ClavePushAPN \
        > -H “apns-topic: org.isotools.mobile” –http2 \
        > https://api.development.push.apple.com/3/device/af5cdca3a1ed695b575bfa0d412933e566f81b2ee658ff1417195a37a8bb2425
        * Trying 17.172.238.203…
        * Connected to api.development.push.apple.com (17.172.238.203) port 443 (#0)
        * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
        * successfully set certificate verify locations:
        * CAfile: /etc/pki/tls/certs/ca-bundle.crt
        CApath: none
        * TLSv1.2 (OUT), TLS handshake, Client hello (1):
        * TLSv1.2 (IN), TLS handshake, Server hello (2):
        * TLSv1.2 (IN), TLS handshake, Certificate (11):
        * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
        * TLSv1.2 (IN), TLS handshake, Request CERT (13):
        * TLSv1.2 (IN), TLS handshake, Server finished (14):
        * TLSv1.2 (OUT), TLS handshake, Certificate (11):
        * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
        * TLSv1.2 (OUT), TLS handshake, CERT verify (15):
        * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
        * TLSv1.2 (OUT), TLS handshake, Finished (20):
        * TLSv1.2 (IN), TLS change cipher, Client hello (1):
        * TLSv1.2 (IN), TLS handshake, Finished (20):
        * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
        * Server certificate:
        * subject: CN=api.development.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
        * start date: Jun 19 01:49:43 2015 GMT
        * expire date: Jul 18 01:49:43 2017 GMT
        * subjectAltName: host “api.development.push.apple.com” matched cert’s “api.development.push.apple.com”
        * issuer: CN=Apple IST CA 2 – G1; OU=Certification Authority; O=Apple Inc.; C=US
        * SSL certificate verify ok.
        > POST /3/device/af5cdca3a1ed695b575bfa0d412933e566f81b2ee658ff1417195a37a8bb2425 HTTP/1.1
        > Host: api.development.push.apple.com
        > User-Agent: curl/7.49.0
        > Accept: */*
        > apns-topic: org.isotools.mobile
        > Content-Length: 47
        > Content-Type: application/x-www-form-urlencoded
        >
        * upload completely sent off: 47 out of 47 bytes
        * TLSv1.2 (IN), TLS alert, Client hello (1):
        * Connection #0 to host api.development.push.apple.com left intact
        ▒@@▒HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 504f5354202f332f6465766963652f616635636463613361[vtellez@localhost ~]$ PuTTY

    1. hello I have got the same response from aspn, it’s {“reason”:”BadDeviceToken”}, how can I get the suitable device token?

  4. Hi
    Thanks for your blog, it is so difficult to find post talking about push notification for iOS.
    I have sent with success push from terminal with this command :curl -v -d ‘{“aps”:{“alert”:”hi”,”sound”:”default”}}’ –cert “myKey.pem” -H “apns-topic:com.my-Topic” –http2 https://api.development.push.apple.com/3/device/6378ab97095297b832cfdcb269b48u8b671bc9d81c3f43df5b5f38dbdafecda8

    but when i want tu use you php model, i received always :
    Response from apple -> 0

    and no push sent.
    please can you tell me why iin command line it is sent with success but not with php ?

    thanks for hekl and sorry for my poor englsih.

    Sandy

    1. i have put some echo, and here what i have received :
      �@@�HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 504f5354202f332f6465766963652f363334396162393730status0Response from apple -> 0

      1. Are you able to fix this problem ?

        I am also getting same error message (?@@?HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 504f5354202f332f6465766963652f393762353130633536bool(true))

  5. all requirements were done but your php code did not work for me.
    for help some others with the same issue, here the simple short php code that worked fine in my case :
    function sendPushiOS($tAlert,$tToken){

    $device_token = $tToken;
    if(defined(‘CURL_HTTP_VERSION_2_0’))
    {
    $pem_file = ‘myKey.pem’;
    $apns_topic = ‘com.myTopic’;

    $sample_alert = ‘{“aps”:{“alert”:”‘.$tAlert.'”,”sound”:”default”}}’;

    $url = “https://api.development.push.apple.com/3/device/$device_token” ;

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $sample_alert);
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(“apns-topic: $apns_topic”));
    curl_setopt($ch, CURLOPT_SSLCERT, $pem_file);

    $response = curl_exec($ch);
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    //var_dump($response);

    }
    }
    hope this helps.

    thanks
    Sandy

  6. Valerio Ferrucci ,

    When i run the below command in terminal i am receiving the push notification .

    curl -d ‘{“aps”:{“alert”:”Hi!”,”sound”:”default”}}’ –cert ck.pem:”” -H “apns-topic: com.SMIT.RealEstate” –http2 https://api.development.push.apple.com/3/device/97b510c565e746a7d1fc120e6a4ef1b7f29e136a01414544a4f9628dd9e09c69

    When i try to execute the Php code I am getting -?@@?HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 504f5354202f332f6465766963652f393762353130633536bool(true)

    According to your paragraph I installed the curl –

    curl 7.49.1 (x86_64-apple-darwin15.5.0) libcurl/7.49.1 OpenSSL/1.0.2h zlib/1.2.5 nghttp2/1.11.0
    Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
    Features: IPv6 Largefile NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets

    But when i check php -i > phpinfo.txt

    cURL support enabled
    cURL Information 7.43.0
    HTTP2 => No.

    How do i change native MAC apache. ?

  7. Hi
    Thanks for your blog.

    I have sent with success push from terminal with this command :curl -v -d ‘{“aps”:{“alert”:”hi”,”sound”:”default”}}’ –cert “ck.pem” -H “apns-topic:com.my-Topic” –http2 https://api.development.push.apple.com/3/device/6378ab97095297b832cfdcb269b48u8b671bc9d81c3f43df5b5f38dbdafecda8

    but when i try to use your php model, i received always :
    ?@@.{“reason”:”BadCertificateEnvironment”Response from apple -> 0

    and no push notification sent.

    Could you share with me the reason?

    Des

  8. dear all,
    if you are not able to send push with php code that Valerio gave please try my php code. it will work.
    good luck
    sandy

Leave a Reply

Your email address will not be published. Required fields are marked *