Changeset 9867

Show
Ignore:
Timestamp:
06/30/08 02:02:32 (5 months ago)
Author:
tomk
Message:

- Refactored non-blocking transport and client classes - getaddrinfo is called
in Client now
- Added NonBlockingHttpBOSH transport (to tranports_nb) and BOSHClient
(to client_nb)
- Extended possible proxy types in configuration by "BOSH" proxy
- Rewrote NonBlockingTLS to invoke success callback only after successful TLS handshake is over (formerly, the TLS Plugin returned right after sending <starttls>)

Location:
branches/bosh_support
Files:
1 removed
12 modified

Legend:

Unmodified
Added
Removed
  • branches/bosh_support/data/glade/manage_proxies_window.glade

    r9543 r9867  
    212212                              <property name="visible">True</property> 
    213213                              <property name="items" translatable="yes">HTTP Connect 
    214 SOCKS5</property> 
     214SOCKS5 
     215BOSH</property> 
    215216                              <property name="add_tearoffs">False</property> 
    216217                              <property name="focus_on_click">True</property> 
  • branches/bosh_support/src/common/connection.py

    r9776 r9867  
    5656import logging 
    5757log = logging.getLogger('gajim.c.connection') 
     58log.setLevel(logging.DEBUG) 
    5859 
    5960ssl_error = { 
     
    208209        def _disconnectedReconnCB(self): 
    209210                '''Called when we are disconnected''' 
    210                 log.debug('disconnectedReconnCB') 
     211                log.error('disconnectedReconnCB') 
    211212                if gajim.account_is_connected(self.name): 
    212213                        # we cannot change our status to offline or connecting 
     
    468469                else: 
    469470                        proxy = None 
    470  
    471471                h = hostname 
    472472                p = 5222 
     
    505505 
    506506        def on_proxy_failure(self, reason): 
    507                 log.debug('Connection to proxy failed') 
     507                log.error('Connection to proxy failed: %s' % reason) 
    508508                self.time_to_reconnect = None 
    509509                self.on_connect_failure = None 
     
    520520                                self.last_connection = None 
    521521                                self.connection = None 
    522                         if gajim.verbose: 
    523                                 con = common.xmpp.NonBlockingClient(self._hostname, caller = self, 
    524                                         on_connect = self.on_connect_success, 
    525                                         on_proxy_failure = self.on_proxy_failure, 
    526                                         on_connect_failure = self.connect_to_next_type) 
    527                         else: 
    528                                 con = common.xmpp.NonBlockingClient(self._hostname, debug = [], 
    529                                         caller = self, on_connect = self.on_connect_success, 
    530                                         on_proxy_failure = self.on_proxy_failure, 
    531                                         on_connect_failure = self.connect_to_next_type) 
    532                         self.last_connection = con 
    533                         # increase default timeout for server responses 
    534                         common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs 
    535                         con.set_idlequeue(gajim.idlequeue) 
    536                         # FIXME: this is a hack; need a better way 
    537                         if self.on_connect_success == self._on_new_account: 
    538                                 con.RegisterDisconnectHandler(self._on_new_account) 
    539522 
    540523                        if self._current_type == 'ssl': 
     
    547530                                else: 
    548531                                        secur = None 
     532 
     533                        if self._proxy and self._proxy['type'] == 'bosh':  
     534                                clientClass = common.xmpp.BOSHClient 
     535                        else: 
     536                                clientClass = common.xmpp.NonBlockingClient 
     537 
     538                        if gajim.verbose: 
     539                                con = common.xmpp.NonBlockingClient( 
     540                                        hostname=self._current_host['host'], 
     541                                        port=port, 
     542                                        caller=self, 
     543                                        idlequeue=gajim.idlequeue) 
     544                        else: 
     545                                con = common.xmpp.NonBlockingClient( 
     546                                        hostname=self._current_host['host'], 
     547                                        debug=[], 
     548                                        port=port, 
     549                                        caller=self, 
     550                                        idlequeue=gajim.idlequeue) 
     551 
     552                        self.last_connection = con 
     553                        # increase default timeout for server responses 
     554                        common.xmpp.dispatcher_nb.DEFAULT_TIMEOUT_SECONDS = self.try_connecting_for_foo_secs 
     555                        # FIXME: this is a hack; need a better way 
     556                        if self.on_connect_success == self._on_new_account: 
     557                                con.RegisterDisconnectHandler(self._on_new_account) 
     558                         
    549559                        log.info('Connecting to %s: [%s:%d]', self.name, 
    550560                                self._current_host['host'], port) 
    551                         con.connect((self._current_host['host'], port), proxy=self._proxy, 
     561                        con.connect( 
     562                                on_connect=self.on_connect_success, 
     563                                on_proxy_failure=self.on_proxy_failure, 
     564                                on_connect_failure=self.connect_to_next_type, 
     565                                proxy=self._proxy, 
    552566                                secure = secur) 
    553567                else: 
     
    562576                        else: 
    563577                                self._connection_types = ['tls', 'ssl', 'plain'] 
     578                         
     579                        # FIXME: remove after tls and ssl will be degubbed 
     580                        #self._connection_types = ['plain'] 
    564581                        host = self.select_next_host(self._hosts) 
    565582                        self._current_host = host 
     
    976993                                self.remove_all_transfers() 
    977994                                self.time_to_reconnect = None 
    978                                 self.connection.start_disconnect(p, self._on_disconnected) 
     995 
     996                                self.connection.RegisterDisconnectHandler(self._on_disconnected) 
     997                                self.connection.send(p) 
     998                                self.connection.StreamTerminate() 
     999                                #self.connection.start_disconnect(p, self._on_disconnected) 
    9791000                        else: 
    9801001                                self.time_to_reconnect = None 
     
    10111032                ''' called when a disconnect request has completed successfully''' 
    10121033                self.dispatch('STATUS', 'offline') 
    1013                 self.disconnect() 
     1034                self.disconnect(on_purpose=True) 
    10141035 
    10151036        def get_status(self): 
  • branches/bosh_support/src/common/xmpp/auth_nb.py

    r9776 r9867  
    170170                        self.DEBUG('Successfully authenticated with remote server.', 'ok') 
    171171                        handlers=self._owner.Dispatcher.dumpHandlers() 
     172                        print '6' * 79 
     173                        print handlers 
     174                        print '6' * 79 
    172175                        self._owner.Dispatcher.PlugOut() 
    173176                        dispatcher_nb.Dispatcher().PlugIn(self._owner) 
  • branches/bosh_support/src/common/xmpp/client_nb.py

    r9776 r9867  
    1818 
    1919''' 
    20 Provides PlugIn class functionality to develop extentions for xmpppy. 
    21 Also provides Client and Component classes implementations as the 
    22 examples of xmpppy structures usage. 
     20Provides Client classes implementations as examples of xmpppy structures usage. 
    2321These classes can be used for simple applications "AS IS" though. 
    2422''' 
     
    2624import socket 
    2725import debug 
    28  
    29 import transports_nb, dispatcher_nb, auth_nb, roster_nb 
     26import random 
     27 
     28import transports_nb, dispatcher_nb, auth_nb, roster_nb, protocol 
    3029from client import * 
     30 
     31import logging 
     32log = logging.getLogger('gajim.c.x.client_nb') 
     33 
     34consoleloghandler = logging.StreamHandler() 
     35consoleloghandler.setLevel(logging.DEBUG) 
     36consoleloghandler.setFormatter( 
     37        logging.Formatter('%(levelname)s: %(message)s') 
     38) 
     39log.setLevel(logging.DEBUG) 
     40log.addHandler(consoleloghandler) 
     41log.propagate = False 
     42 
    3143 
    3244class NBCommonClient: 
    3345        ''' Base for Client and Component classes.''' 
    34         def __init__(self, server, port=5222, debug=['always', 'nodebuilder'], caller=None,  
    35                 on_connect=None, on_proxy_failure=None, on_connect_failure=None): 
    36                 ''' Caches server name and (optionally) port to connect to. "debug" parameter specifies 
    37                         the debug IDs that will go into debug output. You can either specifiy an "include" 
    38                         or "exclude" list. The latter is done via adding "always" pseudo-ID to the list. 
    39                         Full list: ['nodebuilder', 'dispatcher', 'gen_auth', 'SASL_auth', 'bind', 'socket',  
    40                          'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb'] . ''' 
    41                  
    42                 if isinstance(self, NonBlockingClient):  
    43                         self.Namespace, self.DBG = 'jabber:client', DBG_CLIENT 
    44                 elif isinstance(self, NBCommonClient):  
    45                         self.Namespace, self.DBG = dispatcher_nb.NS_COMPONENT_ACCEPT, DBG_COMPONENT 
    46                  
     46        def __init__(self, hostname, idlequeue, port=5222, debug=['always', 'nodebuilder'], caller=None): 
     47                 
     48                ''' Caches connection data: 
     49                :param hostname: hostname of machine where the XMPP server is running (from Account 
     50                        of from SRV request) and port to connect to. 
     51                :param idlequeue: processing idlequeue 
     52                :param port: port of listening XMPP server 
     53                :param debug: specifies the debug IDs that will go into debug output. You can either 
     54                        specifiy an "include" or "exclude" list. The latter is done via adding "always"  
     55                        pseudo-ID to the list. Full list: ['nodebuilder', 'dispatcher', 'gen_auth',  
     56                        'SASL_auth', 'bind', 'socket', 'CONNECTproxy', 'TLS', 'roster', 'browser', 'ibb']. 
     57                        TODO: get rid of debug.py using 
     58                :param caller: calling object - it has to implement certain methods (necessary?) 
     59                         
     60                ''' 
     61                 
     62                self.DBG = DBG_CLIENT 
     63 
     64                self.Namespace = protocol.NS_CLIENT 
     65 
     66                self.idlequeue = idlequeue 
    4767                self.defaultNamespace = self.Namespace 
    4868                self.disconnect_handlers = [] 
    49                 self.Server = server 
     69 
     70                # XMPP server and port from account or SRV 
     71                self.Server = hostname 
    5072                self.Port = port 
    5173                 
    52                 # Who initiated this client 
    53                 # Used to register the EventDispatcher 
     74                # caller is who initiated this client, it is sed to register the EventDispatcher 
    5475                self._caller = caller 
    5576                if debug and type(debug) != list:  
     
    6384                self.connected = '' 
    6485                self._component=0 
    65                 self.idlequeue = None 
    6686                self.socket = None 
    67                 self.on_connect = on_connect 
    68                 self.on_proxy_failure = on_proxy_failure 
    69                 self.on_connect_failure = on_connect_failure 
    70                  
    71         def set_idlequeue(self, idlequeue): 
    72                 self.idlequeue = idlequeue 
     87                self.on_connect = None 
     88                self.on_proxy_failure = None 
     89                self.on_connect_failure = None 
     90                self.proxy = None 
     91                 
    7392         
    74         def disconnected(self): 
    75                 ''' Called on disconnection. Calls disconnect handlers and cleans things up. ''' 
     93        def on_disconnect(self): 
     94                ''' 
     95                Called on disconnection - when connect failure occurs on running connection 
     96                (after stream is successfully opened). 
     97                Calls disconnect handlers and cleans things up. 
     98                ''' 
     99                 
    76100                self.connected='' 
    77101                self.DEBUG(self.DBG,'Disconnect detected','stop') 
    78102                for i in reversed(self.disconnect_handlers): 
     103                        self.DEBUG(self.DBG, 'Calling disc handler %s' % i, 'stop') 
    79104                        i() 
    80105                if self.__dict__.has_key('NonBlockingRoster'): 
     
    95120                        self.NonBlockingTcp.PlugOut() 
    96121                 
    97         def reconnectAndReauth(self): 
    98                 ''' Just disconnect. We do reconnecting in connection.py ''' 
    99                 self.disconnect() 
    100                 return ''  
    101  
    102         def connect(self,server=None,proxy=None, ssl=None, on_stream_start = None): 
    103                 ''' Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream. ''' 
    104                 if not server:  
    105                         server = (self.Server, self.Port) 
    106                 self._Server,  self._Proxy, self._Ssl = server ,  proxy, ssl 
    107                 self.on_stream_start = on_stream_start 
     122 
     123        def send(self, stanza, is_message = False, now = False): 
     124                ''' interface for putting stanzas on wire. Puts ID to stanza if needed and 
     125                sends it via socket wrapper''' 
     126                (id, stanza_to_send) = self.Dispatcher.assign_id(stanza) 
     127 
     128                if is_message: 
     129                        # somehow zeroconf-specific 
     130                        self.Connection.send(stanza_to_send, True, now = now) 
     131                else: 
     132                        self.Connection.send(stanza_to_send, now = now) 
     133                return id 
     134 
     135 
     136 
     137        def connect(self, on_connect, on_connect_failure, on_proxy_failure=None, proxy=None, secure=None): 
     138                '''  
     139                Open XMPP connection (open streams in both directions). 
     140                :param on_connect: called after stream is successfully opened 
     141                :param on_connect_failure: called when error occures during connection 
     142                :param on_proxy_failure: called if error occurres during TCP connection to 
     143                        proxy server or during connection to the proxy 
     144                :param proxy: dictionary with proxy data. It should contain at least values 
     145                        for keys 'host' and 'port' - connection details for proxy server and 
     146                        optionally keys 'user' and 'pass' as proxy credentials 
     147                :param secure: 
     148                ''' 
     149                 
     150                self.on_connect = on_connect 
     151                self.on_connect_failure=on_connect_failure 
     152                self.on_proxy_failure = on_proxy_failure 
     153                self._secure = secure 
     154                self.Connection = None 
     155 
    108156                if proxy: 
     157                        # with proxies, client connects to proxy instead of directly to 
     158                        # XMPP server from __init__.  
     159                        # tcp_server is hostname used for socket connecting 
     160                        tcp_server=proxy['host']                         
     161                        tcp_port=proxy['port'] 
     162                        self._on_tcp_failure = self.on_proxy_failure 
    109163                        if proxy.has_key('type'): 
     164                                if proxy.has_key('user') and proxy.has_key('pass'): 
     165                                        proxy_creds=(proxy['user'],proxy['pass']) 
     166                                else: 
     167                                        proxy_creds=(None, None) 
     168                                                                                         
    110169                                type_ = proxy['type'] 
    111170                                if type_ == 'socks5': 
    112                                         self.socket = transports_nb.NBSOCKS5PROXYsocket( 
    113                                                 self._on_connected, self._on_proxy_failure, 
    114                                                 self._on_connected_failure, proxy, server) 
     171                                        self.socket = transports_nb.NBSOCKS5ProxySocket( 
     172                                                on_disconnect=self.on_disconnect, 
     173                                                proxy_creds=proxy_creds, 
     174                                                xmpp_server=(self.Server, self.Port)) 
    115175                                elif type_ == 'http': 
    116                                         self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected, 
    117                                                 self._on_proxy_failure, self._on_connected_failure, proxy, 
    118                                                 server) 
     176                                        self.socket = transports_nb.NBHTTPProxySocket( 
     177                                                on_disconnect=self.on_disconnect, 
     178                                                proxy_creds=proxy_creds, 
     179                                                xmpp_server=(self.Server, self.Port)) 
     180                                elif type_ == 'bosh': 
     181                                        tcp_server = transports_nb.urisplit(tcp_server)[1] 
     182                                        self.socket = transports_nb.NonBlockingHttpBOSH( 
     183                                                on_disconnect=self.on_disconnect, 
     184                                                bosh_uri = proxy['host'], 
     185                                                bosh_port = tcp_port) 
    119186                        else: 
    120                                 self.socket = transports_nb.NBHTTPPROXYsocket(self._on_connected, 
    121                                         self._on_proxy_failure, self._on_connected_failure, proxy, 
    122                                         server) 
     187                                        self.socket = transports_nb.NBHTTPProxySocket( 
     188                                                on_disconnect=self.on_disconnect, 
     189                                                proxy_creds=(None, None), 
     190                                                xmpp_server=(self.Server, self.Port)) 
    123191                else:  
    124                         self.connected = 'tcp' 
    125                         self.socket = transports_nb.NonBlockingTcp(self._on_connected,  
    126                                 self._on_connected_failure, server) 
     192                        self._on_tcp_failure = self._on_connect_failure 
     193                        tcp_server=self.Server 
     194                        tcp_port=self.Port 
     195                        self.socket = transports_nb.NonBlockingTcp(on_disconnect = self.on_disconnect) 
     196 
    127197                self.socket.PlugIn(self) 
    128                 return True 
     198 
     199                self._resolve_hostname( 
     200                        hostname=tcp_server, 
     201                        port=tcp_port, 
     202                        on_success=self._try_next_ip, 
     203                        on_failure=self._on_tcp_failure) 
     204                         
     205                         
     206 
     207        def _resolve_hostname(self, hostname, port, on_success, on_failure): 
     208                ''' wrapper of getaddinfo call. FIXME: getaddinfo blocks''' 
     209                try: 
     210                        self.ip_addresses = socket.getaddrinfo(hostname,port, 
     211                                socket.AF_UNSPEC,socket.SOCK_STREAM) 
     212                except socket.gaierror, (errnum, errstr): 
     213                        on_failure(err_message='Lookup failure for %s:%s - %s %s' %  
     214                                 (self.Server, self.Port, errnum, errstr)) 
     215                else: 
     216                        on_success() 
     217                 
     218                 
    129219         
    130         def get_attrs(self, on_stream_start): 
    131                 self.on_stream_start = on_stream_start 
    132                 self.onreceive(self._on_receive_document_attrs) 
    133  
    134         def _on_proxy_failure(self, reason):  
    135                 if self.on_proxy_failure: 
    136                         self.on_proxy_failure(reason) 
    137  
    138         def _on_connected_failure(self, retry = None):  
     220        def _try_next_ip(self, err_message=None): 
     221                '''iterates over IP addresses from getaddinfo''' 
     222                if err_message: 
     223                        self.DEBUG(self.DBG,err_message,'connect') 
     224                if self.ip_addresses == []: 
     225                        self._on_tcp_failure(err_message='Run out of hosts for name %s:%s' %  
     226                                (self.Server, self.Port)) 
     227                else: 
     228                        self.current_ip = self.ip_addresses.pop(0) 
     229                        self.socket.connect( 
     230                                conn_5tuple=self.current_ip, 
     231                                on_connect=lambda: self._xmpp_connect(socket_type='tcp'), 
     232                                on_connect_failure=self._try_next_ip) 
     233 
     234 
     235        def incoming_stream_version(self): 
     236                ''' gets version of xml stream''' 
     237                if self.Dispatcher.Stream._document_attrs.has_key('version'): 
     238                        return self.Dispatcher.Stream._document_attrs['version'] 
     239                else: 
     240                        return None 
     241 
     242        def _xmpp_connect(self, socket_type): 
     243                self.connected = socket_type 
     244                self._xmpp_connect_machine() 
     245 
     246 
     247        def _xmpp_connect_machine(self, mode=None, data=None): 
     248                ''' 
     249                Finite automaton called after TCP connecting. Takes care of stream opening 
     250                and features tag handling. Calls _on_stream_start when stream is  
     251                started, and _on_connect_failure on failure. 
     252                ''' 
     253                #FIXME: use RegisterHandlerOnce instead of onreceive 
     254                log.info('=============xmpp_connect_machine() >> mode: %s, data: %s' % (mode,data)) 
     255 
     256                def on_next_receive(mode): 
     257                        if mode is None: 
     258                                self.onreceive(None) 
     259                        else: 
     260                                self.onreceive(lambda data:self._xmpp_connect_machine(mode, data)) 
     261 
     262                if not mode: 
     263                        dispatcher_nb.Dispatcher().PlugIn(self) 
     264                        on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') 
     265 
     266                elif mode == 'FAILURE': 
     267                        self._on_connect_failure(err_message='During XMPP connect: %s' % data) 
     268 
     269                elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': 
     270                        if data: 
     271                                self.Dispatcher.ProcessNonBlocking(data) 
     272                        if not hasattr(self, 'Dispatcher') or \ 
     273                                self.Dispatcher.Stream._document_attrs is None: 
     274                                self._xmpp_connect_machine( 
     275                                        mode='FAILURE', 
     276                                        data='Error on stream open') 
     277                        if self.incoming_stream_version() == '1.0': 
     278                                if not self.Dispatcher.Stream.features:  
     279                                        on_next_receive('RECEIVE_STREAM_FEATURES') 
     280                                else: 
     281                                        self._xmpp_connect_machine(mode='STREAM_STARTED') 
     282 
     283                        else: 
     284                                self._xmpp_connect_machine(mode='STREAM_STARTED') 
     285 
     286                elif mode == 'RECEIVE_STREAM_FEATURES': 
     287                        if data: 
     288                                # sometimes <features> are received together with document 
     289                                # attributes and sometimes on next receive... 
     290                                self.Dispatcher.ProcessNonBlocking(data) 
     291                        if not self.Dispatcher.Stream.features:  
     292                                self._xmpp_connect_machine( 
     293                                        mode='FAILURE', 
     294                                        data='Missing <features> in 1.0 stream') 
     295                        else: 
     296                                self._xmpp_connect_machine(mode='STREAM_STARTED') 
     297 
     298                elif mode == 'STREAM_STARTED': 
     299                        self._on_stream_start() 
     300 
     301        def _on_stream_start(self): 
     302                '''Called when stream is opened. To be overriden in derived classes.''' 
     303 
     304        def _on_connect_failure(self, retry=None, err_message=None):  
     305                self.connected = None 
     306                if err_message: 
     307                        self.DEBUG(self.DBG, err_message, 'connecting') 
    139308                if self.socket: 
    140309                        self.socket.disconnect() 
    141                 if self.on_connect_failure: 
    142                         self.on_connect_failure(retry) 
    143  
    144         def _on_connected(self): 
    145                 # FIXME: why was this needed? Please note that we're working 
    146                 # in nonblocking mode, and this handler is actually called 
    147                 # as soon as connection is initiated, NOT when connection 
    148                 # succeeds, as the name suggests. 
    149                 # # connect succeeded, so no need of this callback anymore  
    150                 # self.on_connect_failure = None 
    151                 self.connected = 'tcp' 
    152                 if self._Ssl: 
    153                         transports_nb.NonBlockingTLS().PlugIn(self, now=1) 
    154                         if not self.Connection: # ssl error, stream is closed 
    155                                 return 
    156                         self.connected = 'ssl' 
    157                 self.onreceive(self._on_receive_document_attrs) 
    158                 dispatcher_nb.Dispatcher().PlugIn(self) 
    159                  
    160         def _on_receive_document_attrs(self, data): 
    161                 if data: 
    162                         self.Dispatcher.ProcessNonBlocking(data) 
    163                 if not hasattr(self, 'Dispatcher') or \ 
    164                         self.Dispatcher.Stream._document_attrs is None: 
    165                         return 
     310                self.on_connect_failure(retry) 
     311 
     312        def _on_connect(self): 
    166313                self.onreceive(None) 
    167                 if self.Dispatcher.Stream._document_attrs.has_key('version') and \ 
    168                         self.Dispatcher.Stream._document_attrs['version'] == '1.0': 
    169                                 self.onreceive(self._on_receive_stream_features) 
    170                                 return 
    171                 if self.on_stream_start: 
    172                         self.on_stream_start() 
    173                         self.on_stream_start = None 
    174                 return True 
    175          
    176         def _on_receive_stream_features(self, data): 
    177