~ubuntu-branches/ubuntu/karmic/sugar-toolkit/karmic

« back to all changes in this revision

Viewing changes to src/sugar/network.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-08-07 19:43:09 UTC
  • mfrom: (0.1.2 lenny) (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080807194309-03302c4lj0j0ipze
Tags: 0.82.0-1
* New upstream release.
* Unfuzz patch 2991.
* Add DEB_MAINTAINER_MODE in debian/rules (thanks to Romain Beauxis).
* Update copyright-hints.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2007 Red Hat, Inc.
 
2
#
 
3
# This library is free software; you can redistribute it and/or
 
4
# modify it under the terms of the GNU Lesser General Public
 
5
# License as published by the Free Software Foundation; either
 
6
# version 2 of the License, or (at your option) any later version.
 
7
#
 
8
# This library is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
11
# Lesser General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU Lesser General Public
 
14
# License along with this library; if not, write to the
 
15
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
16
# Boston, MA 02111-1307, USA.
 
17
 
 
18
import socket
 
19
import os
 
20
import threading
 
21
import traceback
 
22
import xmlrpclib
 
23
import sys
 
24
import httplib
 
25
import urllib
 
26
import fcntl
 
27
import tempfile
 
28
 
 
29
import gobject
 
30
import SimpleXMLRPCServer
 
31
import SimpleHTTPServer
 
32
import SocketServer
 
33
 
 
34
 
 
35
__authinfos = {}
 
36
 
 
37
def _add_authinfo(authinfo):
 
38
    __authinfos[threading.currentThread()] = authinfo
 
39
 
 
40
def get_authinfo():
 
41
    return __authinfos.get(threading.currentThread())
 
42
 
 
43
def _del_authinfo():
 
44
    del __authinfos[threading.currentThread()]
 
45
 
 
46
 
 
47
class GlibTCPServer(SocketServer.TCPServer):
 
48
    """GlibTCPServer
 
49
 
 
50
    Integrate socket accept into glib mainloop.
 
51
    """
 
52
 
 
53
    allow_reuse_address = True
 
54
    request_queue_size = 20
 
55
 
 
56
    def __init__(self, server_address, RequestHandlerClass):
 
57
        SocketServer.TCPServer.__init__(self, server_address,
 
58
                                        RequestHandlerClass)
 
59
        self.socket.setblocking(0)  # Set nonblocking
 
60
 
 
61
        # Watch the listener socket for data
 
62
        gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
 
63
 
 
64
    def _handle_accept(self, source, condition):
 
65
        """Process incoming data on the server's socket by doing an accept()
 
66
        via handle_request()."""
 
67
        if not (condition & gobject.IO_IN):
 
68
            return True
 
69
        self.handle_request()
 
70
        return True
 
71
 
 
72
    def close_request(self, request):
 
73
        """Called to clean up an individual request."""
 
74
        # let the request be closed by the request handler when its done
 
75
        pass
 
76
 
 
77
 
 
78
class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
 
79
    """RequestHandler class that integrates with Glib mainloop.  It writes
 
80
       the specified file to the client in chunks, returning control to the
 
81
       mainloop between chunks.
 
82
    """
 
83
 
 
84
    CHUNK_SIZE = 4096
 
85
 
 
86
    def __init__(self, request, client_address, server):
 
87
        self._file = None
 
88
        self._srcid = 0
 
89
        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
 
90
                                        self, request, client_address, server)
 
91
 
 
92
    def log_request(self, code='-', size='-'):
 
93
        pass
 
94
 
 
95
    def do_GET(self):
 
96
        """Serve a GET request."""
 
97
        self._file = self.send_head()
 
98
        if self._file:
 
99
            self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT |
 
100
                                               gobject.IO_ERR,
 
101
                                               self._send_next_chunk)
 
102
        else:
 
103
            self._file.close()
 
104
            self._cleanup()
 
105
 
 
106
    def _send_next_chunk(self, source, condition):
 
107
        if condition & gobject.IO_ERR:
 
