~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

  • Committer: Aaron Bentley
  • Date: 2006-02-22 14:39:42 UTC
  • mto: (2027.1.2 revert-subpath-56549)
  • mto: This revision was merged to the branch mainline in revision 1570.
  • Revision ID: abentley@panoramicfeedback.com-20060222143942-ae72299f2de66767
Fixed build_tree with symlinks

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
"""Implementation of Transport over http.
17
17
"""
18
18
 
19
 
from bzrlib.transport import Transport, register_transport
20
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
21
 
                           TransportError, ConnectionError)
22
19
import os, errno
23
20
from cStringIO import StringIO
24
21
import urllib, urllib2
25
22
import urlparse
 
23
from warnings import warn
26
24
 
 
25
import bzrlib
 
26
from bzrlib.transport import Transport, Server
 
27
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
 
28
                           TransportError, ConnectionError)
27
29
from bzrlib.errors import BzrError, BzrCheckError
28
30
from bzrlib.branch import Branch
29
31
from bzrlib.trace import mutter
 
32
from bzrlib.ui import ui_factory
30
33
 
31
34
 
32
35
def extract_auth(url, password_manager):
35
38
    password manager.  Return the url, minus those auth parameters (which
36
39
    confuse urllib2).
37
40
    """
38
 
    assert url.startswith('http://') or url.startswith('https://')
39
 
    scheme, host = url.split('//', 1)
40
 
    if '/' in host:
41
 
        host, path = host.split('/', 1)
42
 
        path = '/' + path
43
 
    else:
44
 
        path = ''
45
 
    port = ''
46
 
    if '@' in host:
47
 
        auth, host = host.split('@', 1)
 
41
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
 
42
    assert (scheme == 'http') or (scheme == 'https')
 
43
    
 
44
    if '@' in netloc:
 
45
        auth, netloc = netloc.split('@', 1)
48
46
        if ':' in auth:
49
47
            username, password = auth.split(':', 1)
50
48
        else:
51
49
            username, password = auth, None
52
 
        if ':' in host:
53
 
            host, port = host.split(':', 1)
54
 
            port = ':' + port
55
 
        # FIXME: if password isn't given, should we ask for it?
 
50
        if ':' in netloc:
 
51
            host = netloc.split(':', 1)[0]
 
52
        else:
 
53
            host = netloc
 
54
        username = urllib.unquote(username)
56
55
        if password is not None:
57
 
            username = urllib.unquote(username)
58
56
            password = urllib.unquote(password)
59
 
            password_manager.add_password(None, host, username, password)
60
 
    url = scheme + '//' + host + port + path
 
57
        else:
 
58
            password = ui_factory.get_password(prompt='HTTP %(user)@%(host) password',
 
59
                                               user=username, host=host)
 
60
        password_manager.add_password(None, host, username, password)
 
61
    url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
61
62
    return url
 
63
 
62
64
    
63
65
def get_url(url):
64
66
    import urllib2
65
 
    mutter("get_url %s" % url)
 
67
    mutter("get_url %s", url)
66
68
    manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
67
69
    url = extract_auth(url, manager)
68
70
    auth_handler = urllib2.HTTPBasicAuthHandler(manager)
69
71
    opener = urllib2.build_opener(auth_handler)
70
 
    url_f = opener.open(url)
71
 
    return url_f
 
72
 
 
73
    request = urllib2.Request(url)
 
74
    request.add_header('User-Agent', 'bzr/%s' % bzrlib.__version__)
 
75
    response = opener.open(request)
 
76
    return response
72
77
 
73
78
class HttpTransport(Transport):
74
79
    """This is the transport agent for http:// access.
79
84
    def __init__(self, base):
80
85
        """Set the base path where files will be stored."""
81
86
        assert base.startswith('http://') or base.startswith('https://')
 
87
        if base[-1] != '/':
 
88
            base = base + '/'
82
89
        super(HttpTransport, self).__init__(base)
83
90
        # In the future we might actually connect to the remote host
84
91
        # rather than using get_url
208
215
        """Create a directory at the given path."""
209
216
        raise TransportNotPossible('http does not support mkdir()')
210
217
 
 
218
    def rmdir(self, relpath):
 
219
        """See Transport.rmdir."""
 
220
        raise TransportNotPossible('http does not support rmdir()')
 
221
 
211
222
    def append(self, relpath, f):
212
223
        """Append the text in the file-like object into the final
213
224
        location.
242
253
        """Delete the item at relpath"""
243
254
        raise TransportNotPossible('http does not support delete()')
244
255
 
 
256
    def is_readonly(self):
 
257
        """See Transport.is_readonly."""
 
258
        return True
 
259
 
245
260
    def listable(self):
246
261
        """See Transport.listable."""
247
262
        return False
271
286
        :return: A lock object, which should be passed to Transport.unlock()
272
287
        """
273
288
        raise TransportNotPossible('http does not support lock_write()')
 
289
 
 
290
 
 
291
#---------------- test server facilities ----------------
 
292
import BaseHTTPServer, SimpleHTTPServer, socket, time
 
293
import threading
 
294
 
 
295
 
 
296
class WebserverNotAvailable(Exception):
 
297
    pass
 
298
 
 
299
 
 
300
class BadWebserverPath(ValueError):
 
301
    def __str__(self):
 
302
        return 'path %s is not in %s' % self.args
 
303
 
 
304
 
 
305
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
 
306
 
 
307
    def log_message(self, format, *args):
 
