Source code for tornadio2.proto

# -*- coding: utf-8 -*-
#
# Copyright: (c) 2011 by the Serge S. Koval, see AUTHORS for more details.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""
    tornadio2.proto
    ~~~~~~~~~~~~~~~

    Socket.IO protocol related functions
"""
import logging


logger = logging.getLogger('tornadio2.proto')


try:
    import simplejson as json
    json_decimal_args = {"use_decimal": True}
except ImportError:
    import json
    import decimal

    class DecimalEncoder(json.JSONEncoder):
        def default(self, o):
            if isinstance(o, decimal.Decimal):
                return float(o)
            return super(DecimalEncoder, self).default(o)
    json_decimal_args = {"cls": DecimalEncoder}

# Packet ids
DISCONNECT = '0'
CONNECT = '1'
HEARTBEAT = '2'
MESSAGE = '3'
JSON = '4'
EVENT = '5'
ACK = '6'
ERROR = '7'
NOOP = '8'

# socket.io frame separator
FRAME_SEPARATOR = u'\ufffd'


[docs]def disconnect(endpoint=None): """Generate disconnect packet. `endpoint` Optional endpoint name """ return u'0::%s' % ( endpoint or '' )
[docs]def connect(endpoint=None): """Generate connect packet. `endpoint` Optional endpoint name """ return u'1::%s' % ( endpoint or '' )
[docs]def heartbeat(): """Generate heartbeat message. """ return u'2::'
[docs]def message(endpoint, msg, message_id=None, force_json=False): """Generate message packet. `endpoint` Optional endpoint name `msg` Message to encode. If message is ascii/unicode string, will send message packet. If object or dictionary, will json encode and send as is. `message_id` Optional message id for ACK `force json` Disregard msg type and send the message with JSON type. Usefull for already JSON encoded strings. """ if msg is None: # TODO: Log something ? return u'' packed_message_tpl = u"%(kind)s:%(message_id)s:%(endpoint)s:%(msg)s" packed_data = {'endpoint': endpoint or u'', 'message_id': message_id or u''} # Trying to send a dict over the wire ? if not isinstance(msg, (unicode, str)) and isinstance(msg, (dict, object)): packed_data.update({'kind': JSON, 'msg': json.dumps(msg, **json_decimal_args)}) # for all other classes, including objects. Call str(obj) # and respect forced JSON if requested else: packed_data.update({'kind': MESSAGE if not force_json else JSON, 'msg': msg if isinstance(msg, unicode) else str(msg).decode('utf-8')}) return packed_message_tpl % packed_data
[docs]def event(endpoint, name, message_id, *args, **kwargs): """Generate event message. `endpoint` Optional endpoint name `name` Event name `message_id` Optional message id for ACK `args` Optional event arguments. `kwargs` Optional event arguments. Will be encoded as dictionary. """ if args: evt = dict( name=name, args=args ) if kwargs: logger.error('Can not generate event() with args and kwargs.') else: evt = dict( name=name, args=[kwargs] ) return u'5:%s:%s:%s' % ( message_id or '', endpoint or '', json.dumps(evt) )
[docs]def ack(endpoint, message_id, ack_response=None): """Generate ACK packet. `endpoint` Optional endpoint name `message_id` Message id to acknowledge `ack_response` Acknowledgment response data (will be json serialized) """ if ack_response is not None: if not isinstance(ack_response, tuple): ack_response = (ack_response,) data = json_dumps(ack_response) return u'6::%s:%s+%s' % (endpoint or '', message_id, data) else: return u'6::%s:%s' % (endpoint or '', message_id)
[docs]def error(endpoint, reason, advice=None): """Generate error packet. `endpoint` Optional endpoint name `reason` Error reason `advice` Error advice """ return u'7::%s:%s+%s' % (endpoint or '', (reason or ''), (advice or ''))
[docs]def noop(): """Generate noop packet.""" return u'8::'
[docs]def json_dumps(msg): """Dump object as a json string `msg` Object to dump """ return json.dumps(msg)
[docs]def json_load(msg): """Load json-encoded object `msg` json encoded object """ return json.loads(msg)
[docs]def decode_frames(data): """Decode socket.io encoded messages. Returns list of packets. `data` encoded messages """ # Single message - nothing to decode here assert isinstance(data, unicode), 'frame is not unicode' if not data.startswith(FRAME_SEPARATOR): return [data] # Multiple messages idx = 0 packets = [] while data[idx:idx + 1] == FRAME_SEPARATOR: idx += 1 # Grab message length len_start = idx idx = data.find(FRAME_SEPARATOR, idx) msg_len = int(data[len_start:idx]) idx += 1 # Grab message msg_data = data[idx:idx + msg_len] idx += msg_len packets.append(msg_data) return packets # Encode expects packets in unicode
[docs]def encode_frames(packets): """Encode list of packets. `packets` List of packets to encode """ # No packets - return empty string if not packets: return '' # Exactly one packet - don't do any frame encoding if len(packets) == 1: return packets[0].encode('utf-8') # Multiple packets frames = u''.join(u'%s%d%s%s' % (FRAME_SEPARATOR, len(p), FRAME_SEPARATOR, p) for p in packets) return frames.encode('utf-8')