- implemented BOSH key sequencing, acknowledgements

- improved HTTP persistent connections
- added alarm-unregister method to idlequeue
- extended proxy managing dialog for BOSH proxy
This commit is contained in:
tomk 2008-07-26 22:42:40 +00:00
parent a58618c843
commit af3f1a9dd4
10 changed files with 690 additions and 250 deletions

View file

@ -235,6 +235,7 @@ BOSH</property>
<property name="max_length">0</property> <property name="max_length">0</property>
<property name="text" translatable="yes"></property> <property name="text" translatable="yes"></property>
<property name="has_frame">True</property> <property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property> <property name="activates_default">False</property>
<signal name="changed" handler="on_proxyname_entry_changed" last_modification_time="Wed, 08 Jun 2005 17:43:44 GMT"/> <signal name="changed" handler="on_proxyname_entry_changed" last_modification_time="Wed, 08 Jun 2005 17:43:44 GMT"/>
</widget> </widget>
@ -305,7 +306,7 @@ BOSH</property>
<widget class="GtkTable" id="proxy_table"> <widget class="GtkTable" id="proxy_table">
<property name="visible">True</property> <property name="visible">True</property>
<property name="sensitive">False</property> <property name="sensitive">False</property>
<property name="n_rows">5</property> <property name="n_rows">8</property>
<property name="n_columns">2</property> <property name="n_columns">2</property>
<property name="homogeneous">False</property> <property name="homogeneous">False</property>
<property name="row_spacing">6</property> <property name="row_spacing">6</property>
@ -314,7 +315,7 @@ BOSH</property>
<child> <child>
<widget class="GtkLabel" id="label136"> <widget class="GtkLabel" id="label136">
<property name="visible">True</property> <property name="visible">True</property>
<property name="label" translatable="yes">_Port:</property> <property name="label" translatable="yes">Proxy _Port:</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="use_markup">False</property> <property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property> <property name="justify">GTK_JUSTIFY_LEFT</property>
@ -333,8 +334,8 @@ BOSH</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="right_attach">1</property> <property name="right_attach">1</property>
<property name="top_attach">1</property> <property name="top_attach">4</property>
<property name="bottom_attach">2</property> <property name="bottom_attach">5</property>
<property name="x_options">fill</property> <property name="x_options">fill</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -349,14 +350,15 @@ BOSH</property>
<property name="max_length">0</property> <property name="max_length">0</property>
<property name="text" translatable="yes"></property> <property name="text" translatable="yes"></property>
<property name="has_frame">True</property> <property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property> <property name="activates_default">False</property>
<signal name="changed" handler="on_proxyhost_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:56:20 GMT"/> <signal name="changed" handler="on_proxyhost_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:56:20 GMT"/>
</widget> </widget>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">0</property> <property name="top_attach">3</property>
<property name="bottom_attach">1</property> <property name="bottom_attach">4</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
@ -370,14 +372,15 @@ BOSH</property>
<property name="max_length">0</property> <property name="max_length">0</property>
<property name="text"></property> <property name="text"></property>
<property name="has_frame">True</property> <property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property> <property name="activates_default">False</property>
<signal name="changed" handler="on_proxyport_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:57:45 GMT"/> <signal name="changed" handler="on_proxyport_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:57:45 GMT"/>
</widget> </widget>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">1</property> <property name="top_attach">4</property>
<property name="bottom_attach">2</property> <property name="bottom_attach">5</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
@ -385,7 +388,7 @@ BOSH</property>
<child> <child>
<widget class="GtkLabel" id="label135"> <widget class="GtkLabel" id="label135">
<property name="visible">True</property> <property name="visible">True</property>
<property name="label" translatable="yes">_Host:</property> <property name="label" translatable="yes">Proxy _Host:</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="use_markup">False</property> <property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property> <property name="justify">GTK_JUSTIFY_LEFT</property>
@ -404,8 +407,8 @@ BOSH</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="right_attach">1</property> <property name="right_attach">1</property>
<property name="top_attach">0</property> <property name="top_attach">3</property>
<property name="bottom_attach">1</property> <property name="bottom_attach">4</property>
<property name="x_options">fill</property> <property name="x_options">fill</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -433,8 +436,8 @@ BOSH</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="right_attach">1</property> <property name="right_attach">1</property>
<property name="top_attach">4</property> <property name="top_attach">7</property>
<property name="bottom_attach">5</property> <property name="bottom_attach">8</property>
<property name="x_options">fill</property> <property name="x_options">fill</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -462,8 +465,8 @@ BOSH</property>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="right_attach">1</property> <property name="right_attach">1</property>
<property name="top_attach">3</property> <property name="top_attach">6</property>
<property name="bottom_attach">4</property> <property name="bottom_attach">7</property>
<property name="x_options">fill</property> <property name="x_options">fill</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
@ -478,14 +481,15 @@ BOSH</property>
<property name="max_length">0</property> <property name="max_length">0</property>
<property name="text" translatable="yes"></property> <property name="text" translatable="yes"></property>
<property name="has_frame">True</property> <property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property> <property name="activates_default">False</property>
<signal name="changed" handler="on_proxypass_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:58:01 GMT"/> <signal name="changed" handler="on_proxypass_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:58:01 GMT"/>
</widget> </widget>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">4</property> <property name="top_attach">7</property>
<property name="bottom_attach">5</property> <property name="bottom_attach">8</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
@ -499,14 +503,15 @@ BOSH</property>
<property name="max_length">0</property> <property name="max_length">0</property>
<property name="text" translatable="yes"></property> <property name="text" translatable="yes"></property>
<property name="has_frame">True</property> <property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property> <property name="activates_default">False</property>
<signal name="changed" handler="on_proxyuser_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:57:53 GMT"/> <signal name="changed" handler="on_proxyuser_entry_changed" last_modification_time="Wed, 08 Jun 2005 20:57:53 GMT"/>
</widget> </widget>
<packing> <packing>
<property name="left_attach">1</property> <property name="left_attach">1</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
<property name="top_attach">3</property> <property name="top_attach">6</property>
<property name="bottom_attach">4</property> <property name="bottom_attach">7</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
@ -515,7 +520,7 @@ BOSH</property>
<widget class="GtkCheckButton" id="useauth_checkbutton"> <widget class="GtkCheckButton" id="useauth_checkbutton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="label" translatable="yes">Use authentication</property> <property name="label" translatable="yes">Use proxy authentication</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property> <property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
@ -524,6 +529,80 @@ BOSH</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_useauth_checkbutton_toggled" last_modification_time="Wed, 08 Jun 2005 10:56:33 GMT"/> <signal name="toggled" handler="on_useauth_checkbutton_toggled" last_modification_time="Wed, 08 Jun 2005 10:56:33 GMT"/>
</widget> </widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="boshuri_label">
<property name="visible">True</property>
<property name="label" translatable="yes">_BOSH URL:</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="mnemonic_widget">proxyhost_entry</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="boshuri_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<signal name="changed" handler="on_boshuri_entry_changed" last_modification_time="Thu, 24 Jul 2008 20:27:43 GMT"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="boshuseproxy_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Use HTTP proxy</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_boshuseproxy_checkbutton_toggled" last_modification_time="Thu, 24 Jul 2008 20:51:01 GMT"/>
</widget>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>
<property name="right_attach">2</property> <property name="right_attach">2</property>
@ -533,6 +612,57 @@ BOSH</property>
<property name="y_options"></property> <property name="y_options"></property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkLabel" id="boshport_label">
<property name="visible">True</property>
<property name="label" translatable="yes">B_OSH Port:</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="mnemonic_widget">proxyhost_entry</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="boshport_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
<signal name="changed" handler="on_boshport_entry_changed" last_modification_time="Fri, 25 Jul 2008 21:27:58 GMT"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
</packing>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>

View file

