summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins
diff options
context:
space:
mode:
authorMike Taylor <bear42@gmail.com>2015-04-28 22:44:27 -0400
committerMike Taylor <bear42@gmail.com>2015-04-28 22:44:27 -0400
commit192b7e0349429839261eb4d1d42d8f75933dc0c2 (patch)
treeecb5905d19a563b4e6fe864afd0122321bf7cea1 /sleekxmpp/plugins
parent842157a6cc25d9e85e6e31b3cf3349ba83ece101 (diff)
parent80b60fc0483b21c8d5829b61cabbf9b46aa2c2fb (diff)
downloadslixmpp-192b7e0349429839261eb4d1d42d8f75933dc0c2.tar.gz
slixmpp-192b7e0349429839261eb4d1d42d8f75933dc0c2.tar.bz2
slixmpp-192b7e0349429839261eb4d1d42d8f75933dc0c2.tar.xz
slixmpp-192b7e0349429839261eb4d1d42d8f75933dc0c2.zip
Merge pull request #345 from sangeeths/xep_0332
XEP-0332: HTTP over XMPP transport
Diffstat (limited to 'sleekxmpp/plugins')
-rw-r--r--sleekxmpp/plugins/__init__.py1
-rw-r--r--sleekxmpp/plugins/xep_0332/__init__.py17
-rw-r--r--sleekxmpp/plugins/xep_0332/http.py143
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/__init__.py13
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/data.py30
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/request.py65
-rw-r--r--sleekxmpp/plugins/xep_0332/stanza/response.py61
7 files changed, 330 insertions, 0 deletions
diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py
index 951f31eb..2c90d357 100644
--- a/sleekxmpp/plugins/__init__.py
+++ b/sleekxmpp/plugins/__init__.py
@@ -83,4 +83,5 @@ __all__ = [
'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
+ 'xep_0332', # HTTP Over XMPP Transport
]
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..36b6995f
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0332/http.py
@@ -0,0 +1,143 @@
+"""
+ 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 Request, Response, Data
+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/req'), self._handle_request
+ ))
+ self.xmpp.register_handler(Callback(
+ 'HTTP Response', StanzaPath('iq/resp'), self._handle_response
+ ))
+ register_stanza_plugin(Iq, Request, iterable=True)
+ register_stanza_plugin(Iq, Response, iterable=True)
+ register_stanza_plugin(Request, Headers, iterable=True)
+ register_stanza_plugin(Request, Data, iterable=True)
+ register_stanza_plugin(Response, Headers, iterable=True)
+ register_stanza_plugin(Response, Data, 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['req']['headers'] = headers
+ iq['req']['method'] = method
+ iq['req']['resource'] = resource
+ iq['req']['version'] = '1.1' # TODO: set this implicitly
+ if data is not None:
+ iq['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['resp']['headers'] = headers
+ iq['resp']['code'] = code
+ iq['resp']['message'] = message
+ iq['resp']['version'] = '1.1' # TODO: set this implicitly
+ if data is not None:
+ iq['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['type'] = 'error'
+ iq['from'] = self.xmpp.boundjid
+ iq['to'] = to
+ iq['error']['code'] = ecode
+ iq['error']['type'] = etype
+ iq['error']['condition'] = econd
+ 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..eeab3f31
--- /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 Request
+from sleekxmpp.plugins.xep_0332.stanza.response import Response
+from sleekxmpp.plugins.xep_0332.stanza.data import Data
diff --git a/sleekxmpp/plugins/xep_0332/stanza/data.py b/sleekxmpp/plugins/xep_0332/stanza/data.py
new file mode 100644
index 00000000..9a08426b
--- /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 Data(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..07618727
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0332/stanza/request.py
@@ -0,0 +1,65 @@
+"""
+ 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 Request(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 = '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..0fc46de8
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0332/stanza/response.py
@@ -0,0 +1,61 @@
+"""
+ 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 Response(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 = '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)