108
            self._cleanup()
 
109
            return False
 
110
        if not (condition & gobject.IO_OUT):
 
111
            self._cleanup()
 
112
            return False
 
113
        data = self._file.read(self.CHUNK_SIZE)
 
114
        count = os.write(self.wfile.fileno(), data)
 
115
        if count != len(data) or len(data) != self.CHUNK_SIZE:
 
116
            self._cleanup()
 
117
            return False
 
118
        return True
 
119
 
 
120
    def _cleanup(self):
 
121
        if self._file:
 
122
            self._file.close()
 
123
            self._file = None
 
124
        if self._srcid > 0:
 
125
            gobject.source_remove(self._srcid)
 
126
            self._srcid = 0
 
127
        if not self.wfile.closed:
 
128
            self.wfile.flush()
 
129
        self.wfile.close()
 
130
        self.rfile.close()
 
131
        
 
132
    def finish(self):
 
133
        """Close the sockets when we're done, not before"""
 
134
        pass
 
135
 
 
136
    def send_head(self):
 
137
        """Common code for GET and HEAD commands.
 
138
 
 
139
        This sends the response code and MIME headers.
 
140
 
 
141
        Return value is either a file object (which has to be copied
 
142
        to the outputfile by the caller unless the command was HEAD,
 
143
        and must be closed by the caller under all circumstances), or
 
144
        None, in which case the caller has nothing further to do.
 
145
 
 
146
        ** [dcbw] modified to send Content-disposition filename too
 
147
        """
 
148
        path = self.translate_path(self.path)
 
149
        if not path or not os.path.exists(path):
 
150
            self.send_error(404, "File not found")
 
151
            return None
 
152
 
 
153
        f = None
 
154
        if os.path.isdir(path):
 
155
            for index in "index.html", "index.htm":
 
156
                index = os.path.join(path, index)
 
157
                if os.path.exists(index):
 
158
                    path = index
 
159
                    break
 
160
            else:
 
161
                return self.list_directory(path)
 
162
        ctype = self.guess_type(path)
 
163
        try:
 
164
            # Always read in binary mode. Opening files in text mode may cause
 
165
            # newline translations, making the actual size of the content
 
166
            # transmitted *less* than the content-length!
 
167
            f = open(path, 'rb')
 
168
        except IOError:
 
169
            self.send_error(404, "File not found")
 
170
            return None
 
171
        self.send_response(200)
 
172
        self.send_header("Content-type", ctype)
 
173
        self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
 
174
        self.send_header("Content-Disposition", 'attachment; filename="%s"' %
 
175
                         os.path.basename(path))
 
176
        self.end_headers()
 
177
        return f
 
178
 
 
179
class GlibURLDownloader(gobject.GObject):
 
180
    """Grabs a URL in chunks, returning to the mainloop after each chunk"""
 
181
 
 
182
    __gsignals__ = {
 
183
        'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
 
184
                         ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
 
185
        'error':    (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
 
186
                         ([gobject.TYPE_PYOBJECT])),
 
187
        'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
 
188
                         ([gobject.TYPE_PYOBJECT]))
 
189
    }
 
190
 
 
191
    CHUNK_SIZE = 4096
 
192
 
 
193
    def __init__(self, url, destdir=None):
 
194
        self._url = url
 
195
        if not destdir:
 
196
            destdir = tempfile.gettempdir()
 
197
        self._destdir = destdir
 
198
        self._srcid = 0
 
199
        self._fname = None
 
200
        self._outf = None
 
201
        self._suggested_fname = None
 
202
        self._info = None
 
203
        self._written = 0
 
204
        gobject.GObject.__init__(self)
 
205
 
 
206
    def start(self, destfile=None, destfd=None):
 
207
        self._info = urllib.urlopen(self._url)
 
208
        self._outf = None
 
209
        self._fname = None
 
210
        if destfd and not destfile:
 
211
            raise ValueError("Must provide destination file too when" \
 
212
                             "specifying file descriptor")
 
213
        if destfile:
 
