locked
Notification Hub Delay Issue RRS feed

  • Question

  • Hi,

    We are using notification hub to send messages from Raspberry pi to android application.

    I am using python client app using REST API as per this post How to use Notification Hubs from Python

    Everything works fine but there is, almost 2 seconds of delay in every notification!! How to avoid this delay and get a message within same second.

    Notofication hub:  cavenotificationhub
    Namespace: CaveNotificationHub-ns

    My python script.

    import time
    import hmac
    import base64
    import hashlib
    import json
    import urllib.parse
    import http.client
    
    
    class Notification:
        def __init__(self, notification_format=None, payload=None, debug=0):
            valid_formats = ['template', 'apple', 'gcm', 'windows', 'windowsphone', "adm", "baidu"]
            if not any(x in notification_format for x in valid_formats):
                raise Exception(
                    "Invalid Notification format. " +
                    "Must be one of the following - 'template', 'apple', 'gcm', 'windows', 'windowsphone', 'adm', 'baidu'")
    
            self.format = notification_format
            self.payload = payload
    
            # array with keynames for headers
            # Note: Some headers are mandatory: Windows: X-WNS-Type, WindowsPhone: X-NotificationType
            # Note: For Apple you can set Expiry with header: ServiceBusNotification-ApnsExpiry
            # in W3C DTF, YYYY-MM-DDThh:mmTZD (for example, 1997-07-16T19:20+01:00).
            self.headers = None
    
    
    class NotificationHub:
        API_VERSION = "?api-version=2013-10"
        DEBUG_SEND = "&test"
    
        def __init__(self, connection_string=None, hub_name=None, debug=0):
            self.HubName = hub_name
            self.Debug = debug
    
            # Parse connection string
            parts = connection_string.split(';')
            if len(parts) != 3:
                raise Exception("Invalid ConnectionString.")
    
            for part in parts:
                if part.startswith('Endpoint'):
                    self.Endpoint = 'https' + part[11:]
                if part.startswith('SharedAccessKeyName'):
                    self.SasKeyName = part[20:]
                if part.startswith('SharedAccessKey'):
                    self.SasKeyValue = part[16:]
    
        @staticmethod
        def get_expiry():
            # By default returns an expiration of 5 minutes (=300 seconds) from now
            return int(round(time.time() + 300))
    
        @staticmethod
        def encode_base64(data):
            return base64.b64encode(data)
    
        def sign_string(self, to_sign):
            key = self.SasKeyValue.encode('utf-8')
            to_sign = to_sign.encode('utf-8')
            signed_hmac_sha256 = hmac.HMAC(key, to_sign, hashlib.sha256)
            digest = signed_hmac_sha256.digest()
            encoded_digest = self.encode_base64(digest)
            return encoded_digest
    
        def generate_sas_token(self):
            target_uri = self.Endpoint + self.HubName
            my_uri = urllib.parse.quote(target_uri, '').lower()
            expiry = str(self.get_expiry())
            to_sign = my_uri + '\n' + expiry
            signature = urllib.parse.quote(self.sign_string(to_sign))
            auth_format = 'SharedAccessSignature sig={0}&se={1}&skn={2}&sr={3}'
            sas_token = auth_format.format(signature, expiry, self.SasKeyName, my_uri)
            return sas_token
    
        def make_http_request(self, url, payload, headers):
            parsed_url = urllib.parse.urlparse(url)
            connection = http.client.HTTPSConnection(parsed_url.hostname, parsed_url.port)
    
            if self.Debug > 0:
                connection.set_debuglevel(self.Debug)
                # adding this querystring parameter gets detailed information about the PNS send notification outcome
                url += self.DEBUG_SEND
                print("--- REQUEST ---")
                print("URI: " + url)
                print("Headers: " + json.dumps(headers, sort_keys=True, indent=4, separators=(' ', ': ')))
                print("--- END REQUEST ---\n")
    
            connection.request('POST', url, payload, headers)
            response = connection.getresponse()
    
            if self.Debug > 0:
                # print out detailed response information for debugging purpose
                print("\n\n--- RESPONSE ---")
                print(str(response.status) + " " + response.reason)
                print(response.msg)
                print(response.read())
                print("--- END RESPONSE ---")
    
            elif response.status != 201:
                # Successful outcome of send message is HTTP 201 - Created
                raise Exception(
                    "Error sending notification. Received HTTP code " + str(response.status) + " " + response.reason)
    
            connection.close()
    
        def send_notification(self, notification, tag_or_tag_expression=None):
            url = self.Endpoint + self.HubName + '/messages' + self.API_VERSION
    
            json_platforms = ['template', 'apple', 'gcm', 'adm', 'baidu']
    
            if any(x in notification.format for x in json_platforms):
                content_type = "application/json"
                payload_to_send = json.dumps(notification.payload)
            else:
                content_type = "application/xml"
                payload_to_send = notification.payload
    
            headers = {
                'Content-type': content_type,
                'Authorization': self.generate_sas_token(),
                'ServiceBusNotification-Format': notification.format
            }
    
            if isinstance(tag_or_tag_expression, set):
                tag_list = ' || '.join(tag_or_tag_expression)
            else:
                tag_list = tag_or_tag_expression
    
            # add the tags/tag expressions to the headers collection
            if tag_list != "":
                headers.update({'ServiceBusNotification-Tags': tag_list})
    
            # add any custom headers to the headers collection that the user may have added
            if notification.headers is not None:
                headers.update(notification.headers)
    
            self.make_http_request(url, payload_to_send, headers)
    
        def send_apple_notification(self, payload, tags=""):
            nh = Notification("apple", payload)
            self.send_notification(nh, tags)
    
        def send_gcm_notification(self, payload, tags=""):
            nh = Notification("gcm", payload)
            self.send_notification(nh, tags)
    
        def send_adm_notification(self, payload, tags=""):
            nh = Notification("adm", payload)
            self.send_notification(nh, tags)
    
        def send_baidu_notification(self, payload, tags=""):
            nh = Notification("baidu", payload)
            self.send_notification(nh, tags)
    
        def send_mpns_notification(self, payload, tags=""):
            nh = Notification("windowsphone", payload)
    
            if "<wp:Toast>" in payload:
                nh.headers = {'X-WindowsPhone-Target': 'toast', 'X-NotificationClass': '2'}
            elif "<wp:Tile>" in payload:
                nh.headers = {'X-WindowsPhone-Target': 'tile', 'X-NotificationClass': '1'}
    
            self.send_notification(nh, tags)
    
        def send_windows_notification(self, payload, tags=""):
            nh = Notification("windows", payload)
    
            if "<toast>" in payload:
                nh.headers = {'X-WNS-Type': 'wns/toast'}
            elif "<tile>" in payload:
                nh.headers = {'X-WNS-Type': 'wns/tile'}
            elif "<badge>" in payload:
                nh.headers = {'X-WNS-Type': 'wns/badge'}
    
            self.send_notification(nh, tags)
    
        def send_template_notification(self, properties, tags=""):
            nh = Notification("template", properties)
            self.send_notification(nh, tags)
    
    
    
    isDebug = True
    myConnectionString =  "connection string"
    hub = NotificationHub(myConnectionString, "cavenotificationhub", isDebug)
    data = {}
    data['response'] = 'data: R1|X1|S1,1|$'
    json_data = json.dumps(data,separators=(',',':'))
    print(json_data)
    #gcm_payload = {"response":R1|X1|S1,1}
    val= "R1|X1|S1,1|$"
    gcm_payload = { 'data' : {'response': ''+val+''}}
    hub.send_gcm_notification(gcm_payload


    Attached logs:

    Windows 10 Surface Pro 3 - Python 3.4 script
    
    **** Send GCM Notitification START>  2016-05-08 10:42:07.883226
    *** make_http_request connection OPEN>  2016-05-08 10:42:08.139328
    
    --- REQUEST ---
    #Request header
    --- END REQUEST ---
    
    *** make_http_request START>  2016-05-08 10:42:08.165356
    #Sending request to Azure
    *** make_http_request END>  2016-05-08 10:42:09.016024
    
    --- RESPONSE ---
    #Response received from Azure
    --- END RESPONSE ---
    
    *** make_http_request connection CLOSE>  2016-05-08 10:42:09.184785
    **** Send GCM Notitification END>  2016-05-08 10:42:09.188788
    
    #### Raspberry Pi Model B+ V1.2 - Python 2.7 script
    
    ('*** Send GCM Notitification START> ', '2016-05-08 10:46:32.692844') ('*** make_http_request connection OPEN> ', '2016-05-08 10:46:32.698456')
    
    --- REQUEST ---
    #Request header
    --- END REQUEST ---
    
    ('*** make_http_request START> ', '2016-05-08 10:46:32.705946')
    #Sending request to Azure ('*** make_http_request END> ', '2016-05-08 10:46:39.557759')
    
    --- RESPONSE ---
    #Response received from Azure
    --- END RESPONSE ---
    
    ('*** make_http_request connection CLOSE> ', '2016-05-08 10:46:39.569713') ('*** Send GCM Notitification END> ', '2016-05-08 10:46:39.570986')
    
    #### Raspberry Pi 2 Model B V1.1 - Python 2.7 script
    
    ('*** Send GCM Notitification START> ', '2016-05-08 05:36:49.721024') ('*** make_http_request connection OPEN> ', '2016-05-08 05:36:49.732056')
    
    --- REQUEST ---
    #Request header
    --- END REQUEST ---
    
    ('*** make_http_request START> ', '2016-05-08 05:36:49.733069')
    #Sending request to Azure ('*** make_http_request END> ', '2016-05-08 05:36:50.741605')
    
    --- RESPONSE ---
    #Response received from Azure
    --- END RESPONSE ---
    
    ('*** make_http_request connection CLOSE> ', '2016-05-08 05:36:50.746248') ('*** Send GCM Notitification END> ', '2016-05-08 05:36:50.747000')

    Satish Gunjal









    Saturday, May 7, 2016 10:21 AM

All replies

  • Unfortunately, there's no way to guarantee notification delivery time-frame because your platform PNS (Google in this case) itself doesn't guarantee it. Even if Notification Hubs processes it in less than a second and sends the request to GCM right away, then GCM itself could take however much it needs to process their queue.

    Your NH API call seems to be properly designed. If you're sure that your code that leads to the call doesn't take much time, then I don't see a way to speed it up.

    One thing you can try to do just to compare is to call GCM directly (just hardcode a single token) and see how much time it takes. If it's a second or more, then unfortunately there's nothing I can think of that you can do.

    Sunday, May 8, 2016 7:53 PM
  • We did testing with Three different platforms and our logs are attached in the OP. As per the timestamps we are getting a approx 6 seconds delay (almost 1-2 seconds delay is acceptable) on Raspberry Pi Model B+ V1.2. Can you please share your insight on how to reduce this delay?

    -- Satish Gunjal

    Monday, May 9, 2016 5:20 AM
  • It is important to understand that there are two parts of the equation -- (1) what is going on on the device itself and (2) what is going on on Azure side when you make the API call.

    I can't help you with (1) because I don't know anything about your domain, what processes are being executed on the device etc.

    But (2) actually looks good. If I understand your logs correctly, then even in the part that you attached most of the calls to Azure take less than 1 second. I don't think you can improve that. So I would try to understand whether it's possible to make sure that your RPi side code can build the payload more efficiently. It's possible that the solution is not directly in the code, but somehow re-configuring the device. Or it could be the network or networking stack.

    • Edited by Nikita G Monday, May 9, 2016 4:22 PM
    Monday, May 9, 2016 4:20 PM
  • <g class="gr_ gr_33 gr-alert gr_spell gr_run_anim ContextualSpelling" data-gr-id="33" id="33">Its</g> <g class="gr_ gr_5 gr-alert gr_spell gr_run_anim ContextualSpelling ins-del multiReplace" data-gr-id="5" id="5">RPi</g> issue. Thanks.

    -- Satish Gunjal

    Tuesday, May 10, 2016 1:00 PM