2014-05-20 37 views
7

Tôi đang sử dụng PyAPN để gửi Thông báo đẩy iOS. Tôi cũng sáp nhập các bản sửa lỗi cho vấn đề được biết sauPyAPN gửi thông báo đẩy tới nhiều mã thông báo thiết bị không hoạt động

https://github.com/djacobs/PyAPNs/issues/13

Bây giờ, các mã đang làm việc tốt Nếu tôi gửi thông báo đến một thiết bị cá nhân. Nhưng tôi có một danh sách cho các thẻ thiết bị và tôi phải gửi thông báo cho tất cả chúng một. Vì mục đích này, tôi lặp đi lặp lại đơn giản qua cuộc gọi thông báo duy nhất như sau:

def send_notifications(self, tokens, payload): 
    for token in tokens: 
     try : 
      logging.info("Sending Notification to Token: %s" % (token)) 
      self.send_notification(token, payload)     
     except Exception, e: 
      self._disconnect() 
      logging.info("Exception: %s" % (str(e))) 
      logging.info("Token: %s" % (token)) 

Nhưng vấn đề là mã trên không hoạt động. Mã thông báo thiết bị đang hoạt động tốt cho từng lần đẩy riêng lẻ, không hoạt động khi sử dụng mã trên. Ví dụ: mã thông báo thiết bị 45183e79de216ea05e3d6e83083476ebeb64caf733188bb77b0b1d268526c815 hoạt động tốt nhưng không hoạt động trong trường hợp gửi hàng loạt. Để tham khảo Tôi đặt các tập tin APNS và các bản ghi máy chủ cục bộ:

apns.py

# PyAPNs was developed by Simon Whitaker <[email protected]> 
# Source available at https://github.com/simonwhitaker/PyAPNs 
# 
# PyAPNs is distributed under the terms of the MIT license. 
# 
# Copyright (c) 2011 Goo Software Ltd 
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy of 
# this software and associated documentation files (the "Software"), to deal in 
# the Software without restriction, including without limitation the rights to 
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
# of the Software, and to permit persons to whom the Software is furnished to do 
# so, subject to the following conditions: 
# 
# The above copyright notice and this permission notice shall be included in all 
# copies or substantial portions of the Software. 
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
# SOFTWARE. 

from binascii import a2b_hex, b2a_hex 
from datetime import datetime, timedelta 
from time import mktime 
from socket import socket, AF_INET, SOCK_STREAM, timeout 
from struct import pack, unpack 

import select 

try: 
    from ssl import wrap_socket 
    from ssl import SSLError, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE 
except ImportError: 
    from socket import ssl as wrap_socket 

try: 
    import json 
except ImportError: 
    import simplejson as json 

from apnserrors import * 

import logging 
import StringIO 

MAX_PAYLOAD_LENGTH = 256 
TIMEOUT = 60 
ERROR_RESPONSE_LENGTH = 6 

class APNs(object): 
    """A class representing an Apple Push Notification service connection""" 

    def __init__(self, use_sandbox=False, cert_file=None, key_file=None, enhanced=True): 
     """ 
     Set use_sandbox to True to use the sandbox (test) APNs servers. 
     Default is False. 
     """ 
     super(APNs, self).__init__() 
     self.use_sandbox = use_sandbox 
     self.cert_file = cert_file 
     self.key_file = key_file 
     self.enhanced = enhanced 
     self._feedback_connection = None 
     self._gateway_connection = None 

    @staticmethod 
    def unpacked_uchar_big_endian(byte): 
     """ 
     Returns an unsigned char from a packed big-endian (network) byte 
     """ 
     return unpack('>B', byte)[0] 

    @staticmethod 
    def packed_ushort_big_endian(num): 
     """ 
     Returns an unsigned short in packed big-endian (network) form 
     """ 
     return pack('>H', num) 

    @staticmethod 
    def unpacked_ushort_big_endian(bytes): 
     """ 
     Returns an unsigned short from a packed big-endian (network) byte 
     array 
     """ 
     return unpack('>H', bytes)[0] 

    @staticmethod 
    def packed_uint_big_endian(num): 
     """ 
     Returns an unsigned int in packed big-endian (network) form 
     """ 
     return pack('>I', num) 

    @staticmethod 
    def unpacked_uint_big_endian(bytes): 
     """ 
     Returns an unsigned int from a packed big-endian (network) byte array 
     """ 
     return unpack('>I', bytes)[0] 

    @property 
    def feedback_server(self): 
     if not self._feedback_connection: 
      self._feedback_connection = FeedbackConnection(
       use_sandbox = self.use_sandbox, 
       cert_file = self.cert_file, 
       key_file = self.key_file 
      ) 
     return self._feedback_connection 

    @property 
    def gateway_server(self): 
     if not self._gateway_connection: 
      self._gateway_connection = GatewayConnection(
       use_sandbox = self.use_sandbox, 
       cert_file = self.cert_file, 
       key_file = self.key_file, 
       enhanced = self.enhanced 
      ) 
     return self._gateway_connection 