308
        self.server.test_case.log("webserver - %s - - [%s] %s",
 
309
                                  self.address_string(),
 
310
                                  self.log_date_time_string(),
 
311
                                  format%args)
 
312
 
 
313
    def handle_one_request(self):
 
314
        """Handle a single HTTP request.
 
315
 
 
316
        You normally don't need to override this method; see the class
 
317
        __doc__ string for information on how to handle specific HTTP
 
318
        commands such as GET and POST.
 
319
 
 
320
        """
 
321
        for i in xrange(1,11): # Don't try more than 10 times
 
322
            try:
 
323
                self.raw_requestline = self.rfile.readline()
 
324
            except socket.error, e:
 
325
                if e.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
 
326
                    # omitted for now because some tests look at the log of
 
327
                    # the server and expect to see no errors.  see recent
 
328
                    # email thread. -- mbp 20051021. 
 
329
                    ## self.log_message('EAGAIN (%d) while reading from raw_requestline' % i)
 
330
                    time.sleep(0.01)
 
331
                    continue
 
332
                raise
 
333
            else:
 
334
                break
 
335
        if not self.raw_requestline:
 
336
            self.close_connection = 1
 
337
            return
 
338
        if not self.parse_request(): # An error code has been sent, just exit
 
339
            return
 
340
        mname = 'do_' + self.command
 
341
        if not hasattr(self, mname):
 
342
            self.send_error(501, "Unsupported method (%r)" % self.command)
 
343
            return
 
344
        method = getattr(self, mname)
 
345
        method()
 
346
 
 
347
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
 
348
    def __init__(self, server_address, RequestHandlerClass, test_case):
 
349
        BaseHTTPServer.HTTPServer.__init__(self, server_address,
 
350
                                                RequestHandlerClass)
 
351
        self.test_case = test_case
 
352
 
 
353
 
 
354
class HttpServer(Server):
 
355
    """A test server for http transports."""
 
356
 
 
357
    _HTTP_PORTS = range(13000, 0x8000)
 
358
 
 
359
    def _http_start(self):
 
360
        httpd = None
 
361
        for port in self._HTTP_PORTS:
 
362
            try:
 
363
                httpd = TestingHTTPServer(('localhost', port),
 
364
                                          TestingHTTPRequestHandler,
 
365
                                          self)
 
366
            except socket.error, e:
 
367
                if e.args[0] == errno.EADDRINUSE:
 
368
                    continue
 
369
                print >>sys.stderr, "Cannot run webserver :-("
 
370
                raise
 
371
            else:
 
372
                break
 
373
 
 
374
        if httpd is None:
 
375
            raise WebserverNotAvailable("Cannot run webserver :-( "
 
376
                                        "no free ports in range %s..%s" %
 
377
                                        (_HTTP_PORTS[0], _HTTP_PORTS[-1]))
 
378
 
 
379
        self._http_base_url = 'http://localhost:%s/' % port
 
380
        self._http_starting.release()
 
381
        httpd.socket.settimeout(0.1)
 
382
 
 
383
        while self._http_running:
 
384
            try:
 
385
                httpd.handle_request()
 
386
            except socket.timeout:
 
387
                pass
 
388
 
 
389
    def _get_remote_url(self, path):
 
390
        path_parts = path.split(os.path.sep)
 
391
        if os.path.isabs(path):
 
392
            if path_parts[:len(self._local_path_parts)] != \
 
393
                   self._local_path_parts:
 
394
                raise BadWebserverPath(path, self.test_dir)
 
395
            remote_path = '/'.join(path_parts[len(self._local_path_parts):])
 
396
        else:
 
397
            remote_path = '/'.join(path_parts)
 
398
 
 
399
        self._http_starting.acquire()
 
400
        self._http_starting.release()
 
401
        return self._http_base_url + remote_path
 
402
 
 
403
    def log(self, *args, **kwargs):
 
404
        """Capture Server log output."""
 
405
        self.logs.append(args[3])
 
406
 
 
407
    def setUp(self):
 
408
        """See bzrlib.transport.Server.setUp."""
 
409
        self._home_dir = os.getcwdu()
 
410
        self._local_path_parts = self._home_dir.split(os.path.sep)
 
411
        self._http_starting = threading.Lock()
 
412
        self._http_starting.acquire()
 
413
        self._http_running = True
 
414
        self._http_base_url = None
 
415
        self._http_thread = threading.Thread(target=self._http_start)
 
416
        self._http_thread.setDaemon(True)
 
417
        self._http_thread.start()
 
418
        self._http_proxy = os.environ.get("http_proxy")
 
419
        if self._http_proxy is not None:
 
420
            del os.environ["http_proxy"]
 
421
        self.logs = []
 
422
 
 
423
    def tearDown(self):
 
424
        """See bzrlib.transport.Server.tearDown."""
 
425
        self._http_running = False
 
426
        self._http_thread.join()
 
427
        if self._http_proxy is not None:
 
428
            import os
 
429
            os.environ["http_proxy"] = self._http_proxy
 
430
 
 
431
    def get_url(self):
 
432
        """See bzrlib.transport.Server.get_url."""
 
433
        return self._get_remote_url(self._home_dir)
 
434
        
 
435
    def get_bogus_url(self):
 
436
        """See bzrlib.transport.Server.get_bogus_url."""
 
437
        return 'http://jasldkjsalkdjalksjdkljasd'
 
438
 
 
439
 
 
440
def get_test_permutations():
 
441
    """Return the permutations to be used in testing."""
 
442
    warn("There are no HTTPS transport provider tests yet.")
 
443
    return [(HttpTransport, HttpServer),
 
444
            ]