Current File : /home/jvzmxxx/wiki1/extensions/EventLogging/server/eventlogging/streams.py
# -*- coding: utf-8 -*-
"""
  eventlogging.streams
  ~~~~~~~~~~~~~~~~~~~~

  This module provides helpers for reading from and writing to ZeroMQ
  data streams using ZeroMQ or UDP.

"""
from __future__ import unicode_literals

import io
import json
import re
import socket

import zmq

from .compat import items


__all__ = ('iter_json', 'iter_unicode', 'make_canonical', 'pub_socket',
           'stream', 'sub_socket', 'udp_socket')

# High water mark. The maximum number of outstanding messages to queue
# in memory for any single peer that the socket is communicating with.
ZMQ_HIGH_WATER_MARK = 3000

# If a socket is closed before all its messages has been sent, ZeroMQ
# will wait up to this many miliseconds before discarding the messages.
# We'd rather fail fast, even at the cost of dropping a few events.
ZMQ_LINGER = 0

# The maximum socket buffer size in bytes. This is used to set either
# SO_SNDBUF or SO_RCVBUF for the underlying socket, depending on its
# type. We set it to 64 kB to match Udp2LogConfig::BLOCK_SIZE.
SOCKET_BUFFER_SIZE = 64 * 1024


def pub_socket(endpoint):
    """Get a pre-configured ZeroMQ publisher."""
    context = zmq.Context.instance()
    sock = context.socket(zmq.PUB)
    if hasattr(zmq, 'HWM'):
        sock.hwm = ZMQ_HIGH_WATER_MARK
    sock.linger = ZMQ_LINGER
    sock.sndbuf = SOCKET_BUFFER_SIZE
    canonical_endpoint = make_canonical(endpoint, host='*')
    sock.bind(canonical_endpoint)
    return sock


def sub_socket(endpoint, identity='', subscribe=''):
    """Get a pre-configured ZeroMQ subscriber."""
    context = zmq.Context.instance()
    sock = context.socket(zmq.SUB)
    if hasattr(zmq, 'HWM'):
        sock.hwm = ZMQ_HIGH_WATER_MARK
    sock.linger = ZMQ_LINGER
    sock.rcvbuf = SOCKET_BUFFER_SIZE
    if identity and hasattr(sock, 'identity'):
        sock.identity = identity.encode('utf-8')
    canonical_endpoint = make_canonical(endpoint)
    sock.connect(canonical_endpoint)
    sock.subscribe = subscribe.encode('utf-8')
    return sock


def udp_socket(hostname, port):
    """Parse a URI and configure a UDP socket for it."""
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((hostname, port))
    return sock


def iter_file(file):
    """Wrap a file object's underlying file descriptor with a UTF8 line
    reader and read successive lines."""
    with io.open(file.fileno(), mode='rt', encoding='utf8',
                 errors='replace') as f:
        for line in f:
            yield line


def iter_unicode(stream):
    """Iterator; read and decode unicode strings from a stream."""
    if hasattr(stream, 'recv_unicode'):
        return iter(stream.recv_unicode, None)
    elif hasattr(stream, 'fileno'):
        return iter_file(stream)
    else:
        return (line.decode('utf-8', 'replace') for line in stream)


def iter_json(stream):
    """Iterator; read and decode successive JSON objects from a stream."""
    if hasattr(stream, 'recv_json'):
        return iter(stream.recv_json, None)
    return (json.loads(dgram) for dgram in iter_unicode(stream))


def stream(s, raw=False):
    """Convenience method for getting a JSON-based or line-based
    streaming iterator."""
    return iter_unicode(s) if raw else iter_json(s)


def make_canonical(uri, protocol='tcp', host='127.0.0.1'):
    """Convert a partial endpoint URI to a fully canonical one, using
    TCP and localhost as the default protocol and host. The partial URI
    must at minimum contain a port number."""
    fragments = dict(protocol=protocol, host=host)
    match = re.match(r'((?P<protocol>[^:]+)://)?((?P<host>[^:]+):)?'
                     r'(?P<port>\d+)(?:\?.*)?', '%s' % uri)
    fragments.update((k, v) for k, v in items(match.groupdict()) if v)
    return '%(protocol)s://%(host)s:%(port)s' % dict(fragments)