diff options
Diffstat (limited to 'sleekxmpp/plugins/xep_0332')
-rw-r--r-- | sleekxmpp/plugins/xep_0332/__init__.py | 17 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0332/http.py | 159 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0332/stanza/__init__.py | 13 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0332/stanza/data.py | 30 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0332/stanza/request.py | 71 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0332/stanza/response.py | 66 |
6 files changed, 356 insertions, 0 deletions
diff --git a/sleekxmpp/plugins/xep_0332/__init__.py b/sleekxmpp/plugins/xep_0332/__init__.py new file mode 100644 index 00000000..27755faa --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/__init__.py @@ -0,0 +1,17 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.base import register_plugin + +from sleekxmpp.plugins.xep_0332 import stanza +from sleekxmpp.plugins.xep_0332.http import XEP_0332 + + +register_plugin(XEP_0332) diff --git a/sleekxmpp/plugins/xep_0332/http.py b/sleekxmpp/plugins/xep_0332/http.py new file mode 100644 index 00000000..70bcafa6 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/http.py @@ -0,0 +1,159 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +import logging + +from sleekxmpp import Iq + +from sleekxmpp.xmlstream import register_stanza_plugin +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath + +from sleekxmpp.plugins.base import BasePlugin +from sleekxmpp.plugins.xep_0332.stanza import ( + HTTPRequest, HTTPResponse, HTTPData +) +from sleekxmpp.plugins.xep_0131.stanza import Headers + + +log = logging.getLogger(__name__) + + +class XEP_0332(BasePlugin): + """ + XEP-0332: HTTP over XMPP transport + """ + + name = 'xep_0332' + description = 'XEP-0332: HTTP over XMPP transport' + + #: xep_0047 not included. + #: xep_0001, 0137 and 0166 are missing + dependencies = set(['xep_0030', 'xep_0131']) + + #: TODO: Do we really need to mention the supported_headers?! + default_config = { + 'supported_headers': set([ + 'Content-Length', 'Transfer-Encoding', 'DateTime', + 'Accept-Charset', 'Location', 'Content-ID', 'Description', + 'Content-Language', 'Content-Transfer-Encoding', 'Timestamp', + 'Expires', 'User-Agent', 'Host', 'Proxy-Authorization', 'Date', + 'WWW-Authenticate', 'Accept-Encoding', 'Server', 'Error-Info', + 'Identifier', 'Content-Location', 'Content-Encoding', 'Distribute', + 'Accept', 'Proxy-Authenticate', 'ETag', 'Expect', 'Content-Type' + ]) + } + + def plugin_init(self): + self.xmpp.register_handler( + Callback( + 'HTTP Request', + StanzaPath('iq/http-req'), + self._handle_request + ) + ) + self.xmpp.register_handler( + Callback( + 'HTTP Response', + StanzaPath('iq/http-resp'), + self._handle_response + ) + ) + register_stanza_plugin(Iq, HTTPRequest, iterable=True) + register_stanza_plugin(Iq, HTTPResponse, iterable=True) + register_stanza_plugin(HTTPRequest, Headers, iterable=True) + register_stanza_plugin(HTTPRequest, HTTPData, iterable=True) + register_stanza_plugin(HTTPResponse, Headers, iterable=True) + register_stanza_plugin(HTTPResponse, HTTPData, iterable=True) + # TODO: Should we register any api's here? self.api.register() + + def plugin_end(self): + self.xmpp.remove_handler('HTTP Request') + self.xmpp.remove_handler('HTTP Response') + self.xmpp['xep_0030'].del_feature('urn:xmpp:http') + for header in self.supported_headers: + self.xmpp['xep_0030'].del_feature( + feature='%s#%s' % (Headers.namespace, header) + ) + + def session_bind(self, jid): + self.xmpp['xep_0030'].add_feature('urn:xmpp:http') + for header in self.supported_headers: + self.xmpp['xep_0030'].add_feature( + '%s#%s' % (Headers.namespace, header) + ) + # TODO: Do we need to add the supported headers to xep_0131? + # self.xmpp['xep_0131'].supported_headers.add(header) + + def _handle_request(self, iq): + self.xmpp.event('http_request', iq) + + def _handle_response(self, iq): + self.xmpp.event('http_response', iq) + + def send_request(self, to=None, method=None, resource=None, headers=None, + data=None, **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'set' + iq['http-req']['headers'] = headers + iq['http-req']['method'] = method + iq['http-req']['resource'] = resource + iq['http-req']['version'] = '1.1' # TODO: set this implicitly + if 'id' in kwargs: + iq['id'] = kwargs["id"] + if data is not None: + iq['http-req']['data'] = data + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) + + def send_response(self, to=None, code=None, message=None, headers=None, + data=None, **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'result' + iq['http-resp']['headers'] = headers + iq['http-resp']['code'] = code + iq['http-resp']['message'] = message + iq['http-resp']['version'] = '1.1' # TODO: set this implicitly + if 'id' in kwargs: + iq['id'] = kwargs["id"] + if data is not None: + iq['http-resp']['data'] = data + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) + + def send_error(self, to=None, ecode='500', etype='wait', + econd='internal-server-error', **kwargs): + iq = self.xmpp.Iq() + iq['from'] = self.xmpp.boundjid + iq['to'] = to + iq['type'] = 'error' + iq['error']['code'] = ecode + iq['error']['type'] = etype + iq['error']['condition'] = econd + if 'id' in kwargs: + iq['id'] = kwargs["id"] + return iq.send( + timeout=kwargs.get('timeout', None), + block=kwargs.get('block', True), + callback=kwargs.get('callback', None), + timeout_callback=kwargs.get('timeout_callback', None) + ) diff --git a/sleekxmpp/plugins/xep_0332/stanza/__init__.py b/sleekxmpp/plugins/xep_0332/stanza/__init__.py new file mode 100644 index 00000000..201824b7 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/__init__.py @@ -0,0 +1,13 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.plugins.xep_0332.stanza.request import HTTPRequest +from sleekxmpp.plugins.xep_0332.stanza.response import HTTPResponse +from sleekxmpp.plugins.xep_0332.stanza.data import HTTPData diff --git a/sleekxmpp/plugins/xep_0332/stanza/data.py b/sleekxmpp/plugins/xep_0332/stanza/data.py new file mode 100644 index 00000000..a3678038 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/data.py @@ -0,0 +1,30 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase + + +class HTTPData(ElementBase): + """ + The data element. + """ + name = 'data' + namespace = 'urn:xmpp:http' + interfaces = set(['data']) + plugin_attrib = 'data' + is_extension = True + + def get_data(self, encoding='text'): + data = self._get_sub_text(encoding, None) + return str(data) if data is not None else data + + def set_data(self, data, encoding='text'): + self._set_sub_text(encoding, text=data) + diff --git a/sleekxmpp/plugins/xep_0332/stanza/request.py b/sleekxmpp/plugins/xep_0332/stanza/request.py new file mode 100644 index 00000000..9a298e57 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/request.py @@ -0,0 +1,71 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase + + +class HTTPRequest(ElementBase): + + """ + All HTTP communication is done using the `Request`/`Response` paradigm. + Each HTTP Request is made sending an `iq` stanza containing a `req` + element to the server. Each `iq` stanza sent is of type `set`. + + Examples: + <iq type='set' from='a@b.com/browser' to='x@y.com' id='1'> + <req xmlns='urn:xmpp:http' + method='GET' + resource='/api/users' + version='1.1'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Host'>b.com</header> + </headers> + </req> + </iq> + + <iq type='set' from='a@b.com/browser' to='x@y.com' id='2'> + <req xmlns='urn:xmpp:http' + method='PUT' + resource='/api/users' + version='1.1'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Host'>b.com</header> + <header name='Content-Type'>text/html</header> + <header name='Content-Length'>...</header> + </headers> + <data> + <text>...</text> + </data> + </req> + </iq> + """ + + name = 'request' + namespace = 'urn:xmpp:http' + interfaces = set(['method', 'resource', 'version']) + plugin_attrib = 'http-req' + + def get_method(self): + return self._get_attr('method', None) + + def set_method(self, method): + self._set_attr('method', method) + + def get_resource(self): + return self._get_attr('resource', None) + + def set_resource(self, resource): + self._set_attr('resource', resource) + + def get_version(self): + return self._get_attr('version', None) + + def set_version(self, version='1.1'): + self._set_attr('version', version) diff --git a/sleekxmpp/plugins/xep_0332/stanza/response.py b/sleekxmpp/plugins/xep_0332/stanza/response.py new file mode 100644 index 00000000..6804ade9 --- /dev/null +++ b/sleekxmpp/plugins/xep_0332/stanza/response.py @@ -0,0 +1,66 @@ +""" + SleekXMPP: The Sleek XMPP Library + Implementation of HTTP over XMPP transport + http://xmpp.org/extensions/xep-0332.html + Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + +from sleekxmpp.xmlstream import ElementBase + + +class HTTPResponse(ElementBase): + + """ + When the HTTP Server responds, it does so by sending an `iq` stanza + response (type=`result`) back to the client containing the `resp` element. + Since response are asynchronous, and since multiple requests may be active + at the same time, responses may be returned in a different order than the + in which the original requests were made. + + Examples: + <iq type='result' + from='httpserver@clayster.com' + to='httpclient@clayster.com/browser' id='2'> + <resp xmlns='urn:xmpp:http' + version='1.1' + statusCode='200' + statusMessage='OK'> + <headers xmlns='http://jabber.org/protocol/shim'> + <header name='Date'>Fri, 03 May 2013 16:39:54GMT-4</header> + <header name='Server'>Clayster</header> + <header name='Content-Type'>text/turtle</header> + <header name='Content-Length'>...</header> + <header name='Connection'>Close</header> + </headers> + <data> + <text> + ... + </text> + </data> + </resp> + </iq> + """ + + name = 'response' + namespace = 'urn:xmpp:http' + interfaces = set(['code', 'message', 'version']) + plugin_attrib = 'http-resp' + + def get_code(self): + code = self._get_attr('statusCode', None) + return int(code) if code is not None else code + + def set_code(self, code): + self._set_attr('statusCode', str(code)) + + def get_message(self): + return self._get_attr('statusMessage', '') + + def set_message(self, message): + self._set_attr('statusMessage', message) + + def set_version(self, version='1.1'): + self._set_attr('version', version) |