class APNsConnection(object): 
    """ 
    A generic connection class for communicating with the APNs 
    """ 
    def __init__(self, cert_file=None, key_file=None, enhanced=True): 
     super(APNsConnection, self).__init__() 
     self.cert_file = cert_file 
     self.key_file = key_file 
     self.enhanced = enhanced 
     self._socket = None 
     self._ssl = None 

    def __del__(self): 
     self._disconnect(); 

    def _connect(self): 
     # Establish an SSL connection 
     self._socket = socket(AF_INET, SOCK_STREAM) 
     self._socket.connect((self.server, self.port)) 

     if self.enhanced: 
      self._ssl = wrap_socket(self._socket, StringIO.StringIO(self.key_file), StringIO.StringIO(self.cert_file), 
            do_handshake_on_connect=False) 
      self._ssl.setblocking(0) 
      while True: 
       try: 
        self._ssl.do_handshake() 
        break 
       except SSLError, err: 
        if SSL_ERROR_WANT_READ == err.args[0]: 
         select.select([self._ssl], [], []) 
        elif SSL_ERROR_WANT_WRITE == err.args[0]: 
         select.select([], [self._ssl], []) 
        else: 
         raise 
     else: 
      self._ssl = wrap_socket(self._socket, StringIO.StringIO(self.key_file), StringIO.StringIO(self.cert_file)) 

    def _disconnect(self): 
     if self._socket: 
      self._socket.close() 
      self._ssl = None 

    def _connection(self): 
     if not self._ssl: 
      self._connect() 
     return self._ssl 

    def read(self, n=None): 
     return self._connection().recv(n) 

    def recvall(self, n): 
     data = "" 
     while True: 
      more = self._connection().recv(n - len(data)) 
      data += more 
      if len(data) >= n: 
       break 
      rlist, _, _ = select.select([self._connection()], [], [], TIMEOUT) 
      if not rlist: 
       raise timeout 

     return data 

    def write(self, string): 
     if self.enhanced: # nonblocking socket 
      rlist, _, _ = select.select([self._connection()], [], [], 0) 

      if rlist: # there's error response from APNs 
       buff = self.recvall(ERROR_RESPONSE_LENGTH) 
       if len(buff) != ERROR_RESPONSE_LENGTH: 
        return None 

       command = APNs.unpacked_uchar_big_endian(buff[0]) 

       if 8 != command: 
        self._disconnect() 
        raise UnknownError(0) 

       status = APNs.unpacked_uchar_big_endian(buff[1]) 
       identifier = APNs.unpacked_uint_big_endian(buff[2:6]) 

       self._disconnect() 

       raise { 1: ProcessingError, 
         2: MissingDeviceTokenError, 
         3: MissingTopicError, 
         4: MissingPayloadError, 
         5: InvalidTokenSizeError, 
         6: InvalidTopicSizeError, 
         7: InvalidPayloadSizeError, 
         8: InvalidTokenError }.get(status, UnknownError)(identifier) 

      _, wlist, _ = select.select([], [self._connection()], [], TIMEOUT) 
      if wlist: 
       return self._connection().sendall(string) 
      else: 
       self._disconnect() 
       raise timeout 

     else: # not-enhanced format using blocking socket 
      return self._connection().sendall(string) 

class PayloadAlert(object): 
    def __init__(self, body, action_loc_key=None, loc_key=None, 
       loc_args=None, launch_image=None): 
     super(PayloadAlert, self).__init__() 
     self.body = body 
     self.action_loc_key = action_loc_key 
     self.loc_key = loc_key 
     self.loc_args = loc_args 
     self.launch_image = launch_image 

    def dict(self): 
     d = { 'body': self.body } 
     if self.action_loc_key: 
      d['action-loc-key'] = self.action_loc_key 
     if self.loc_key: 
      d['loc-key'] = self.loc_key 
     if self.loc_args: 
      d['loc-args'] = self.loc_args 
     if self.launch_image: 
      d['launch-image'] = self.launch_image 
     return d 