214
            self._suggested_fname = os.path.basename(destfile)
 
215
            self._fname = os.path.abspath(os.path.expanduser(destfile))
 
216
            if destfd:
 
217
                # Use the user-supplied destination file descriptor
 
218
                self._outf = destfd
 
219
            else:
 
220
                self._outf = os.open(self._fname, os.O_RDWR |
 
221
                                     os.O_TRUNC | os.O_CREAT, 0644)
 
222
        else:
 
223
            fname = self._get_filename_from_headers(self._info.headers)
 
224
            self._suggested_fname = fname
 
225
            garbage_, path = urllib.splittype(self._url)
 
226
            garbage_, path = urllib.splithost(path or "")
 
227
            path, garbage_ = urllib.splitquery(path or "")
 
228
            path, garbage_ = urllib.splitattr(path or "")
 
229
            suffix = os.path.splitext(path)[1]
 
230
            (self._outf, self._fname) = tempfile.mkstemp(suffix=suffix,
 
231
                                                         dir=self._destdir)
 
232
 
 
233
        fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
 
234
        self._srcid = gobject.io_add_watch(self._info.fp.fileno(),
 
235
                                           gobject.IO_IN | gobject.IO_ERR,
 
236
                                           self._read_next_chunk)
 
237
 
 
238
    def cancel(self):
 
239
        if self._srcid == 0:
 
240
            raise RuntimeError("Download already canceled or stopped")
 
241
        self.cleanup(remove=True)
 
242
 
 
243
    def _get_filename_from_headers(self, headers):
 
244
        if not headers.has_key("Content-Disposition"):
 
245
            return None
 
246
 
 
247
        ftag = "filename="
 
248
        data = headers["Content-Disposition"]
 
249
        fidx = data.find(ftag)
 
250
        if fidx < 0:
 
251
            return None
 
252
        fname = data[fidx+len(ftag):]
 
253
        if fname[0] == '"' or fname[0] == "'":
 
254
            fname = fname[1:]
 
255
        if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
 
256
            fname = fname[:len(fname)-1]
 
257
        return fname
 
258
 
 
259
    def _read_next_chunk(self, source, condition):
 
260
        if condition & gobject.IO_ERR:
 
261
            self.cleanup(remove=True)
 
262
            self.emit("error", "Error downloading file.")
 
263
            return False
 
264
        elif not (condition & gobject.IO_IN):
 
265
            # shouldn't get here, but...
 
266
            return True
 
267
 
 
268
        try:
 
269
            data = self._info.fp.read(self.CHUNK_SIZE)
 
270
            count = os.write(self._outf, data)
 
271
            self._written += len(data)
 
272
 
 
273
            # error writing data to file?
 
274
            if count < len(data):
 
275
                self.cleanup(remove=True)
 
276
                self.emit("error", "Error writing to download file.")
 
277
                return False
 
278
 
 
279
            self.emit("progress", self._written)
 
280
 
 
281
            # done?
 
282
            if len(data) < self.CHUNK_SIZE:
 
283
                self.cleanup()
 
284
                self.emit("finished", self._fname, self._suggested_fname)
 
285
                return False
 
286
        except Exception, err:
 
287
            self.cleanup(remove=True)
 
288
            self.emit("error", "Error downloading file: %s" % err)
 
289
            return False
 
290
        return True
 
291
 
 
292
    def cleanup(self, remove=False):
 
293
        if self._srcid > 0:
 
294
            gobject.source_remove(self._srcid)
 
295
            self._srcid = 0
 
296
        del self._info
 
297
        self._info = None
 
298
        os.close(self._outf)
 
299
        if remove:
 
300
            os.remove(self._fname)
 
301
        self._outf = None
 
302
 
 
303
 
 
304
class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
 
305
    """ GlibXMLRPCRequestHandler
 
306
    
 
307
    The stock SimpleXMLRPCRequestHandler and server don't allow any way to pass
 
308
    the client's address and/or SSL certificate into the function that actually
 
309
    _processes_ the request.  So we have to store it in a thread-indexed dict.
 
310
    """
 