@ -337,8 +337,17 @@ class Config:
'type': [ opt_str, 'http' ], 'type': [ opt_str, 'http' ],
'host': [ opt_str, '' ], 'host': [ opt_str, '' ],
'port': [ opt_int, 3128 ], 'port': [ opt_int, 3128 ],
'useauth': [ opt_bool, False ],
'user': [ opt_str, '' ], 'user': [ opt_str, '' ],
'pass': [ opt_str, '' ], 'pass': [ opt_str, '' ],
'bosh_uri': [ opt_str, '' ],
'bosh_port': [ opt_int, 80 ],
'bosh_useproxy': [ opt_bool, False ],
'bosh_wait': [ opt_int, 30 ],
'bosh_hold': [ opt_int, 2 ],
'bosh_content': [ opt_str, 'text/xml; charset=utf-8' ],
'bosh_http_pipelining': [ opt_bool, False ],
'bosh_wait_for_restart_response': [ opt_bool, False ],
}, {}), }, {}),
'themes': ({ 'themes': ({
'accounttextcolor': [ opt_color, 'black', '', True ], 'accounttextcolor': [ opt_color, 'black', '', True ],

View file

@ -428,11 +428,11 @@ class Connection(ConnectionHandlers):
# create connection if it doesn't already exist # create connection if it doesn't already exist
self.connected = 1 self.connected = 1
if p and p in gajim.config.get_per('proxies'): if p and p in gajim.config.get_per('proxies'):
proxy = {'host': gajim.config.get_per('proxies', p, 'host')} proxy = {}
proxy['port'] = gajim.config.get_per('proxies', p, 'port') proxyptr = gajim.config.get_per('proxies',p)
proxy['user'] = gajim.config.get_per('proxies', p, 'user') for key in proxyptr.keys(): proxy[key]=proxyptr[key][1]
proxy['password'] = gajim.config.get_per('proxies', p, 'pass') print proxy
proxy['type'] = gajim.config.get_per('proxies', p, 'type')
elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'): elif gajim.config.get_per('accounts', self.name, 'use_env_http_proxy'):
try: try:
try: try:
@ -546,11 +546,11 @@ class Connection(ConnectionHandlers):
con.RegisterDisconnectHandler(self._on_new_account) con.RegisterDisconnectHandler(self._on_new_account)
# FIXME: BOSH properties should be loaded from config # FIXME: BOSH properties should be loaded from config
if self._proxy and self._proxy['type'] == 'bosh': #if self._proxy and self._proxy['type'] == 'bosh':
self._proxy['bosh_hold'] = '1' # self._proxy['bosh_hold'] = '2'
self._proxy['bosh_wait'] = '60' # self._proxy['bosh_wait'] = '10'
self._proxy['bosh_content'] = 'text/xml; charset=utf-8' # self._proxy['bosh_content'] = 'text/xml; charset=utf-8'
self._proxy['wait_for_restart_response'] = False # self._proxy['wait_for_restart_response'] = False
log.info('Connecting to %s: [%s:%d]', self.name, log.info('Connecting to %s: [%s:%d]', self.name,
@ -1003,7 +1003,7 @@ class Connection(ConnectionHandlers):
self.connection.RegisterDisconnectHandler(self._on_disconnected) self.connection.RegisterDisconnectHandler(self._on_disconnected)
self.connection.send(p, now=True) self.connection.send(p, now=True)
self.connection.StreamTerminate() self.connection.start_disconnect()
#self.connection.start_disconnect(p, self._on_disconnected) #self.connection.start_disconnect(p, self._on_disconnected)
else: else:
self.time_to_reconnect = None self.time_to_reconnect = None

View file

@ -1,16 +1,20 @@
import locale, random import locale, random
from transports_nb import NonBlockingTransport, NonBlockingHTTP, CONNECTED, CONNECTING, DISCONNECTED from transports_nb import NonBlockingTransport, NonBlockingHTTPBOSH,\
CONNECTED, CONNECTING, DISCONNECTED, DISCONNECTING,\
urisplit
from protocol import BOSHBody from protocol import BOSHBody
from simplexml import Node from simplexml import Node
import sha
import logging import logging
log = logging.getLogger('gajim.c.x.bosh') log = logging.getLogger('gajim.c.x.bosh')
KEY_COUNT = 10
FAKE_DESCRIPTOR = -1337 FAKE_DESCRIPTOR = -1337
'''Fake file descriptor - it's used for setting read_timeout in idlequeue for '''Fake file descriptor - it's used for setting read_timeout in idlequeue for
BOSH Transport. Timeouts in queue are saved by socket descriptor. BOSH Transport.
In TCP-derived transports it is file descriptor of socket''' In TCP-derived transports it is file descriptor of socket'''
@ -19,12 +23,6 @@ class NonBlockingBOSH(NonBlockingTransport):
bosh_dict): bosh_dict):
NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTransport.__init__(self, raise_event, on_disconnect, idlequeue)
# 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)
r = random.Random()
r.seed()
self.bosh_rid = r.getrandbits(50)
self.bosh_sid = None self.bosh_sid = None
if locale.getdefaultlocale()[0]: if locale.getdefaultlocale()[0]:
self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]
@ -33,25 +31,30 @@ class NonBlockingBOSH(NonBlockingTransport):
self.http_version = 'HTTP/1.1' self.http_version = 'HTTP/1.1'
self.http_persistent = True self.http_persistent = True
self.http_pipelining = False self.http_pipelining = bosh_dict['bosh_http_pipelining']
self.bosh_to = domain self.bosh_to = domain
self.route_host, self.route_port = xmpp_server self.route_host, self.route_port = xmpp_server
self.bosh_wait = bosh_dict['bosh_wait'] self.bosh_wait = bosh_dict['bosh_wait']
self.bosh_hold = bosh_dict['bosh_hold'] self.bosh_hold = bosh_dict['bosh_hold']
self.bosh_host = bosh_dict['host'] self.bosh_requests = self.bosh_hold
self.bosh_port = bosh_dict['port'] self.bosh_uri = bosh_dict['bosh_uri']
self.bosh_port = bosh_dict['bosh_port']
self.bosh_content = bosh_dict['bosh_content'] self.bosh_content = bosh_dict['bosh_content']
self.wait_cb_time = None
self.http_socks = [] self.http_socks = []
self.stanzas_to_send = [] self.stanza_buffer = []
self.prio_bosh_stanza = None self.prio_bosh_stanzas = []
self.current_recv_handler = None self.current_recv_handler = None
self.current_recv_socket = None
self.key_stack = None
self.ack_checker = None
self.after_init = False
# if proxy_host .. do sth about HTTP proxy etc. # if proxy_host .. do sth about HTTP proxy etc.
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure) NonBlockingTransport.connect(self, conn_5tuple, on_connect, on_connect_failure)
@ -59,14 +62,20 @@ class NonBlockingBOSH(NonBlockingTransport):
FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1 FAKE_DESCRIPTOR = FAKE_DESCRIPTOR - 1
self.fd = FAKE_DESCRIPTOR self.fd = FAKE_DESCRIPTOR
self.http_persistent = True self.stanza_buffer = []
self.http_socks.append(self.get_http_socket()) self.prio_bosh_stanzas = []
self.key_stack = KeyStack(KEY_COUNT)
self.ack_checker = AckChecker()
self.after_init = True
self.http_socks.append(self.get_new_http_socket())
self.tcp_connection_started() self.tcp_connection_started()
# this connect() is not needed because sockets can be connected on send but # following connect() is not necessary because sockets can be connected on
# we need to know if host is reachable in order to invoke callback for # send but we need to know if host is reachable in order to invoke callback
# connecting failurei eventually (it's different than callback for errors # for connecting failure eventually (the callback is different than callback
# occurring after connection is etabilished) # for errors occurring after connection is etabilished)
self.http_socks[0].connect( self.http_socks[0].connect(
conn_5tuple = conn_5tuple, conn_5tuple = conn_5tuple,
on_connect = lambda: self._on_connect(self.http_socks[0]), on_connect = lambda: self._on_connect(self.http_socks[0]),
@ -83,121 +92,209 @@ class NonBlockingBOSH(NonBlockingTransport):
Called after HTTP response is received - another request is possible. Called after HTTP response is received - another request is possible.
There should be always one pending request on BOSH CM. There should be always one pending request on BOSH CM.
''' '''
log.info('on_http_req possible state:\n%s' % self.get_current_state()) log.info('on_http_req possible, state:\n%s' % self.get_current_state())
# if one of sockets is connecting, sth is about to be sent if self.state == DISCONNECTING:
# if there is a pending request, we shouldn't send another one self.disconnect()
return
self.send_BOSH(None)
def get_socket_in(self, state):
for s in self.http_socks: for s in self.http_socks:
if s.state==CONNECTING or s.pending_requests>0: return if s.state==state: return s
self.flush_stanzas() return None
def get_free_socket(self):
def flush_stanzas(self): if self.http_pipelining:
# another to-be-locked candidate assert( len(self.http_socks) == 1 )
log.info('flushing stanzas') return self.get_socket_in(CONNECTED)
if self.prio_bosh_stanza:
tmp = self.prio_bosh_stanza
self.prio_bosh_stanza = None
else: else:
if self.stanzas_to_send: last_recv_time, tmpsock = 0, None
tmp = self.stanzas_to_send.pop(0) for s in self.http_socks:
else: # we're interested only into CONNECTED socket with no req pending
tmp = [] if s.state==CONNECTED and s.pending_requests==0:
self.send_http(tmp) # if there's more of them, we want the one with less recent data receive
# (lowest last_recv_time)
if (last_recv_time==0) or (s.last_recv_time < last_recv_time):
last_recv_time = s.last_recv_time
tmpsock = s
if tmpsock:
return tmpsock
else:
return None
def send_BOSH(self, payload):
total_pending_reqs = sum([s.pending_requests for s in self.http_socks])
# when called after HTTP response when there are some pending requests and
# no data to send, we do nothing and disccard the payload
if payload is None and \
total_pending_reqs > 0 and \
self.stanza_buffer == [] and \
self.prio_bosh_stanzas == [] or \
self.state==DISCONNECTED:
return
# now the payload is put to buffer and will be sent at some point
self.append_stanza(payload)
# if we're about to make more requests than allowed, we don't send - stanzas will be
# sent after HTTP response from CM, exception is when we're disconnecting - then we
# send anyway
if total_pending_reqs >= self.bosh_requests and self.state!=DISCONNECTING:
log.warn('attemp to make more requests than allowed by Connection Manager:\n%s' %
self.get_current_state())
return
# when there's free CONNECTED socket, we flush the data
if self.get_free_socket():
self.plug_socket()
return
# if there is a connecting socket, we just wait for when it connects,
# payload will be sent in a sec when the socket connects
if self.get_socket_in(CONNECTING): return
# being here means there are either DISCONNECTED sockets or all sockets are
# CONNECTED with too many pending requests
s = self.get_socket_in(DISCONNECTED)
# if we have DISCONNECTED socket, lets connect it and ...
if s:
self.connect_and_flush(s)
else:
if len(self.http_socks) > 1: return
ss = self.get_new_http_socket()
self.http_socks.append(ss)
self.connect_and_flush(ss)
return
def plug_socket(self):
stanza = None
s = self.get_free_socket()
if s:
s._plug_idle(writable=True, readable=True)
else:
log.error('=====!!!!!!!!====> Couldnt get free socket in plug_socket())')
def build_stanza(self, socket):
if self.prio_bosh_stanzas:
stanza, add_payload = self.prio_bosh_stanzas.pop(0)
if add_payload:
stanza.setPayload(self.stanza_buffer)
self.stanza_buffer = []
else:
stanza = self.boshify_stanzas(self.stanza_buffer)
self.stanza_buffer = []
stanza = self.ack_checker.backup_stanza(stanza, socket)
key, newkey = self.key_stack.get()
if key:
stanza.setAttr('key', key)
if newkey:
stanza.setAttr('newkey', newkey)
log.info('sending msg with rid=%s to sock %s' % (stanza.getAttr('rid'), id(socket)))
socket.send(stanza)
self.renew_bosh_wait_timeout()
return stanza
def on_bosh_wait_timeout(self):
log.error('Connection Manager didn\'t respond within % seconds --> forcing \
disconnect' % self.bosh_wait)
self.disconnect()
def renew_bosh_wait_timeout(self):
if self.wait_cb_time is not None:
self.remove_bosh_wait_timeout()
sched_time = self.idlequeue.set_alarm(self.on_bosh_wait_timeout, self.bosh_wait+10)
self.wait_cb_time = sched_time
def remove_bosh_wait_timeout(self):
self.idlequeue.remove_alarm(
self.on_bosh_wait_timeout,
self.wait_cb_time)
def on_persistent_fallback(self):
log.warn('Fallback to nonpersistent HTTP (no pipelining as well)')
self.http_persistent = False
self.http_pipelining = False
def handle_body_attrs(self, stanza_attrs):
self.remove_bosh_wait_timeout()
if self.after_init:
self.after_init = False
if stanza_attrs.has_key('sid'):
# session ID should be only in init response
self.bosh_sid = stanza_attrs['sid']
if stanza_attrs.has_key('requests'):
#self.bosh_requests = int(stanza_attrs['requests'])
self.bosh_requests = int(stanza_attrs['wait'])
if stanza_attrs.has_key('wait'):
self.bosh_wait = int(stanza_attrs['wait'])
ack = None
if stanza_attrs.has_key('ack'):
ack = stanza_attrs['ack']
self.ack_checker.process_incoming_ack(ack=ack,
socket=self.current_recv_socket)
if stanza_attrs.has_key('type'):
if stanza_attrs['type'] in ['terminate', 'terminal']:
condition = 'n/a'
if stanza_attrs.has_key('condition'):
condition = stanza_attrs['condition']
log.error('Received terminating stanza: %s - %s' % (condition, bosh_errors[condition]))
self.set_state(DISCONNECTING)
if stanza_attrs['type'] == 'error':
# recoverable error
pass
return
def append_stanza(self, stanza):
if stanza:
if isinstance(stanza, tuple):
# tuple of BOSH stanza and True/False for whether to add payload
self.prio_bosh_stanzas.append(stanza)
else:
self.stanza_buffer.append(stanza)
def send(self, stanza, now=False): def send(self, stanza, now=False):
# body tags should be send only via send_http() # body tags should be send only via send_BOSH()
assert(not isinstance(stanza, BOSHBody)) assert(not isinstance(stanza, BOSHBody))
self.send_http([stanza]) self.send_BOSH(stanza)
def send_http(self, payload):
# "Protocol" and string/unicode stanzas should be sent via send()
# (only initiating and terminating BOSH stanzas should be send via send_http)
assert(isinstance(payload, list) or isinstance(payload, BOSHBody))
log.warn('send_http: stanzas: %s\n%s' % (payload, self.get_current_state()))
if isinstance(payload, list):
bosh_stanza = self.boshify_stanzas(payload)
else:
# bodytag_payload is <body ...>, we don't boshify, only add the rid
bosh_stanza = payload
picked_sock = self.pick_socket()
if picked_sock:
log.info('sending to socket %s' % id(picked_sock))
bosh_stanza.setAttr('rid', self.get_rid())
picked_sock.send(bosh_stanza)
else:
# no socket was picked but one is about to connect - save the stanza and
# return
log.info('send_http: no free socket:\n%s' % self.get_current_state())
if self.prio_bosh_stanza:
payload = self.merge_stanzas(payload, self.prio_bosh_stanza)
if payload is None:
# if we cant merge the stanzas (both are BOSH <body>), add the current to
# queue to be sent later
self.stanzas_to_send.append(bosh_stanza)
log.warn('in BOSH send_http - unable to send %s because %s\
is already about to be sent' % (str(payload), str(self.prio_bosh_stanza)))
return
self.prio_bosh_stanza = payload
def merge_stanzas(self, s1, s2):
if isinstance(s1, BOSHBody):
if isinstance(s2, BOSHBody):
# both are boshbodies
return
else:
s1.setPayload(s2, add=True)
return s1
elif isinstance(s2, BOSHBody):
s2.setPayload(s1, add=True)
return s2
else:
#both are lists
s1.extend(s2)
return s1
def get_current_state(self): def get_current_state(self):
t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n' t = '------ SOCKET_ID\tSOCKET_STATE\tPENDING_REQS\n'
for s in self.http_socks: for s in self.http_socks:
t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests) t = '%s------ %s\t%s\t%s\n' % (t,id(s), s.state, s.pending_requests)
t = '%s------ prio stanza to send: %s, queued stanzas: %s' \ t = '%s------ prio stanzas: %s, queued XMPP stanzas: %s, not_acked stanzas: %s' \
% (t, self.prio_bosh_stanza, self.stanzas_to_send) % (t, self.prio_bosh_stanzas, self.stanza_buffer,
self.ack_checker.get_not_acked_rids())
return t return t
def pick_socket(self):
# try to pick connected socket with no pending reqs
for s in self.http_socks:
if s.state == CONNECTED and s.pending_requests == 0:
return s
# try to connect some disconnected socket
for s in self.http_socks:
if s.state==DISCONNECTED:
self.connect_and_flush(s)
return
# if there is any just-connecting socket, it will send the data in its
# connect callback
for s in self.http_socks:
if s.state==CONNECTING:
return
# being here means there are only CONNECTED scokets with pending requests.
# Lets create and connect another one
if len(self.http_socks) < 2:
s = self.get_http_socket()
self.http_socks.append(s)
self.connect_and_flush(s)
return
def connect_and_flush(self, socket): def connect_and_flush(self, socket):
socket.connect( socket.connect(
conn_5tuple = self.conn_5tuple, conn_5tuple = self.conn_5tuple,
on_connect = self.flush_stanzas, on_connect = lambda :self.send_BOSH(None),
on_connect_failure = self.disconnect) on_connect_failure = self.disconnect)
@ -209,65 +306,163 @@ class NonBlockingBOSH(NonBlockingTransport):
return tag return tag
def get_initial_bodytag(self, after_SASL=False): def send_init(self, after_SASL=False):
return BOSHBody( if after_SASL:
attrs={'content': self.bosh_content, t = BOSHBody(
'hold': str(self.bosh_hold), attrs={ 'to': self.bosh_to,
'route': '%s:%s' % (self.route_host, self.route_port), 'sid': self.bosh_sid,
'to': self.bosh_to, 'xml:lang': self.bosh_xml_lang,
'wait': str(self.bosh_wait), 'xmpp:restart': 'true',
'xml:lang': self.bosh_xml_lang, 'xmlns:xmpp': 'urn:xmpp:xbosh'})
'xmpp:version': '1.0', else:
'ver': '1.6', t = BOSHBody(
'xmlns:xmpp': 'urn:xmpp:xbosh'}) attrs={ 'content': self.bosh_content,
'hold': str(self.bosh_hold),
'route': '%s:%s' % (self.route_host, self.route_port),
'to': self.bosh_to,
'wait': str(self.bosh_wait),
'xml:lang': self.bosh_xml_lang,
'xmpp:version': '1.0',
'ver': '1.6',
'xmlns:xmpp': 'urn:xmpp:xbosh'})
self.send_BOSH((t,True))
def get_after_SASL_bodytag(self): def start_disconnect(self):
return BOSHBody( NonBlockingTransport.start_disconnect(self)
attrs={ 'to': self.bosh_to, self.send_BOSH(
'sid': self.bosh_sid, (BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'}), True))
'xml:lang': self.bosh_xml_lang,
'xmpp:restart': 'true',
'xmlns:xmpp': 'urn:xmpp:xbosh'})
def get_closing_bodytag(self):
return BOSHBody(attrs={'sid': self.bosh_sid, 'type': 'terminate'})
def get_rid(self):
self.bosh_rid = self.bosh_rid + 1
return str(self.bosh_rid)
def get_http_socket(self): def get_new_http_socket(self):
s = NonBlockingHTTP( s = NonBlockingHTTPBOSH(
raise_event=self.raise_event, raise_event=self.raise_event,
on_disconnect=self.disconnect, on_disconnect=self.disconnect,
idlequeue = self.idlequeue, idlequeue = self.idlequeue,
on_http_request_possible = self.on_http_request_possible, on_http_request_possible = self.on_http_request_possible,
http_uri = self.bosh_host, http_uri = self.bosh_uri,
http_port = self.bosh_port, http_port = self.bosh_port,
http_version = self.http_version, http_version = self.http_version,
http_persistent = self.http_persistent) http_persistent = self.http_persistent,
if self.current_recv_handler: on_persistent_fallback = self.on_persistent_fallback)
s.onreceive(self.current_recv_handler) s.onreceive(self.on_received_http)
s.set_stanza_build_cb(self.build_stanza)
return s return s
def onreceive(self, recv_handler): def onreceive(self, recv_handler):
if recv_handler is None: if recv_handler is None:
recv_handler = self._owner.Dispatcher.ProcessNonBlocking recv_handler = self._owner.Dispatcher.ProcessNonBlocking
self.current_recv_handler = recv_handler self.current_recv_handler = recv_handler
for s in self.http_socks:
s.onreceive(recv_handler)
def http_socket_disconnect(self, socket):
if self.http_persistent:
self.disconnect()
def on_received_http(self, data, socket):
self.current_recv_socket = socket
self.current_recv_handler(data)
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
self.remove_bosh_wait_timeout()
if self.state == DISCONNECTED: return if self.state == DISCONNECTED: return
self.fd = -1 self.fd = -1
for s in self.http_socks: for s in self.http_socks:
s.disconnect(do_callback=False) s.disconnect(do_callback=False)
NonBlockingTransport.disconnect(self, do_callback) NonBlockingTransport.disconnect(self, do_callback)
def get_rand_number():
# 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)
# it's also used for sequence key initialization
r = random.Random()
r.seed()
return r.getrandbits(50)
class AckChecker():
def __init__(self):
self.rid = get_rand_number()
self.ack = 1
self.last_rids = {}
self.not_acked = []
def get_not_acked_rids(self): return [rid for rid, st in self.not_acked]
def backup_stanza(self, stanza, socket):
socket.pending_requests += 1
rid = self.get_rid()
self.not_acked.append((rid, stanza))
stanza.setAttr('rid', str(rid))
self.last_rids[socket]=rid
if self.rid != self.ack + 1:
stanza.setAttr('ack', str(self.ack))
return stanza
def process_incoming_ack(self, socket, ack=None):
socket.pending_requests -= 1
if ack:
ack = int(ack)
else:
ack = self.last_rids[socket]
i = len([rid for rid, st in self.not_acked if ack >= rid])
self.not_acked = self.not_acked[i:]
self.ack = ack
def get_rid(self):
self.rid = self.rid + 1
return self.rid
class KeyStack():
def __init__(self, count):
self.count = count
self.keys = []
self.reset()
self.first_call = True
def reset(self):
seed = str(get_rand_number())
self.keys = [sha.new(seed).hexdigest()]
for i in range(self.count-1):
curr_seed = self.keys[i]
self.keys.append(sha.new(curr_seed).hexdigest())
def get(self):
if self.first_call:
self.first_call = False
return (None, self.keys.pop())
if len(self.keys)>1:
return (self.keys.pop(), None)
else:
last_key = self.keys.pop()
self.reset()
new_key = self.keys.pop()
return (last_key, new_key)
# http://www.xmpp.org/extensions/xep-0124.html#errorstatus-terminal
bosh_errors = {
'n/a': 'none or unknown condition in terminating body stanza',
'bad-request': 'The format of an HTTP header or binding element received from the client is unacceptable (e.g., syntax error), or Script Syntax is not supported.',
'host-gone': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is no longer serviced by the connection manager.',
'host-unknown': 'The target domain specified in the "to" attribute or the target host or port specified in the "route" attribute is unknown to the connection manager.',
'improper-addressing': 'The initialization element lacks a "to" or "route" attribute (or the attribute has no value) but the connection manager requires one.',
'internal-server-error': 'The connection manager has experienced an internal error that prevents it from servicing the request.',
'item-not-found': '(1) "sid" is not valid, (2) "stream" is not valid, (3) "rid" is larger than the upper limit of the expected window, (4) connection manager is unable to resend response, (5) "key" sequence is invalid',
'other-request': 'Another request being processed at the same time as this request caused the session to terminate.',
'policy-violation': 'The client has broken the session rules (polling too frequently, requesting too frequently, too many simultaneous requests).',
'remote-connection-failed': 'The connection manager was unable to connect to, or unable to connect securely to, or has lost its connection to, the server.',
'remote-stream-error': 'Encapsulates an error in the protocol being transported.',
'see-other-uri': 'The connection manager does not operate at this URI (e.g., the connection manager accepts only SSL or TLS connections at some https: URI rather than the http: URI requested by the client). The client may try POSTing to the URI in the content of the <uri/> child element.',
'system-shutdown': 'The connection manager is being shut down. All active HTTP sessions are being terminated. No new sessions can be created.',
'undefined-condition': 'The error is not one of those defined herein; the connection manager SHOULD include application-specific information in the content of the <body/> wrapper.'
}

View file

@ -128,8 +128,8 @@ class NBCommonClient:
self.ip_addresses = socket.getaddrinfo(hostname,port, self.ip_addresses = socket.getaddrinfo(hostname,port,
socket.AF_UNSPEC,socket.SOCK_STREAM) socket.AF_UNSPEC,socket.SOCK_STREAM)
except socket.gaierror, (errnum, errstr): except socket.gaierror, (errnum, errstr):
on_failure(err_message='Lookup failure for %s:%s - %s %s' % on_failure('Lookup failure for %s:%s, hostname: %s - %s' %
(self.Server, self.Port, errnum, errstr)) (self.Server, self.Port, hostname, errstr))
else: else:
on_success() on_success()
@ -385,7 +385,7 @@ class NonBlockingClient(NBCommonClient):
# with proxies, client connects to proxy instead of directly to # with proxies, client connects to proxy instead of directly to
# XMPP server ((hostname, port)) # XMPP server ((hostname, port))
# tcp_host is hostname of machine used for socket connection # tcp_host is hostname of machine used for socket connection
# (DNS request will be done for this hostname) # (DNS request will be done for proxy or BOSH CM hostname)
tcp_host, tcp_port, proxy_user, proxy_pass = \ tcp_host, tcp_port, proxy_user, proxy_pass = \
transports_nb.get_proxy_data_from_dict(proxy) transports_nb.get_proxy_data_from_dict(proxy)
@ -400,7 +400,7 @@ class NonBlockingClient(NBCommonClient):
domain = self.Server, domain = self.Server,
bosh_dict = proxy) bosh_dict = proxy)
self.protocol_type = 'BOSH' self.protocol_type = 'BOSH'
self.wait_for_restart_response = proxy['wait_for_restart_response'] self.wait_for_restart_response = proxy['bosh_wait_for_restart_response']
else: else:
if proxy['type'] == 'socks5': if proxy['type'] == 'socks5':

View file

@ -35,7 +35,6 @@ log.setLevel(logging.INFO)
DEFAULT_TIMEOUT_SECONDS = 25 DEFAULT_TIMEOUT_SECONDS = 25
ID = 0 ID = 0
STREAM_TERMINATOR = '</stream:stream>'
XML_DECLARATION = '<?xml version=\'1.0\'?>' XML_DECLARATION = '<?xml version=\'1.0\'?>'
# FIXME: ugly # FIXME: ugly
@ -46,7 +45,8 @@ class Dispatcher():
# named by __class__.__name__ of the dispatcher instance .. long story short: # named by __class__.__name__ of the dispatcher instance .. long story short:
# I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp/ # I wrote following to avoid changing each client.Dispatcher.whatever() in xmpp/
# If having two kinds of dispatcher will go well, I will rewrite the # If having two kinds of dispatcher will go well, I will rewrite the dispatcher
# references in other scripts
def PlugIn(self, client_obj, after_SASL=False, old_features=None): def PlugIn(self, client_obj, after_SASL=False, old_features=None):
if client_obj.protocol_type == 'XMPP': if client_obj.protocol_type == 'XMPP':
XMPPDispatcher().PlugIn(client_obj) XMPPDispatcher().PlugIn(client_obj)
@ -71,7 +71,7 @@ class XMPPDispatcher(PlugIn):
self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, \ self._exported_methods=[self.RegisterHandler, self.RegisterDefaultHandler, \
self.RegisterEventHandler, self.UnregisterCycleHandler, self.RegisterCycleHandler, \ self.RegisterEventHandler, self.UnregisterCycleHandler, self.RegisterCycleHandler, \
self.RegisterHandlerOnce, self.UnregisterHandler, self.RegisterProtocol, \ self.RegisterHandlerOnce, self.UnregisterHandler, self.RegisterProtocol, \
self.SendAndWaitForResponse, self.StreamTerminate, \ self.SendAndWaitForResponse, \
self.SendAndCallForResponse, self.getAnID, self.Event, self.send] self.SendAndCallForResponse, self.getAnID, self.Event, self.send]
def getAnID(self): def getAnID(self):
@ -134,9 +134,6 @@ class XMPPDispatcher(PlugIn):
locale.getdefaultlocale()[0].split('_')[0]) locale.getdefaultlocale()[0].split('_')[0])
self._owner.send("%s%s>" % (XML_DECLARATION,str(self._metastream)[:-2])) self._owner.send("%s%s>" % (XML_DECLARATION,str(self._metastream)[:-2]))
def StreamTerminate(self):
''' Send a stream terminator. '''
self._owner.send(STREAM_TERMINATOR)
def _check_stream_start(self, ns, tag, attrs): def _check_stream_start(self, ns, tag, attrs):
if ns<>NS_STREAMS or tag<>'stream': if ns<>NS_STREAMS or tag<>'stream':
@ -445,16 +442,12 @@ class BOSHDispatcher(XMPPDispatcher):
locale.getdefaultlocale()[0].split('_')[0]) locale.getdefaultlocale()[0].split('_')[0])
self.restart = True self.restart = True
if self.after_SASL: self._owner.Connection.send_init(after_SASL = self.after_SASL)
self._owner.Connection.send_http(self._owner.Connection.get_after_SASL_bodytag())
else:
self._owner.Connection.send_http(self._owner.Connection.get_initial_bodytag())
def StreamTerminate(self): def StreamTerminate(self):
''' Send a stream terminator. ''' ''' Send a stream terminator. '''
self._owner.Connection.send_http(self._owner.Connection.get_closing_bodytag()) self._owner.Connection.send_terminator()
def ProcessNonBlocking(self, data=None): def ProcessNonBlocking(self, data=None):
@ -472,22 +465,13 @@ class BOSHDispatcher(XMPPDispatcher):
if stanza.getName()=='body' and stanza.getNamespace()==NS_HTTP_BIND: if stanza.getName()=='body' and stanza.getNamespace()==NS_HTTP_BIND:
stanza_attrs = stanza.getAttrs() stanza_attrs = stanza.getAttrs()
if stanza_attrs.has_key('authid'): if stanza_attrs.has_key('authid'):
# should be only in init response # should be only in init response
# auth module expects id of stream in document attributes # auth module expects id of stream in document attributes
self.Stream._document_attrs['id'] = stanza_attrs['authid'] self.Stream._document_attrs['id'] = stanza_attrs['authid']
if stanza_attrs.has_key('sid'): self._owner.Connection.handle_body_attrs(stanza_attrs)
# session ID should be only in init response
self._owner.Connection.bosh_sid = stanza_attrs['sid']
if stanza_attrs.has_key('terminate'):
self._owner.disconnect()
if stanza_attrs.has_key('error'):
# recoverable error
pass
children = stanza.getChildren() children = stanza.getChildren()

