added prototype of BOSHClient class and script for usage example, removed import of common.gajim from transports_nb
This commit is contained in:
parent
794a5f33d5
commit
cb2d629535
|
@ -0,0 +1,194 @@
|
|||
## client_bosh.py
|
||||
##
|
||||
## Copyright (C) 2008 Tomas Karasek <tom.to.the.k@gmail.com>
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2, or (at your option)
|
||||
## any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
|
||||
import locale, random
|
||||
import protocol
|
||||
import simplexml
|
||||
import debug
|
||||
import dispatcher_nb
|
||||
from client_nb import NBCommonClient
|
||||
|
||||
DBG_BOSHCLIENT='boshclient'
|
||||
|
||||
class BOSHClient(NBCommonClient):
|
||||
'''
|
||||
BOSH (XMPP over HTTP) client implementation. It should provide the same
|
||||
methods and functionality as NonBlockingClient.
|
||||
'''
|
||||
|
||||
|
||||
def __init__(self, server, bosh_conn_mgr, port=5222, bosh_port=5280,
|
||||
on_connect=None, on_connect_failure=None, on_proxy_failure=None, caller=None):
|
||||
'''
|
||||
Class constuctor has the same parameters as NBCommonClient plus bosh_conn_mgr
|
||||
and bosh_port - Connection manager address and port. bosh_conn_mgr should be
|
||||
in form: 'http://httpcm.jabber.org/http-bind/'
|
||||
Tcp connection will be opened to bosh_conn_mgr:bosh_port instead of
|
||||
server:port.
|
||||
'''
|
||||
self.bosh_protocol, self.bosh_host, self.bosh_uri = self.urisplit(bosh_conn_mgr)
|
||||
if self.bosh_protocol is None:
|
||||
self.bosh_protocol = 'http'
|
||||
|
||||
self.bosh_port = bosh_port
|
||||
|
||||
if self.bosh_uri == '':
|
||||
bosh_uri = '/'
|
||||
|
||||
self.xmpp_server = server
|
||||
self.xmpp_port = port
|
||||
|
||||
self.bosh_hold = 1
|
||||
self.bosh_wait=60
|
||||
self.bosh_rid=-1
|
||||
self.bosh_httpversion = 'HTTP/1.1'
|
||||
|
||||
NBCommonClient.__init__(self, self.bosh_host, self.bosh_port, caller=caller,
|
||||
on_connect=on_connect, on_connect_failure=on_connect_failure,
|
||||
on_proxy_failure=on_proxy_failure)
|
||||
|
||||
# Namespace and DBG are detected in NBCommonClient constructor
|
||||
# with isinstance(). Since BOSHClient is descendant of NBCommonClient
|
||||
# and client_bosh.py is NOT imported in client_nb.py, NB_COMPONENT_ACCEPT
|
||||
# is put to namespace. This is not very nice, thus:
|
||||
# TODO: refactor Namespace and DBG recognition in NBCommonClient or
|
||||
# derived classes
|
||||
self.Namespace, self.DBG = protocol.NS_HTTP_BIND, DBG_BOSHCLIENT
|
||||
# pop of DBG_COMPONENT
|
||||
self.debug_flags.pop()
|
||||
self.debug_flags.append(self.DBG)
|
||||
self.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
|
||||
|
||||
def urisplit(self, uri):
|
||||
'''
|
||||
Function for splitting URI string to tuple (protocol, host, path).
|
||||
e.g. urisplit('http://httpcm.jabber.org/webclient') returns
|
||||
('http', 'httpcm.jabber.org', '/webclient')
|
||||
'''
|
||||
import re
|
||||
regex = '(([^:/]+)(://))?([^/]*)(/?.*)'
|
||||
grouped = re.match(regex, uri).groups()
|
||||
proto, host, path = grouped[1], grouped[3], grouped[4]
|
||||
return proto, host, path
|
||||
|
||||
|
||||
def _on_connected(self):
|
||||
'''
|
||||
method called after socket starts connecting from NonBlockingTcp._do_connect
|
||||
'''
|
||||
self.onreceive(self.on_bosh_session_init_response)
|
||||
dispatcher_nb.Dispatcher().PlugIn(self)
|
||||
|
||||
|
||||
def parse_http_message(self, message):
|
||||
'''
|
||||
splits http message to tuple (
|
||||
statusline - list of e.g. ['HTTP/1.1', '200', 'OK'],
|
||||
headers - dictionary of headers e.g. {'Content-Length': '604',
|
||||
'Content-Type': 'text/xml; charset=utf-8'},
|
||||
httpbody - string with http body
|
||||
)
|
||||
'''
|
||||
message = message.replace('\r','')
|
||||
(header, httpbody) = message.split('\n\n')
|
||||
header = header.split('\n')
|
||||
statusline = header[0].split(' ')
|
||||
header = header[1:]
|
||||
headers = {}
|
||||
for dummy in header:
|
||||
row = dummy.split(' ',1)
|
||||
headers[row[0][:-1]] = row[1]
|
||||
return (statusline, headers, httpbody)
|
||||
|
||||
|
||||
def on_bosh_session_init_response(self, data):
|
||||
'''
|
||||
Called on init response - should check relevant attributes from body tag
|
||||
'''
|
||||
if data:
|
||||
statusline, headers, httpbody = self.parse_http_message(data)
|
||||
|
||||
if statusline[1] != '200':
|
||||
self.DEBUG(self.DBG, "HTTP Error in received session init response: %s"
|
||||
% statusline, 'error')
|
||||
# error handling TBD!
|
||||
|
||||
# ATM, whole <body> tag is pass to ProcessNonBocking.
|
||||
# Question is how to peel off the body tag from incoming stanzas and make
|
||||
# use of ordinar xmpp traffic handling.
|
||||
self.Dispatcher.ProcessNonBlocking(httpbody)
|
||||
|
||||
|
||||
def _check_stream_start(self, ns, tag, attrs):
|
||||
'''
|
||||
callback stub called from XML Parser when <stream..> is discovered
|
||||
'''
|
||||
self.DEBUG(self.DBG, 'CHECK_STREAM_START: ns: %s, tag: %s, attrs: %s'
|
||||
% (ns, tag, attrs), 'info')
|
||||
|
||||
|
||||
def StreamInit(self):
|
||||
'''
|
||||
Initiation of BOSH session. Called instead of Dispatcher.StreamInit()
|
||||
Initial body tag is created and sent to Conn Manager.
|
||||
'''
|
||||
self.Dispatcher.Stream = simplexml.NodeBuilder()
|
||||
self.Dispatcher.Stream._dispatch_depth = 2
|
||||
self.Dispatcher.Stream.dispatch = self.Dispatcher.dispatch
|
||||
self.Dispatcher.Stream.stream_header_received = self._check_stream_start
|
||||
self.debug_flags.append(simplexml.DBG_NODEBUILDER)
|
||||
self.Dispatcher.Stream.DEBUG = self.DEBUG
|
||||
self.Dispatcher.Stream.features = None
|
||||
|
||||
initial_body_tag = simplexml.Node('body')
|
||||
initial_body_tag.setNamespace(self.Namespace)
|
||||
initial_body_tag.setAttr('content', 'text/xml; charset=utf-8')
|
||||
initial_body_tag.setAttr('hold', str(self.bosh_hold))
|
||||
initial_body_tag.setAttr('to', self.xmpp_server)
|
||||
initial_body_tag.setAttr('wait', str(self.bosh_wait))
|
||||
|
||||
r = random.Random()
|
||||
r.seed()
|
||||
# with 50-bit random initial rid, session would have to go up
|
||||
# to 7881299347898368 messages to raise rid over 2**53
|
||||
# (see http://www.xmpp.org/extensions/xep-0124.html#rids)
|
||||
self.bosh_rid = r.getrandbits(50)
|
||||
initial_body_tag.setAttr('rid', str(self.bosh_rid))
|
||||
|
||||
if locale.getdefaultlocale()[0]:
|
||||
initial_body_tag.setAttr('xml:lang',
|
||||
locale.getdefaultlocale()[0].split('_')[0])
|
||||
initial_body_tag.setAttr('xmpp:version', '1.0')
|
||||
initial_body_tag.setAttr('xmlns:xmpp', 'urn:xmpp:xbosh')
|
||||
|
||||
self.send(self.build_bosh_message(initial_body_tag))
|
||||
|
||||
|
||||
def build_bosh_message(self, httpbody):
|
||||
'''
|
||||
Builds bosh http message with given body.
|
||||
Values for headers and status line fields are taken from class variables.
|
||||
)
|
||||
'''
|
||||
headers = ['POST %s HTTP/1.1' % self.bosh_uri,
|
||||
'Host: %s' % self.bosh_host,
|
||||
'Content-Type: text/xml; charset=utf-8',
|
||||
'Content-Length: %s' % len(str(httpbody)),
|
||||
'\r\n']
|
||||
headers = '\r\n'.join(headers)
|
||||
return('%s%s\r\n' % (headers, httpbody))
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Example script for usage of BOSHClient class
|
||||
# --------------------------------------------
|
||||
# run `python run_client_bosh.py` in gajim/src/common/xmpp/examples/ directory
|
||||
# and quit with CTRL + c.
|
||||
# Script will open TCP connection to Connection Manager, send BOSH initial
|
||||
# request and receive initial response. Handling of init response is not
|
||||
# done yet.
|
||||
|
||||
|
||||
# imports gtk because of gobject.timeout_add() which is used for processing
|
||||
# idlequeue
|
||||
# TODO: rewrite to thread timer
|
||||
import gtk
|
||||
import gobject
|
||||
import sys, os.path
|
||||
|
||||
xmpppy_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
|
||||
sys.path.append(xmpppy_dir)
|
||||
|
||||
import idlequeue
|
||||
from client_bosh import BOSHClient
|
||||
|
||||
|
||||
class DummyConnection():
|
||||
'''
|
||||
Dummy class for test run of bosh_client. I use it because in Connection class
|
||||
the gajim.py module is imported and stuff from it is read in there, so it
|
||||
would be difficult to debug IMHO. On the other hand, I will have to test with
|
||||
Connection class sooner or later somehow because that's the main place where
|
||||
BOSHClient should be used.
|
||||
DummyConnection class holds and processes IdleQueue for BOSHClient.
|
||||
'''
|
||||
|
||||
def __init__(self, iq_interval_ms=1000):
|
||||
self.classname = self.__class__.__name__
|
||||
self.iq_interval_ms = iq_interval_ms
|
||||
self.idlequeue = idlequeue.IdleQueue()
|
||||
self.timer=gobject.timeout_add(iq_interval_ms, self.process_idlequeue)
|
||||
|
||||
|
||||
def process_idlequeue(self):
|
||||
'''
|
||||
called each iq_interval_ms miliseconds. Checks for idlequeue timeouts.
|
||||
'''
|
||||
self.idlequeue.process()
|
||||
return True
|
||||
|
||||
# callback stubs follows
|
||||
def _event_dispatcher(self, realm, event, data):
|
||||
print "\n>>> %s._event_dispatcher called:" % self.classname
|
||||
print ">>> realm: %s, event: %s, data: %s\n" % (realm, event, data)
|
||||
|
||||
|
||||
def onconsucc(self):
|
||||
print '%s: CONNECTION SUCCEEDED' % self.classname
|
||||
|
||||
|
||||
def onconfail(self, retry=None):
|
||||
print '%s: CONNECTION FAILED.. retry?: %s' % (self.classname, retry)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dc = DummyConnection()
|
||||
|
||||
# you can use my instalation of ejabberd2:
|
||||
server = 'star.securitynet.cz'
|
||||
bosh_conn_mgr = 'http://star.securitynet.cz/http-bind/'
|
||||
|
||||
#server='jabbim.cz'
|
||||
#bosh_conn_mgr='http://bind.jabbim.cz/'
|
||||
|
||||
bc = BOSHClient(
|
||||
server = server,
|
||||
bosh_conn_mgr = bosh_conn_mgr,
|
||||
bosh_port = 80,
|
||||
on_connect = dc.onconsucc,
|
||||
on_connect_failure = dc.onconfail,
|
||||
caller = dc
|
||||
)
|
||||
|
||||
bc.set_idlequeue(dc.idlequeue)
|
||||
|
||||
bc.connect()
|
||||
|
||||
try:
|
||||
gtk.main()
|
||||
except KeyboardInterrupt:
|
||||
dc.process_idlequeue()
|
||||
|
|
@ -33,7 +33,16 @@ import thread
|
|||
import logging
|
||||
log = logging.getLogger('gajim.c.x.transports_nb')
|
||||
|
||||
import common.gajim
|
||||
# I don't need to load gajim.py just because of few TLS variables, so I changed
|
||||
# :%s/common\.gajim\.DATA_DIR/\'\.\.\/data\'/c
|
||||
# :%s/common\.gajim\.MY_CACERTS/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/c
|
||||
|
||||
# To change it back do:
|
||||
# %s/\'\.\.\/data\'/common\.gajim\.DATA_DIR/c
|
||||
# :%s/\'\%s\/\.gajim\/cacerts\.pem\' \% os\.environ\[\'HOME\'\]/common\.gajim\.MY_CACERTS/c
|
||||
|
||||
# import common.gajim
|
||||
|
||||
|
||||
USE_PYOPENSSL = False
|
||||
|
||||
|
@ -762,16 +771,16 @@ class NonBlockingTLS(PlugIn):
|
|||
#tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
|
||||
tcpsock.ssl_errnum = 0
|
||||
tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback)
|
||||
cacerts = os.path.join(common.gajim.DATA_DIR, 'other', 'cacerts.pem')
|
||||
cacerts = os.path.join('../data', 'other', 'cacerts.pem')
|
||||
try:
|
||||
tcpsock._sslContext.load_verify_locations(cacerts)
|
||||
except:
|
||||
log.warning('Unable to load SSL certificats from file %s' % \
|
||||
os.path.abspath(cacerts))
|
||||
# load users certs
|
||||
if os.path.isfile(common.gajim.MY_CACERTS):
|
||||
if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']):
|
||||
store = tcpsock._sslContext.get_cert_store()
|
||||
f = open(common.gajim.MY_CACERTS)
|
||||
f = open('%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
lines = f.readlines()
|
||||
i = 0
|
||||
begin = -1
|
||||
|
@ -786,11 +795,11 @@ class NonBlockingTLS(PlugIn):
|
|||
store.add_cert(X509cert)
|
||||
except OpenSSL.crypto.Error, exception_obj:
|
||||
log.warning('Unable to load a certificate from file %s: %s' %\
|
||||
(common.gajim.MY_CACERTS, exception_obj.args[0][0][2]))
|
||||
('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2]))
|
||||
except:
|
||||
log.warning(
|
||||
'Unknown error while loading certificate from file %s' % \
|
||||
common.gajim.MY_CACERTS)
|
||||
'%s/.gajim/cacerts.pem' % os.environ['HOME'])
|
||||
begin = -1
|
||||
i += 1
|
||||
tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock)
|
||||
|
|
Loading…
Reference in New Issue