def __init__(self, m2m_ep, _, client_id, handle_request_func=None, subscribe_sys_topics=False, ca_certs=None, cert_file=None, key_file=None, insecure=False): """ :param str m2m_ep: :param bool _: :param str client_id: :param call handle_request_func: :param bool subscribe_sys_topics: Whether to subscribe to $SYS topics or not (cf <https://github.com/mqtt/mqtt.github.io/wiki/SYS-Topics>) """ super(OneM2MMQTTClient, self).__init__() parsed_url = urlparse(m2m_ep) self._default_target_id = parsed_url.fragment def _default(x): if isinstance(x, datetime): try: isoformat = x.isoformat except AttributeError: raise TypeError("%s (%s)" % (x, type(x))) return isoformat() else: return x self._encode = JSONEncoder(default=_default).encode self._decode = JSONDecoder().decode self._handle_request_func = handle_request_func self._processed_request_ids = deque([], maxlen=200) self._request_promises = LRUCache(threadsafe=False, max_items=200) if client_id is None: import random import string client_id = ''.join(random.sample(string.letters, 16)) else: client_id = self._get_client_id_from_originator(client_id) self._client = mqtt.Client( clean_session=False, client_id='::'.join([ 'C' if client_id[0].lower() in ['c', 'm'] else 'A', self._mqtt_mask(client_id), ]), ) self._client_id = client_id @self.attach_callback() def on_connect(client, userdata, flags_dict, rc): """ :param mqtt.Client client: :param All userdata: :param dict flags_dict: :param integer rc: :return void: """ if not rc == mqtt.CONNACK_ACCEPTED: raise ConnectionFailed(mqtt.connack_string(rc)) topics = [ self._build_topic(originator=client_id, receiver='#', type='resp'), ] client.message_callback_add(topics[0], self._response_callback) if self._handle_request_func is not None: topics.append(self._build_topic(receiver=client_id) + '/+') client.message_callback_add(topics[1], self._request_callback) if subscribe_sys_topics: topics.append('$SYS/#') self.logger.debug('Subscribing to topic(s) %s ...' % (', '.join(topics), )) client.subscribe([(str(topic), MQTT_QOS_LEVEL) for topic in topics]) @self.attach_callback() def on_disconnect(client, userdata, rc): """ :param mqtt.Client client: :param All userdata: :param int rc: :return void: """ if not rc == mqtt.MQTT_ERR_SUCCESS: self.logger.error( 'Involuntary connection loss: %s (code %d). Waiting for reconnect ...' % (mqtt.error_string(rc), rc)) @self.attach_callback() def on_message(client, userdata, message): """ :param mqtt.Client client: :param All userdata: :param mqtt.MQTTMessage message: :return void: """ self.logger.debug('message received on topic %s' % (message.topic, )) @self.attach_callback() def on_log(client, userdata, level, buf): """ :param mqtt.Client client: :param All userdata: :param integer level: :param string buf: :return void: """ self.logger.debug('pahomqtt-%d: %s' % (level, buf)) if parsed_url.username: self._client.username_pw_set(parsed_url.username, parsed_url.password) if parsed_url.scheme != 'mqtt': self._client.tls_set(ca_certs=ca_certs, certfile=cert_file, keyfile=key_file) self._client.tls_insecure_set(insecure) try: self._client.connect(parsed_url.hostname, parsed_url.port or portmap[parsed_url.scheme]) except SocketError as e: raise ConnectionFailed(e.message) def loop(): try: while self._client.loop( timeout=0.1) != mqtt.mqtt_cs_disconnecting: gevent.sleep() except (SystemExit, KeyboardInterrupt, AttributeError): pass gevent.spawn(loop)
#: Dictionary mapping supported schemes to port numbers portmap = { 'mqtt': 1883, 'mqtts': 8883, # NB: The correct (i.e. registered with IANA) service-name for SSL/TLS-wrapped MQTT is # 'secure-mqtt' in an effort to prevent confusion with MQTT-S/N. But as the entire world seems # to insist on using 'mqtts' (including TS 0010, sec. 6.6) ... We are supporting both names here # for maximum compliance and robustness. 'secure-mqtt': 8883, } MQTT_QOS_LEVEL = 1 MQTT_RESPONSE_TIMEOUT = 1 _clients = LRUCache(threadsafe=False) def get_client(m2m_ep, use_xml=False, client_id=None, handle_request_func=None, ca_certs=None, cert_file=None, key_file=None, insecure=False): """ :param string m2m_ep: :param boolean use_xml: :param string client_id:
def __init__(self, m2m_ep, _, client_id, handle_request_func=None, subscribe_sys_topics=False): """ :param str m2m_ep: :param bool _: :param str client_id: :param call handle_request_func: :param bool subscribe_sys_topics: Whether to subscribe to $SYS topics or not (cf <https://github.com/mqtt/mqtt.github.io/wiki/SYS-Topics>) """ super(OneM2MMQTTClient, self).__init__() parsed_url = urlparse(m2m_ep) self._target_id = parsed_url.fragment self._encode = JSONEncoder().encode self._decode = JSONDecoder().decode self._handle_request_func = handle_request_func self._processed_request_ids = deque([], maxlen=200) self._request_promises = LRUCache(threadsafe=False, max_items=200) if client_id is None: import random import string client_id = ''.join(random.sample(string.letters, 16)) self._client = mqtt.Client( clean_session=False, client_id='::'.join([ 'C' if client_id[0].lower() in ['c', 'm'] else 'A', self._mqtt_mask(client_id), ]), ) @self.attach_callback() def on_connect(client, _, rc): """ :param mqtt.Client client: :param All userdata: :param integer rc: :return void: """ if not rc == mqtt.CONNACK_ACCEPTED: raise ConnectionFailed(mqtt.connack_string(rc)) def request_callback(client, _, message): """ Catch requests and :param mqtt.Client client: :param All _: :param mqtt.MQTTMessage message: :return void: """ originator = message.topic.split('/')[3] try: request = self._decode(message.payload) except JSONDecodeError as e: self.logger.warn('Got rubbish request from client %s: %s' % ( originator, e.message, )) return try: if request['rqi'] in self._processed_request_ids: self.logger.info( 'Request %s already processed; discarding duplicate.' % (request['rqi'], )) return else: rqi = request['rqi'] except KeyError: self.logger.warn( 'Special treatment for special request w/o request id from %s.' % (originator, )) return try: request['pc'] = decode_onem2m_content( self._encode(request['pc']), 'application/json') request['ty'] = type(request['pc']) except KeyError: # No content, eh? request['ty'] = None self.logger.debug('Decoded JSON request: %s' % (request, )) op = OneM2MOperation._member_map_.values()[request['op'] - 1] to = request['to'] del request['op'], request['to'] try: response = self._handle_request_func( OneM2MRequest(op, to, **request)).get() except OneM2MErrorResponse as response: self.logger.error('OneM2MError: %s' % (response.message, )) except CSEError as e: response = OneM2MErrorResponse( status_code=e.response_status_code, rqi=rqi) if not response.rqi: # This really should not happen. No, really, it shouldn't. self.logger.debug( 'FIXUP! FIXUP! FIXUP! Adding missing request identifier to response: %s' % (rqi, )) response.rqi = rqi if response.content: response.content = self._decode( encode_onem2m_content(response.content, 'application/json', path=response.to)[1]) self._publish_message( self._encode({ k: getattr(response, k) for k in self.__response_fields if getattr(response, k) is not None }), self._build_topic(originator, client_id, type='resp'), ) self._processed_request_ids.append(rqi) def response_callback(client, _, message): """ :param mqtt.Client client: :param All _: :param mqtt.MQTTMessage message: :return: """ try: response = self._decode(message.payload) except JSONDecodeError as e: self.logger.error( 'Discarding response w/ damaged payload: %s', (e.message, )) return promise_key = (message.topic.split('/')[4], response['rqi']) try: p = self._request_promises[promise_key] except KeyError: self.logger.debug( 'Response %s could not be mapped to a request. Discarding.' % (response['rqi'], )) return try: response['pc'] = decode_onem2m_content( self._encode(response['pc']), 'application/json') except KeyError: pass except CSEValueError as e: self.logger.error( 'Content of response %s could not be parsed, throwing on the trash heap: %s' % (response['rqi'], e.message)) p.reject(e) status_code = response['rsc'] del response['rsc'] if status_code >= ERROR_MIN: p.reject(OneM2MErrorResponse(status_code, **response)) else: p.fulfill(OneM2MResponse(status_code, **response)) topics = [ self._build_topic(originator=client_id, receiver='#', type='resp'), ] client.message_callback_add(topics[0], response_callback) if self._handle_request_func is not None: topics.append(self._build_topic(receiver=client_id) + '/+') client.message_callback_add(topics[1], request_callback) if subscribe_sys_topics: topics.append('$SYS/#') self.logger.debug('Subscribing to topic(s) %s ...' % (', '.join(topics), )) client.subscribe([(str(topic), MQTT_QOS_LEVEL) for topic in topics]) @self.attach_callback() def on_disconnect(client, userdata, rc): """ :param mqtt.Client client: :param All userdata: :param int rc: :return void: """ if not rc == mqtt.MQTT_ERR_SUCCESS: self.logger.error( 'Involuntary connection loss: %s (code %d). Waiting for reconnect ...' % (mqtt.error_string(rc), rc)) @self.attach_callback() def on_message(client, userdata, message): """ :param mqtt.Client client: :param All userdata: :param mqtt.MQTTMessage message: :return void: """ self.logger.debug('message received on topic %s' % (message.topic, )) @self.attach_callback() def on_log(client, userdata, level, buf): """ :param mqtt.Client client: :param All userdata: :param integer level: :param string buf: :return void: """ self.logger.debug('pahomqtt-%d: %s' % (level, buf)) if parsed_url.username: self._client.username_pw_set(parsed_url.username, parsed_url.password) try: self._client.connect(parsed_url.hostname, parsed_url.port or portmap[parsed_url.scheme]) except SocketError as e: raise ConnectionFailed(e.message) def loop(): try: while self._client.loop( timeout=0.1) != mqtt.mqtt_cs_disconnecting: gevent.sleep() except (KeyboardInterrupt, SystemExit): pass gevent.spawn(loop)
def __init__(self, uri, username=None, password=None, certfile=None, keyfile=None, cacertfile=None, content_type="text/plain", headers=None, cache=True, timeout=None, get_timeout=None, component_name = "server", connection_manager = None, *args, **kw): super(RestClient, self).__init__(*args, **kw) self.logger.debug("Creating RestClient for %s", uri) self.timeout = timeout or self.timeout self.get_timeout = get_timeout or timeout or self.get_timeout if cache: if cache is True: from futile.caching import LRUCache cache = LRUCache() self.__cache = cache if "://" not in uri: uri = "http://" + uri self.__content_type = content_type self.component_name = component_name info = urlparse(uri) self.logger.debug("Restclient certfile is %s"%certfile) if info.scheme == "https": if bool(certfile) ^ bool(keyfile): raise ValueError("Must give both certfile and keyfile if any") if certfile: from os.path import exists if not exists(certfile): raise ValueError("Certificate file not found: %s" % (certfile, )) if not exists(keyfile): raise ValueError("Key file not found: %s" % (keyfile, )) elif info.scheme != "http": raise ValueError(info.scheme) else: # In case of http, we do not want any certificates keyfile = certfile = None port = info.port and int(info.port) or getservbyname(info.scheme) self.__base = info.path or "" #if not self.__base.endswith("/"): # self.__base += "/" if not username: username = info.username if not headers: headers = {} headers.setdefault("Accept", "*/*") headers["Accept-Encoding"] = "identity" if username: password = password or info.password or "" headers["Authorization"] = "Basic " + b64encode("%s:%s" % (username, password)) self.__headers = headers if not connection_manager: #from SimpleConnectionManager import SimpleConnectionManager as connection_manager from ConnectionPoolManager import ConnectionPoolManager as connection_manager self.__connection_manager = connection_manager(host=info.hostname, port=port, certfile = certfile, keyfile = keyfile, cacertfile = cacertfile, force_ssl = info.scheme == "https")
def _init(self): # TODO: make max_items configurable self._clients = LRUCache(threadsafe=False) self.api.register_client(("http", "https"), self.send_request) self._initialized()