class Payload(object): 
    """A class representing an APNs message payload""" 
    def __init__(self, alert=None, badge=None, sound=None, custom={}): 
     super(Payload, self).__init__() 
     self.alert = alert 
     self.badge = badge 
     self.sound = sound 
     self.custom = custom 
     self._check_size() 

    def dict(self): 
     """Returns the payload as a regular Python dictionary""" 
     d = {} 
     if self.alert: 
      # Alert can be either a string or a PayloadAlert 
      # object 
      if isinstance(self.alert, PayloadAlert): 
       d['alert'] = self.alert.dict() 
      else: 
       d['alert'] = self.alert 
     if self.sound: 
      d['sound'] = self.sound 
     if self.badge is not None: 
      d['badge'] = int(self.badge) 

     d = { 'aps': d } 
     d.update(self.custom) 
     return d 

    def json(self): 
     return json.dumps(self.dict(), separators=(',',':'), ensure_ascii=False).encode('utf-8') 

    def _check_size(self): 
     if len(self.json()) > MAX_PAYLOAD_LENGTH: 
      raise PayloadTooLargeError() 

    def __repr__(self): 
     attrs = ("alert", "badge", "sound", "custom") 
     args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs]) 
     return "%s(%s)" % (self.__class__.__name__, args) 


class FeedbackConnection(APNsConnection): 
    """ 
    A class representing a connection to the APNs Feedback server 
    """ 
    def __init__(self, use_sandbox=False, **kwargs): 
     super(FeedbackConnection, self).__init__(**kwargs) 
     self.server = (
      'feedback.push.apple.com', 
      'feedback.sandbox.push.apple.com')[use_sandbox] 
     self.port = 2196 

    def _chunks(self): 
     BUF_SIZE = 4096 
     while 1: 
      data = self.read(BUF_SIZE) 
      yield data 
      if not data: 
       break 

    def items(self): 
     """ 
     A generator that yields (token_hex, fail_time) pairs retrieved from 
     the APNs feedback server 
     """ 
     buff = '' 
     for chunk in self._chunks(): 
      buff += chunk 

      # Quit if there's no more data to read 
      if not buff: 
       break 

      # Sanity check: after a socket read we should always have at least 
      # 6 bytes in the buffer 
      if len(buff) < 6: 
       break 

      while len(buff) > 6: 
       token_length = APNs.unpacked_ushort_big_endian(buff[4:6]) 
       bytes_to_read = 6 + token_length 
       if len(buff) >= bytes_to_read: 
        fail_time_unix = APNs.unpacked_uint_big_endian(buff[0:4]) 
        fail_time = datetime.utcfromtimestamp(fail_time_unix) 
        token = b2a_hex(buff[6:bytes_to_read]) 

        yield (token, fail_time) 

        # Remove data for current token from buffer 
        buff = buff[bytes_to_read:] 
       else: 
        # break out of inner while loop - i.e. go and fetch 
        # some more data and append to buffer 
        break 

class GatewayConnection(APNsConnection): 
    """ 
    A class that represents a connection to the APNs gateway server 
    """ 
    def __init__(self, use_sandbox=False, **kwargs): 
     super(GatewayConnection, self).__init__(**kwargs) 
     self.server = (
      'gateway.push.apple.com', 
      'gateway.sandbox.push.apple.com')[use_sandbox] 
     self.port = 2195 

    def _get_notification(self, token_hex, payload): 
     """ 
     Takes a token as a hex string and a payload as a Python dict and sends 
     the notification 
     """ 
     token_bin = a2b_hex(token_hex) 
     token_length_bin = APNs.packed_ushort_big_endian(len(token_bin)) 
     payload_json = payload.json() 
     payload_length_bin = APNs.packed_ushort_big_endian(len(payload_json)) 

     notification = ('\0' + token_length_bin + token_bin 
         + payload_length_bin + payload_json) 

     return notification 

    def _get_enhanced_notification(self, token_hex, payload, identifier, expiry): 
     """ 
     Takes a token as a hex string and a payload as a Python dict and sends 
     the notification in the enhanced format 
     """ 
     token_bin = a2b_hex(token_hex) 
     token_length_bin = APNs.packed_ushort_big_endian(len(token_bin)) 
     payload_json = payload.json() 
     payload_length_bin = APNs.packed_ushort_big_endian(len(payload_json)) 
     identifier_bin = APNs.packed_uint_big_endian(identifier) 
     expiry_bin = APNs.packed_uint_big_endian(int(mktime(expiry.timetuple()))) 

     notification = ('\1' + identifier_bin + expiry_bin + token_length_bin + token_bin 
         + payload_length_bin + payload_json) 

     return notification 

    def send_notification(self, token_hex, payload, identifier=None, expiry=None): 
     if self.enhanced: 
      if not expiry: # by default, undelivered notification expires after 30 seconds 
       expiry = datetime.utcnow() + timedelta(30) 
      if not identifier: 
       identifier = 0 

      logging.info("self.write(self._get_enhanced_notification())")  
      self.write(self._get_enhanced_notification(token_hex, payload, identifier, 
                 expiry)) 
     else: 
      logging.info("self.write(self._get_notification(token_hex, payload))") 
      self.write(self._get_notification(token_hex, payload)) 

    def send_notifications(self, tokens, payload): 
     for token in tokens: 
      try : 
       logging.info("Sending Notification to Token: %s" % (token)) 
       self.send_notification(token, payload)     
      except Exception, e: 
       self._disconnect() 
       logging.info("Exception: %s" % (str(e))) 
       logging.info("Token: %s" % (token)) 