311
 
 
312
    def do_POST(self):
 
313
        _add_authinfo(self.client_address)
 
314
        try:
 
315
            SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
 
316
        except socket.timeout:
 
317
            pass
 
318
        except socket.error, e:
 
319
            print "Error (%s): socket error - '%s'" % (self.client_address, e)
 
320
        except:
 
321
            print "Error while processing POST:"
 
322
            traceback.print_exc()
 
323
        _del_authinfo()
 
324
 
 
325
class GlibXMLRPCServer(GlibTCPServer,
 
326
                       SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
 
327
    """GlibXMLRPCServer
 
328
    
 
329
    Use nonblocking sockets and handle the accept via glib rather than
 
330
    blocking on accept().
 
331
    """
 
332
 
 
333
    def __init__(self, addr, requestHandler=GlibXMLRPCRequestHandler,
 
334
                 logRequests=0, allow_none=False):
 
335
        self.logRequests = logRequests
 
336
        if sys.version_info[:3] >= (2, 5, 0):
 
337
            SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(
 
338
                                            self, allow_none, encoding="utf-8")
 
339
        else:
 
340
            SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
 
341
        GlibTCPServer.__init__(self, addr, requestHandler)
 
342
 
 
343
    def _marshaled_dispatch(self, data, dispatch_method = None):
 
344
        """Dispatches an XML-RPC method from marshalled (XML) data.
 
345
 
 
346
        XML-RPC methods are dispatched from the marshalled (XML) data
 
347
        using the _dispatch method and the result is returned as
 
348
        marshalled data. For backwards compatibility, a dispatch
 
349
        function can be provided as an argument (see comment in
 
350
        SimpleXMLRPCRequestHandler.do_POST) but overriding the
 
351
        existing method through subclassing is the prefered means
 
352
        of changing method dispatch behavior.
 
353
        """
 
354
 
 
355
        params, method = xmlrpclib.loads(data)
 
356
 
 
357
        # generate response
 
358
        try:
 
359
            if dispatch_method is not None:
 
360
                response = dispatch_method(method, params)
 
361
            else:
 
362
                response = self._dispatch(method, params)
 
363
            # wrap response in a singleton tuple
 
364
            response = (response,)
 
365
            response = xmlrpclib.dumps(response, methodresponse=1)
 
366
        except xmlrpclib.Fault, fault:
 
367
            response = xmlrpclib.dumps(fault)
 
368
        except:
 
369
            print "Exception while processing request:"
 
370
            traceback.print_exc()
 
371
 
 
372
            # report exception back to server
 
373
            response = xmlrpclib.dumps(
 
374
                xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
 
375
                )
 
376
 
 
377
        return response
 
378
 
 
379
 
 
380
class GlibHTTP(httplib.HTTP):
 
381
    """Subclass HTTP so we can return it's connection class' socket."""
 
382
    def connect(self, host=None, port=None):
 
383
        httplib.HTTP.connect(self, host, port)
 
384
        self._conn.sock.setblocking(0)
 
385
 
 
386
class GlibXMLRPCTransport(xmlrpclib.Transport):
 
387
    """Integrate the request with the glib mainloop rather than blocking."""
 
388
    ##
 
389
    # Connect to server.
 
390
    #
 
391
    # @param host Target host.
 
392
    # @return A connection handle.
 
393
 
 
394
    def __init__(self, use_datetime=0):
 
395
        self.verbose = None
 
396
        if sys.version_info[:3] >= (2, 5, 0):
 
397
            xmlrpclib.Transport.__init__(self, use_datetime)
 
398
 
 
399
    def make_connection(self, host):
 
400
        """Use our own connection object so we can get its socket."""
 
401
        # create a HTTP connection object from a host descriptor
 
402
        host, extra_headers_, x509_ = self.get_host_info(host)
 
403
        return GlibHTTP(host)
 
404
 
 
405
    ##
 
406
    # Send a complete request, and parse the response.
 
407
    #
 
408
    # @param host Target host.
 
409
    # @param handler Target PRC handler.
 
410
    # @param request_body XML-RPC request body.
 
411
    # @param verbose Debugging flag.
 
412
    # @return Parsed response.
 
413
 
 
414
    def start_request(self, host, handler, request_body, verbose=0,
 
415
                      reply_handler=None, error_handler=None, user_data=None):
 
416
        """Do the first half of the request by sending data to the remote
 
417
        server.  The bottom half bits get run when the remote server's response
 
418
        actually comes back."""
 
419
        # issue XML-RPC request
 
420
 
 
421
        h = self.make_connection(host)
 
422
        if verbose:
 
423
            h.set_debuglevel(1)
 
424
 
 
425
        self.send_request(h, handler, request_body)
 
426
        self.send_host(h, host)
 
427
        self.send_user_agent(h)
 
428
        self.send_content(h, request_body)
 
429
 
 
430
        # Schedule a GIOWatch so we don't block waiting for the response
 
431
        gobject.io_add_watch(h._conn.sock, gobject.IO_IN, self._finish_request,
 
432
             h, host, handler, verbose, reply_handler, error_handler, user_data)
 
433
 
 
434
    def _finish_request(self, source, condition, h, host, handler, verbose,
 
435
                        reply_handler=None, error_handler=None, user_data=None):
 
436
        """Parse and return response when the
 
437
           remote server actually returns it."""
 
438
        if not (condition & gobject.IO_IN):
 
439
            return True
 
440
 
 
441
        try:
 
442
            errcode, errmsg, headers = h.getreply()
 
443
        except socket.error, err:
 
444
            if err[0] != 104:
 
445
                raise socket.error(err)
 
446
            else:
 
447
                if error_handler:
 
448
                    gobject.idle_add(error_handler, err, user_data)
 
449
                return False
 
450
                
 
451
        if errcode != 200:
 
452
            raise xmlrpclib.ProtocolError(host + handler, errcode,
 
453
                                          errmsg, headers)
 
454
        self.verbose = verbose        
 
455
        response = self._parse_response(h.getfile(), h._conn.sock)
 
456
        if reply_handler:
 
457
            # Coerce to a list so we can append user data
 
458
            response = response[0]
 
459
            if not isinstance(response, list):
 
460
                response = [response]
 
461
            response.append(user_data)
 
462
            gobject.idle_add(reply_handler, *response)
 
463
        return False
 
464
 
 
465
class _Method:
 
466
    """Right, so python people thought it would be funny to make this
 
467
    class private to xmlrpclib.py..."""
 
468
    # some magic to bind an XML-RPC method to an RPC server.
 
469
    # supports "nested" methods (e.g. examples.getStateName)
 
470
    def __init__(self, send, name):
 
471
        self.__send = send
 
472
        self.__name = name
 
473
    def __getattr__(self, name):
 
474
        return _Method(self.__send, "%s.%s" % (self.__name, name))
 
475
    def __call__(self, *args, **kwargs):
 
476
        return self.__send(self.__name, *args, **kwargs)
 
477
 
 
478
 
 
479
class GlibServerProxy(xmlrpclib.ServerProxy):
 
480
    """Subclass xmlrpclib.ServerProxy so we can run the XML-RPC request
 
481
    in two parts, integrated with the glib mainloop, such that we don't
 
482
    block anywhere.
 
483
    
 
484
    Using this object is somewhat special; it requires more arguments to each
 
485
    XML-RPC request call than the normal xmlrpclib.ServerProxy object:
 
486
    
 
487
    client = GlibServerProxy("http://127.0.0.1:8888")
 
488
    user_data = "bar"
 
489
    xmlrpc_arg1 = "test"
 
490
    xmlrpc_arg2 = "foo"
 
491
    client.test(xmlrpc_test_cb, user_data, xmlrpc_arg1, xmlrpc_arg2)
 
492
 
 
493
    Here, 'xmlrpc_test_cb' is the callback function, which has the following
 
494
    signature:
 
495
    
 
496
    def xmlrpc_test_cb(result_status, response, user_data=None):
 
497
        ...
 
498
    """
 
499
    def __init__(self, uri, encoding=None, verbose=0, allow_none=0):
 
500
        self._transport = GlibXMLRPCTransport()
 
501
        self._encoding = encoding
 
502
        self._verbose = verbose
 
503
        self._allow_none = allow_none
 
504
        xmlrpclib.ServerProxy.__init__(self, uri, self._transport,
 
505
                                       encoding, verbose, allow_none)
 
506
 
 
507
        # get the url
 
508
        urltype, uri = urllib.splittype(uri)
 
509
        if urltype not in ("http", "https"):
 
510
            raise IOError, "unsupported XML-RPC protocol"
 
511
        self._host, self._handler = urllib.splithost(uri)
 
512
        if not self._handler:
 
513
            self._handler = "/RPC2"
 
514
 
 
515
    def __request(self, methodname, *args, **kwargs):
 
516
        """Call the method on the remote server.  We just start the request here
 
517
        and the transport itself takes care of scheduling the response callback
 
518
        when the remote server returns the response.
 
519
        We don't want to block anywhere."""
 
520
 
 
521
        request = xmlrpclib.dumps(args, methodname, encoding=self._encoding,
 
522
                        allow_none=self._allow_none)
 
523
 
 
524
        reply_hdl = kwargs.get("reply_handler")
 
525
        err_hdl = kwargs.get("error_handler")
 
526
        udata = kwargs.get("user_data")
 
527
        try:
 
528
            response = self._transport.start_request(
 
529
                self._host,
 
530
                self._handler,
 
531
                request,
 
532
                verbose=self._verbose,
 
533
                reply_handler=reply_hdl,
 
534
                error_handler=err_hdl,
 
535
                user_data=udata
 
536
                )
 
537
        except socket.error, exc:
 
538
            if err_hdl:
 
539
                gobject.idle_add(err_hdl, exc, udata)
 
540
 
 
541
    def __getattr__(self, name):
 
542
        # magic method dispatcher
 
543
        return _Method(self.__request, name)
 
544
 
 
545
 
 
546
class Test(object):
 
547
    def test(self, arg1, arg2):
 
548
        print "Request got %s, %s" % (arg1, arg2)
 
549
        return "success", "bork"
 
550
 
 
551
def xmlrpc_success_cb(response, resp2, loop):
 
552
    print "Response was %s %s" % (response, resp2)
 
553
    loop.quit()
 
554
 
 
555
def xmlrpc_error_cb(err, loop):
 
556
    print "Error: %s" % err
 
557
    loop.quit()
 
558
 
 
559
def xmlrpc_test(loop):
 
560
    client = GlibServerProxy("http://127.0.0.1:8888")
 
561
    client.test("bar", "baz",
 
562
                reply_handler=xmlrpc_success_cb,
 
563
                error_handler=xmlrpc_error_cb,
 
564
                user_data=loop)
 
565
 
 
566
def start_xmlrpc():
 
567
    server = GlibXMLRPCServer(("", 8888))
 
568
    inst = Test()
 
569
    server.register_instance(inst)
 
570
    gobject.idle_add(xmlrpc_test, loop)
 
571
 
 
572
class TestReqHandler(ChunkedGlibHTTPRequestHandler):
 
573
    def translate_path(self, path):
 
574
        return "/tmp/foo"
 
575
 
 
576
def start_http():
 
577
    server = GlibTCPServer(("", 8890), TestReqHandler)
 
578
 
 
579
def main():
 
580
    loop = gobject.MainLoop()
 
581
#    start_xmlrpc()
 
582
    start_http()
 
583
    try:
 
584
        loop.run()
 
585
    except KeyboardInterrupt:
 
586
        print 'Ctrl+C pressed, exiting...'
 
587
    print "Done."
 
588
 
 
589
if __name__ == "__main__":
 
590
    main()
 
591
 
 
592