View file

@ -15,7 +15,6 @@
import select import select
import logging import logging
log = logging.getLogger('gajim.c.x.idlequeue') log = logging.getLogger('gajim.c.x.idlequeue')
log.setLevel(logging.DEBUG)
class IdleObject: class IdleObject:
''' base class for all idle listeners, these are the methods, which are called from IdleQueue ''' base class for all idle listeners, these are the methods, which are called from IdleQueue
@ -68,6 +67,25 @@ class IdleQueue:
self.alarms[alarm_time].append(alarm_cb) self.alarms[alarm_time].append(alarm_cb)
else: else:
self.alarms[alarm_time] = [alarm_cb] self.alarms[alarm_time] = [alarm_cb]
return alarm_time
def remove_alarm(self, alarm_cb, alarm_time):
''' removes alarm callback alarm_cb scheduled on alarm_time'''
if not self.alarms.has_key(alarm_time): return False
i = -1
for i in range(len(self.alarms[alarm_time])):
# let's not modify the list inside the loop
if self.alarms[alarm_time][i] is alarm_cb: break
if i != -1:
del self.alarms[alarm_time][i]
if self.alarms[alarm_time] == []:
del self.alarms[alarm_time]
return True
else:
return False
def set_read_timeout(self, fd, seconds): def set_read_timeout(self, fd, seconds):
''' set a new timeout, if it is not removed after 'seconds', ''' set a new timeout, if it is not removed after 'seconds',
@ -91,9 +109,10 @@ class IdleQueue:
for alarm_time in times: for alarm_time in times:
if alarm_time > current_time: if alarm_time > current_time:
break break
for cb in self.alarms[alarm_time]: if self.alarms.has_key(alarm_time):
cb() for cb in self.alarms[alarm_time]:
del(self.alarms[alarm_time]) cb()
del(self.alarms[alarm_time])
def plug_idle(self, obj, writable = True, readable = True): def plug_idle(self, obj, writable = True, readable = True):
if obj.fd == -1: if obj.fd == -1:

View file

@ -46,25 +46,17 @@ def urisplit(uri):
return proto, host, path return proto, host, path
def get_proxy_data_from_dict(proxy): def get_proxy_data_from_dict(proxy):
tcp_host, tcp_port, proxy_user, proxy_pass = None, None, None, None
type = proxy['type'] type = proxy['type']
# with http-connect/socks5 proxy, we do tcp connecting to the proxy machine if type == 'bosh' and not proxy['bosh_useproxy']:
tcp_host, tcp_port = proxy['host'], proxy['port'] # with BOSH not over proxy we have to parse the hostname from BOSH URI
if type == 'bosh': tcp_host, tcp_port = urisplit(proxy['bosh_uri'])[1], proxy['bosh_port']
# in ['host'] is whole URI else:
tcp_host = urisplit(proxy['host'])[1] # with proxy!=bosh or with bosh over HTTP proxy we're connecting to proxy
# in BOSH, client connects to Connection Manager instead of directly to # machine
# XMPP server ((hostname, port)). If HTTP Proxy is specified, client connects tcp_host, tcp_port = proxy['host'], proxy['port']
# to HTTP proxy and Connection Manager is specified at URI and Host header if proxy['useauth']:
# in HTTP message proxy_user, proxy_pass = proxy['user'], proxy['pass']
if proxy.has_key('proxy_host') and proxy.has_key('proxy_port'):
tcp_host, tcp_port = proxy['proxy_host'], proxy['proxy_port']
# user and pass for socks5/http_connect proxy. In case of BOSH, it's user and
# pass for http proxy - If there's no proxy_host they won't be used
if proxy.has_key('user'): proxy_user = proxy['user']
else: proxy_user = None
if proxy.has_key('pass'): proxy_pass = proxy['pass']
else: proxy_pass = None
return tcp_host, tcp_port, proxy_user, proxy_pass return tcp_host, tcp_port, proxy_user, proxy_pass
@ -104,7 +96,7 @@ class NonBlockingTransport(PlugIn):
self.port = None self.port = None
self.state = DISCONNECTED self.state = DISCONNECTED
self._exported_methods=[self.disconnect, self.onreceive, self.set_send_timeout, self._exported_methods=[self.disconnect, self.onreceive, self.set_send_timeout,
self.set_timeout, self.remove_timeout] self.set_timeout, self.remove_timeout, self.start_disconnect]
# time to wait for SOME stanza to come and then send keepalive # time to wait for SOME stanza to come and then send keepalive
self.sendtimeout = 0 self.sendtimeout = 0
@ -152,10 +144,10 @@ class NonBlockingTransport(PlugIn):
self.on_connect_failure(err_message=err_message) self.on_connect_failure(err_message=err_message)
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
if self.state != CONNECTED: if self.state not in [CONNECTED]:
log.error('Trying to send %s when state is %s.' % log.error('Unable to send %s \n because state is %s.' %
(raw_data, self.state)) (raw_data, self.state))
return
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
self.set_state(DISCONNECTED) self.set_state(DISCONNECTED)
@ -205,6 +197,10 @@ class NonBlockingTransport(PlugIn):
else: else:
self.on_timeout = None self.on_timeout = None
def start_disconnect(self):
self.set_state(DISCONNECTING)
class NonBlockingTCP(NonBlockingTransport, IdleObject): class NonBlockingTCP(NonBlockingTransport, IdleObject):
''' '''
@ -221,8 +217,12 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
# bytes remained from the last send message # bytes remained from the last send message
self.sendbuff = '' self.sendbuff = ''
self.terminator = '</stream:stream>'
def start_disconnect(self):
self.send('</stream:stream>')
NonBlockingTransport.start_disconnect(self)
def connect(self, conn_5tuple, on_connect, on_connect_failure): def connect(self, conn_5tuple, on_connect, on_connect_failure):
''' '''
@ -316,7 +316,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def disconnect(self, do_callback=True): def disconnect(self, do_callback=True):
if self.state == DISCONNECTED: if self.state == DISCONNECTED:
return return
self.set_state(DISCONNECTING) self.set_state(DISCONNECTED)
self.idlequeue.unplug_idle(self.fd) self.idlequeue.unplug_idle(self.fd)
try: try:
self._sock.shutdown(socket.SHUT_RDWR) self._sock.shutdown(socket.SHUT_RDWR)
@ -342,7 +342,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
def set_timeout(self, timeout): def set_timeout(self, timeout):
if self.state in [CONNECTING, CONNECTED] and self.fd != -1: if self.state != DISCONNECTED and self.fd != -1:
NonBlockingTransport.set_timeout(self, timeout) NonBlockingTransport.set_timeout(self, timeout)
else: else:
log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd)) log.warn('set_timeout: TIMEOUT NOT SET: state is %s, fd is %s' % (self.state, self.fd))
@ -394,6 +394,9 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
if not self.sendbuff: if not self.sendbuff:
if not self.sendqueue: if not self.sendqueue:
log.warn('calling send on empty buffer and queue') log.warn('calling send on empty buffer and queue')
self._plug_idle(
writable= ((self.sendqueue!=[]) or (self.sendbuff!='')),
readable=True)
return None return None
self.sendbuff = self.sendqueue.pop(0) self.sendbuff = self.sendqueue.pop(0)
try: try:
@ -402,7 +405,7 @@ class NonBlockingTCP(NonBlockingTransport, IdleObject):
sent_data = self.sendbuff[:send_count] sent_data = self.sendbuff[:send_count]
self.sendbuff = self.sendbuff[send_count:] self.sendbuff = self.sendbuff[send_count:]
self._plug_idle( self._plug_idle(
writable=self.sendqueue or self.sendbuff, writable= ((self.sendqueue!=[]) or (self.sendbuff!='')),
readable=True) readable=True)
self.raise_event(DATA_SENT, sent_data) self.raise_event(DATA_SENT, sent_data)
@ -477,7 +480,8 @@ class NonBlockingHTTP(NonBlockingTCP):
''' '''
def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible, def __init__(self, raise_event, on_disconnect, idlequeue, on_http_request_possible,
http_uri, http_port, http_version='HTTP/1.1', http_persistent=False): http_uri, http_port, on_persistent_fallback, http_version='HTTP/1.1',
http_persistent=False):
NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue) NonBlockingTCP.__init__(self, raise_event, on_disconnect, idlequeue)
@ -493,22 +497,29 @@ class NonBlockingHTTP(NonBlockingTCP):
self.recvbuff = '' self.recvbuff = ''
self.expected_length = 0 self.expected_length = 0
self.pending_requests = 0 self.pending_requests = 0
self.on_persistent_fallback = on_persistent_fallback
self.on_http_request_possible = on_http_request_possible self.on_http_request_possible = on_http_request_possible
self.just_responed = False
self.last_recv_time = 0
def send(self, raw_data, now=False): def send(self, raw_data, now=False):
NonBlockingTCP.send( NonBlockingTCP.send(
self, self,
self.build_http_message(raw_data), self.build_http_message(raw_data),
now) now)
self.pending_requests += 1
def on_remote_disconnect(self): def on_remote_disconnect(self):
log.warn('on_remote_disconnect called, http_persistent = %s' % self.http_persistent)
if self.http_persistent: if self.http_persistent:
self.http_persistent = False self.http_persistent = False
self.on_persistent_fallback()
self.disconnect(do_callback=False) self.disconnect(do_callback=False)
self.connect( self.connect(
conn_5tuple = self.conn_5tuple, conn_5tuple = self.conn_5tuple,
# after connect, the socket will be plugged as writable - pollout will be
# called, and since there are still data in sendbuff, _do_send will be
# called and sendbuff will be flushed
on_connect = lambda: self._plug_idle(writable=True, readable=True), on_connect = lambda: self._plug_idle(writable=True, readable=True),
on_connect_failure = self.disconnect) on_connect_failure = self.disconnect)
@ -534,20 +545,21 @@ class NonBlockingHTTP(NonBlockingTCP):
if self.expected_length > len(self.recvbuff): if self.expected_length > len(self.recvbuff):
# If we haven't received the whole HTTP mess yet, let's end the thread. # If we haven't received the whole HTTP mess yet, let's end the thread.
# It will be finnished from one of following polls (io_watch) on plugged socket. # It will be finnished from one of following polls (io_watch) on plugged socket.
log.info('not enough bytes - %d expected, %d got' % (self.expected_length, len(self.recvbuff))) log.info('not enough bytes in HTTP response - %d expected, %d got' %
(self.expected_length, len(self.recvbuff)))
return return
# all was received, now call the on_receive callback # everything was received
httpbody = self.recvbuff httpbody = self.recvbuff
self.recvbuff='' self.recvbuff=''
self.expected_length=0 self.expected_length=0
self.pending_requests -= 1
assert(self.pending_requests >= 0)
if not self.http_persistent: if not self.http_persistent:
# not-persistent connections disconnect after response # not-persistent connections disconnect after response
self.disconnect(do_callback = False) self.disconnect(do_callback = False)
self.on_receive(httpbody) self.last_recv_time = time.time()
self.on_receive(data=httpbody, socket=self)
self.on_http_request_possible() self.on_http_request_possible()
@ -563,6 +575,9 @@ class NonBlockingHTTP(NonBlockingTCP):
'Host: %s:%s' % (self.http_host, self.http_port), 'Host: %s:%s' % (self.http_host, self.http_port),
'Content-Type: text/xml; charset=utf-8', 'Content-Type: text/xml; charset=utf-8',
'Content-Length: %s' % len(str(httpbody)), 'Content-Length: %s' % len(str(httpbody)),
'Proxy-Connection: keep-alive',
'Pragma: no-cache',
'Accept-Encoding: gzip, deflate',
'\r\n'] '\r\n']
headers = '\r\n'.join(headers) headers = '\r\n'.join(headers)
return('%s%s\r\n' % (headers, httpbody)) return('%s%s\r\n' % (headers, httpbody))
@ -587,6 +602,35 @@ class NonBlockingHTTP(NonBlockingTCP):
headers[row[0][:-1]] = row[1] headers[row[0][:-1]] = row[1]
return (statusline, headers, httpbody) return (statusline, headers, httpbody)
class NonBlockingHTTPBOSH(NonBlockingHTTP):
def set_stanza_build_cb(self, build_cb):
self.build_cb = build_cb
def _do_send(self):
if not self.sendbuff:
stanza = self.build_cb(socket=self)
stanza = self.build_http_message(httpbody=stanza)
if isinstance(stanza, unicode):
stanza = stanza.encode('utf-8')
elif not isinstance(stanza, str):
stanza = ustr(stanza).encode('utf-8')
self.sendbuff = stanza
try:
send_count = self._send(self.sendbuff)
if send_count:
sent_data = self.sendbuff[:send_count]
self.sendbuff = self.sendbuff[send_count:]
self._plug_idle(writable = self.sendbuff != '', readable = True)
self.raise_event(DATA_SENT, sent_data)
except socket.error, e:
log.error('_do_send:', exc_info=True)
traceback.print_exc()
self.disconnect()
class NBProxySocket(NonBlockingTCP): class NBProxySocket(NonBlockingTCP):
''' '''
@ -663,7 +707,6 @@ class NBHTTPProxySocket(NBProxySocket):
self.after_proxy_connect() self.after_proxy_connect()
#self.onreceive(self._on_proxy_auth) #self.onreceive(self._on_proxy_auth)
# FIXME: find out what it this method for
def _on_proxy_auth(self, reply): def _on_proxy_auth(self, reply):
if self.reply.find('\n\n') == -1: if self.reply.find('\n\n') == -1:
if reply is None: if reply is None:

View file

@ -1101,9 +1101,30 @@ class ManageProxiesWindow:
self.proxies_treeview = self.xml.get_widget('proxies_treeview') self.proxies_treeview = self.xml.get_widget('proxies_treeview')
self.proxyname_entry = self.xml.get_widget('proxyname_entry') self.proxyname_entry = self.xml.get_widget('proxyname_entry')
self.proxytype_combobox = self.xml.get_widget('proxytype_combobox') self.proxytype_combobox = self.xml.get_widget('proxytype_combobox')
self.init_list() self.init_list()
self.xml.signal_autoconnect(self) self.xml.signal_autoconnect(self)
self.window.show_all() self.window.show_all()
# hide the BOSH fields by default
self.show_bosh_fields()
def show_bosh_fields(self, show=True):
if show:
self.xml.get_widget('boshuri_entry').show()
self.xml.get_widget('boshport_entry').show()
self.xml.get_widget('boshuri_label').show()
self.xml.get_widget('boshport_label').show()
self.xml.get_widget('boshuseproxy_checkbutton').show()
else:
cb = self.xml.get_widget('boshuseproxy_checkbutton')
cb.hide()
cb.set_active(True)
self.on_boshuseproxy_checkbutton_toggled(cb)
self.xml.get_widget('boshuri_entry').hide()
self.xml.get_widget('boshport_entry').hide()
self.xml.get_widget('boshuri_label').hide()
self.xml.get_widget('boshport_label').hide()
def fill_proxies_treeview(self): def fill_proxies_treeview(self):
model = self.proxies_treeview.get_model() model = self.proxies_treeview.get_model()
@ -1158,9 +1179,18 @@ class ManageProxiesWindow:
def on_useauth_checkbutton_toggled(self, widget): def on_useauth_checkbutton_toggled(self, widget):
act = widget.get_active() act = widget.get_active()
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'useauth', act)
self.xml.get_widget('proxyuser_entry').set_sensitive(act) self.xml.get_widget('proxyuser_entry').set_sensitive(act)
self.xml.get_widget('proxypass_entry').set_sensitive(act) self.xml.get_widget('proxypass_entry').set_sensitive(act)
def on_boshuseproxy_checkbutton_toggled(self, widget):
act = widget.get_active()
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'bosh_useproxy', act)
self.xml.get_widget('proxyhost_entry').set_sensitive(act)
self.xml.get_widget('proxyport_entry').set_sensitive(act)
def on_proxies_treeview_cursor_changed(self, widget): def on_proxies_treeview_cursor_changed(self, widget):
#FIXME: check if off proxy settings are correct (see #FIXME: check if off proxy settings are correct (see
# http://trac.gajim.org/changeset/1921#file2 line 1221 # http://trac.gajim.org/changeset/1921#file2 line 1221
@ -1173,19 +1203,33 @@ class ManageProxiesWindow:
proxyport_entry = self.xml.get_widget('proxyport_entry') proxyport_entry = self.xml.get_widget('proxyport_entry')
proxyuser_entry = self.xml.get_widget('proxyuser_entry') proxyuser_entry = self.xml.get_widget('proxyuser_entry')
proxypass_entry = self.xml.get_widget('proxypass_entry') proxypass_entry = self.xml.get_widget('proxypass_entry')
boshuri_entry = self.xml.get_widget('boshuri_entry')
boshport_entry = self.xml.get_widget('boshport_entry')
useauth_checkbutton = self.xml.get_widget('useauth_checkbutton') useauth_checkbutton = self.xml.get_widget('useauth_checkbutton')
boshuseproxy_checkbutton = self.xml.get_widget('boshuseproxy_checkbutton')
proxyhost_entry.set_text('') proxyhost_entry.set_text('')
proxyport_entry.set_text('') proxyport_entry.set_text('')
proxyuser_entry.set_text('') proxyuser_entry.set_text('')
proxypass_entry.set_text('') proxypass_entry.set_text('')
useauth_checkbutton.set_active(False) boshuri_entry.set_text('')
self.on_useauth_checkbutton_toggled(useauth_checkbutton)
#boshuseproxy_checkbutton.set_active(False)
#self.on_boshuseproxy_checkbutton_toggled(boshuseproxy_checkbutton)
#useauth_checkbutton.set_active(False)
#self.on_useauth_checkbutton_toggled(useauth_checkbutton)
if proxy == _('None'): # special proxy None if proxy == _('None'): # special proxy None
self.show_bosh_fields(False)
self.proxyname_entry.set_editable(False) self.proxyname_entry.set_editable(False)
self.xml.get_widget('remove_proxy_button').set_sensitive(False) self.xml.get_widget('remove_proxy_button').set_sensitive(False)
self.xml.get_widget('proxytype_combobox').set_sensitive(False) self.xml.get_widget('proxytype_combobox').set_sensitive(False)
self.xml.get_widget('proxy_table').set_sensitive(False) self.xml.get_widget('proxy_table').set_sensitive(False)
else: else:
proxytype = gajim.config.get_per('proxies', proxy, 'type')
self.show_bosh_fields(proxytype=='bosh')
self.proxyname_entry.set_editable(True) self.proxyname_entry.set_editable(True)
self.xml.get_widget('remove_proxy_button').set_sensitive(True) self.xml.get_widget('remove_proxy_button').set_sensitive(True)
self.xml.get_widget('proxytype_combobox').set_sensitive(True) self.xml.get_widget('proxytype_combobox').set_sensitive(True)
@ -1198,11 +1242,16 @@ class ManageProxiesWindow:
'user')) 'user'))
proxypass_entry.set_text(gajim.config.get_per('proxies', proxy, proxypass_entry.set_text(gajim.config.get_per('proxies', proxy,
'pass')) 'pass'))
proxytype = gajim.config.get_per('proxies', proxy, 'type') boshuri_entry.set_text(gajim.config.get_per('proxies', proxy,
'bosh_uri'))
boshport_entry.set_text(unicode(gajim.config.get_per('proxies', proxy,
'bosh_port')))
types = ['http', 'socks5', 'bosh'] types = ['http', 'socks5', 'bosh']
self.proxytype_combobox.set_active(types.index(proxytype)) self.proxytype_combobox.set_active(types.index(proxytype))
if gajim.config.get_per('proxies', proxy, 'user'): boshuseproxy_checkbutton.set_active(
useauth_checkbutton.set_active(True) gajim.config.get_per('proxies', proxy, 'bosh_useproxy'))
useauth_checkbutton.set_active(
gajim.config.get_per('proxies', proxy, 'useauth'))
def on_proxies_treeview_key_press_event(self, widget, event): def on_proxies_treeview_key_press_event(self, widget, event):
if event.keyval == gtk.keysyms.Delete: if event.keyval == gtk.keysyms.Delete:
@ -1229,6 +1278,7 @@ class ManageProxiesWindow:
def on_proxytype_combobox_changed(self, widget): def on_proxytype_combobox_changed(self, widget):
types = ['http', 'socks5', 'bosh'] types = ['http', 'socks5', 'bosh']
type_ = self.proxytype_combobox.get_active() type_ = self.proxytype_combobox.get_active()
self.show_bosh_fields(types[type_]=='bosh')
proxy = self.proxyname_entry.get_text().decode('utf-8') proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'type', types[type_]) gajim.config.set_per('proxies', proxy, 'type', types[type_])
@ -1247,6 +1297,16 @@ class ManageProxiesWindow:
proxy = self.proxyname_entry.get_text().decode('utf-8') proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'user', value) gajim.config.set_per('proxies', proxy, 'user', value)
def on_boshuri_entry_changed(self, widget):
value = widget.get_text().decode('utf-8')
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'bosh_uri', value)
def on_boshport_entry_changed(self, widget):
value = widget.get_text().decode('utf-8')
proxy = self.proxyname_entry.get_text().decode('utf-8')
gajim.config.set_per('proxies', proxy, 'bosh_port', value)
def on_proxypass_entry_changed(self, widget): def on_proxypass_entry_changed(self, widget):
value = widget.get_text().decode('utf-8') value = widget.get_text().decode('utf-8')
proxy = self.proxyname_entry.get_text().decode('utf-8') proxy = self.proxyname_entry.get_text().decode('utf-8')

View file

@ -50,7 +50,7 @@ import logging
consoleloghandler = logging.StreamHandler() consoleloghandler = logging.StreamHandler()
consoleloghandler.setLevel(1) consoleloghandler.setLevel(1)
consoleloghandler.setFormatter( consoleloghandler.setFormatter(
logging.Formatter('%(name)s: %(levelname)s: %(message)s') logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s')
) )
log = logging.getLogger('gajim') log = logging.getLogger('gajim')
log.setLevel(logging.WARNING) log.setLevel(logging.WARNING)