server Logs:

Sending Notification to Token: 99f65209a76ed41ce50c73198d72048f94085dd2a2dde0245110dccccda86fd0 
    I 2014-05-20 05:18:24.029 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:24.437 
    Sending Notification to Token: 2230c2421e3b83cd6b16a69c6ba528230b11d29183b0bfb73b159816237b17ce 
    I 2014-05-20 05:18:24.437 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:24.442 
. 
. 
. 
    Sending Notification to Token: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:24.986 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:24.991 
    Sending Notification to Token: 2230c2421e3b83cd6b16a69c6ba528230b11d29183b0bfb73b159816237b17ce 
    I 2014-05-20 05:18:24.991 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:24.996 
    Sending Notification to Token: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:24.996 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.004 
    Sending Notification to Token: 1bacfcb6b80868493b236ec6131bed11918c935752734701b89b060045e6b006 
    I 2014-05-20 05:18:25.004 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.021 
    Sending Notification to Token: 35bd8dda849e30a85b12b2a0e274b9507db7c7f365aa5a27f3fbda316052246e 
    I 2014-05-20 05:18:25.021 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.054 
    Sending Notification to Token: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:25.054 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.059 
    Sending Notification to Token: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:25.059 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.064 
    Sending Notification to Token: 1bacfcb6b80868493b236ec6131bed11918c935752734701b89b060045e6b006 
    I 2014-05-20 05:18:25.064 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.068 
    Sending Notification to Token: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:25.069 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.073 
    Sending Notification to Token: d25a34a1fd031abf3fbfb5916af415206048fb6343586b91b96d0506eb28cb54 
    I 2014-05-20 05:18:25.073 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:25.078 

. 
. 
. 
    Sending Notification to Token: 45183e79de216ea05e3d6e83083476ebeb64caf733188bb77b0b1d268526c815 
    I 2014-05-20 05:18:30.145 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:30.152 
    Sending Notification to Token: b57b2d96a4b4db552137bcea4fd58f3ce53393fbe7c828b617306df2922dbfd3 
    I 2014-05-20 05:18:30.152 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:30.159 
    Sending Notification to Token: 82acbf3dc5da893d2f4d551df10c129c8c192efe335cc608d291dc922e947615 
    I 2014-05-20 05:18:30.159 
    self.write(self._get_enhanced_notification()) 
    I 2014-05-20 05:18:30.166 

    feedback token_hex: 0cf58d47f435f170473b63e1852b637c11935b6e38d41321fe98911eaf898301 
    I 2014-05-20 05:18:31.754 
    feedback token_hex: 0d344046d62f808c30bc5670cbb7dc478cca0a9798830d22f8f6ed27c76923c6 
    I 2014-05-20 05:18:31.754 
    feedback token_hex: 2230c2421e3b83cd6b16a69c6ba528230b11d29183b0bfb73b159816237b17ce 
    I 2014-05-20 05:18:31.754 
    feedback token_hex: 349c54d18bb1ee014dc84f7b7b60c4a2eef1b9d3cf51c12daab93261d5e09e7c 
    I 2014-05-20 05:18:31.754 
    feedback token_hex: 3980924c6cd4e752f2a02b8d28f7ce11d7a3eba5f41628166733cda4e621bfcf 
    I 2014-05-20 05:18:31.755 
    feedback token_hex: 6bd80feb5158a8f92537955c93acd1661242c007dcffebe55e77bb38cafef0ba 
    I 2014-05-20 05:18:31.755 
    feedback token_hex: b96e27adab644f0a18e8f4dfe19786aab82b69e1ef46c580b887e6779964c55f 
    I 2014-05-20 05:18:31.755 
    feedback token_hex: e5ee1848342d2e4789cfa07baae3ac754785d78ccb50dc5b5f10044053843115 
    I 2014-05-20 05:18:31.755 
    feedback token_hex: f339e53e44efa03996dffc24b5c9419609018fd8dd5d1953230a4bd8c5cabc78 
    I 2014-05-20 05:18:31.760 
    feedback fail_count: 9 

Trả lời

5

Tăng thời gian hết hạn đã làm điều kỳ diệu đối với tôi.

def send_notifications(self, tokens, payload): 
    for token in tokens: 
     try : 
      logging.info("Sending Notification to Token: %s" % (token)) 
      self.send_notification(token, payload, identifier=None, expiry = (datetime.utcnow() + timedelta(300)))     
     except Exception, e: 
      self._disconnect() 
      logging.info("Exception: %s" % (str(e))) 
      logging.info("Token: %s" % (token)) 
Các vấn đề liên quan