~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

  • Committer: Robert Collins
  • Date: 2005-10-02 22:47:02 UTC
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20051002224701-8a8b20b90de559a6
support ghosts in commits

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
"""Base implementation of Transport over http.
18
 
 
19
 
There are separate implementation modules for each http client implementation.
 
16
"""Implementation of Transport over http.
20
17
"""
21
18
 
 
19
from bzrlib.transport import Transport, register_transport
 
20
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
 
21
                           NonRelativePath, TransportError)
 
22
import os, errno
22
23
from cStringIO import StringIO
23
 
import errno
24
 
import mimetools
25
 
import os
26
 
import posixpath
27
 
import re
28
 
import sys
 
24
import urllib2
29
25
import urlparse
30
 
import urllib
31
 
from warnings import warn
32
 
 
33
 
# TODO: load these only when running http tests
34
 
import BaseHTTPServer, SimpleHTTPServer, socket, time
35
 
import threading
36
 
 
37
 
from bzrlib import errors
38
 
from bzrlib.errors import (TransportNotPossible, NoSuchFile,
39
 
                           TransportError, ConnectionError, InvalidURL)
 
26
 
 
27
from bzrlib.errors import BzrError, BzrCheckError
40
28
from bzrlib.branch import Branch
41
29
from bzrlib.trace import mutter
42
 
from bzrlib.transport import Transport, register_transport, Server
43
 
from bzrlib.transport.http.response import (HttpMultipartRangeResponse,
44
 
                                            HttpRangeResponse)
45
 
from bzrlib.ui import ui_factory
46
 
 
47
 
 
48
 
def extract_auth(url, password_manager):
49
 
    """Extract auth parameters from am HTTP/HTTPS url and add them to the given
50
 
    password manager.  Return the url, minus those auth parameters (which
51
 
    confuse urllib2).
52
 
    """
53
 
    assert re.match(r'^(https?)(\+\w+)?://', url), \
54
 
            'invalid absolute url %r' % url
55
 
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
 
30
 
 
31
# velocitynet.com.au transparently proxies connections and thereby
 
32
# breaks keep-alive -- sucks!
 
33
 
 
34
 
 
35
def get_url(url):
 
36
    import urllib2
 
37
    mutter("get_url %s" % url)
 
38
    url_f = urllib2.urlopen(url)
 
39
    return url_f
 
40
 
 
41
class HttpTransportError(TransportError):
 
42
    pass
 
43
 
 
44
class HttpTransport(Transport):
 
45
    """This is the transport agent for http:// access.
56
46
    
57
 
    if '@' in netloc:
58
 
        auth, netloc = netloc.split('@', 1)
59
 
        if ':' in auth:
60
 
            username, password = auth.split(':', 1)
61
 
        else:
62
 
            username, password = auth, None
63
 
        if ':' in netloc:
64
 
            host = netloc.split(':', 1)[0]
65
 
        else:
66
 
            host = netloc
67
 
        username = urllib.unquote(username)
68
 
        if password is not None:
69
 
            password = urllib.unquote(password)
70
 
        else:
71
 
            password = ui_factory.get_password(prompt='HTTP %(user)@%(host) password',
72
 
                                               user=username, host=host)
73
 
        password_manager.add_password(None, host, username, password)
74
 
    url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
75
 
    return url
76
 
 
77
 
 
78
 
def _extract_headers(header_text, url):
79
 
    """Extract the mapping for an rfc2822 header
80
 
 
81
 
    This is a helper function for the test suite and for _pycurl.
82
 
    (urllib already parses the headers for us)
83
 
 
84
 
    In the case that there are multiple headers inside the file,
85
 
    the last one is returned.
86
 
 
87
 
    :param header_text: A string of header information.
88
 
        This expects that the first line of a header will always be HTTP ...
89
 
    :param url: The url we are parsing, so we can raise nice errors
90
 
    :return: mimetools.Message object, which basically acts like a case 
91
 
        insensitive dictionary.
92
 
    """
93
 
    first_header = True
94
 
    remaining = header_text
95
 
 
96
 
    if not remaining:
97
 
        raise errors.InvalidHttpResponse(url, 'Empty headers')
98
 
 
99
 
    while remaining:
100
 
        header_file = StringIO(remaining)
101
 
        first_line = header_file.readline()
102
 
        if not first_line.startswith('HTTP'):
103
 
            if first_header: # The first header *must* start with HTTP
104
 
                raise errors.InvalidHttpResponse(url,
105
 
                    'Opening header line did not start with HTTP: %s' 
106
 
                    % (first_line,))
107
 
                assert False, 'Opening header line was not HTTP'
108
 
            else:
109
 
                break # We are done parsing
110
 
        first_header = False
111
 
        m = mimetools.Message(header_file)
112
 
 
113
 
        # mimetools.Message parses the first header up to a blank line
114
 
        # So while there is remaining data, it probably means there is
115
 
        # another header to be parsed.
116
 
        # Get rid of any preceeding whitespace, which if it is all whitespace
117
 
        # will get rid of everything.
118
 
        remaining = header_file.read().lstrip()
119
 
    return m
120
 
 
121
 
 
122
 
class HttpTransportBase(Transport):
123
 
    """Base class for http implementations.
124
 
 
125
 
    Does URL parsing, etc, but not any network IO.
126
 
 
127
 
    The protocol can be given as e.g. http+urllib://host/ to use a particular
128
 
    implementation.
129
 
    """
130
 
 
131
 
    # _proto: "http" or "https"
132
 
    # _qualified_proto: may have "+pycurl", etc
 
47
    TODO: Implement pipelined versions of all of the *_multi() functions.
 
48
    """
133
49
 
134
50
    def __init__(self, base):
135
51
        """Set the base path where files will be stored."""
136
 
        proto_match = re.match(r'^(https?)(\+\w+)?://', base)
137
 
        if not proto_match:
138
 
            raise AssertionError("not a http url: %r" % base)
139
 
        self._proto = proto_match.group(1)
140
 
        impl_name = proto_match.group(2)
141
 
        if impl_name:
142
 
            impl_name = impl_name[1:]
143
 
        self._impl_name = impl_name
144
 
        if base[-1] != '/':
145
 
            base = base + '/'
146
 
        super(HttpTransportBase, self).__init__(base)
 
52
        assert base.startswith('http://') or base.startswith('https://')
 
53
        super(HttpTransport, self).__init__(base)
147
54
        # In the future we might actually connect to the remote host
148
55
        # rather than using get_url
149
56
        # self._connection = None
150
 
        (apparent_proto, self._host,
 
57
        (self._proto, self._host,
151
58
            self._path, self._parameters,
152
59
            self._query, self._fragment) = urlparse.urlparse(self.base)
153
 
        self._qualified_proto = apparent_proto
 
60
 
 
61
    def should_cache(self):
 
62
        """Return True if the data pulled across should be cached locally.
 
63
        """
 
64
        return True
 
65
 
 
66
    def clone(self, offset=None):
 
67
        """Return a new HttpTransport with root at self.base + offset
 
68
        For now HttpTransport does not actually connect, so just return
 
69
        a new HttpTransport object.
 
70
        """
 
71
        if offset is None:
 
72
            return HttpTransport(self.base)
 
73
        else:
 
74
            return HttpTransport(self.abspath(offset))
154
75
 
155
76
    def abspath(self, relpath):
156
77
        """Return the full url to the given relative path.
157
 
 
158
 
        This can be supplied with a string or a list.
159
 
 
160
 
        The URL returned always has the protocol scheme originally used to 
161
 
        construct the transport, even if that includes an explicit
162
 
        implementation qualifier.
 
78
        This can be supplied with a string or a list
163
79
        """
164
 
        assert isinstance(relpath, basestring)
165
 
        if isinstance(relpath, unicode):
166
 
            raise InvalidURL(relpath, 'paths must not be unicode.')
167
80
        if isinstance(relpath, basestring):
168
 
            relpath_parts = relpath.split('/')
169
 
        else:
170
 
            # TODO: Don't call this with an array - no magic interfaces
171
 
            relpath_parts = relpath[:]
172
 
        if len(relpath_parts) > 1:
173
 
            if relpath_parts[0] == '':
174
 
                raise ValueError("path %r within branch %r seems to be absolute"
175
 
                                 % (relpath, self._path))
176
 
            if relpath_parts[-1] == '':
177
 
                raise ValueError("path %r within branch %r seems to be a directory"
178
 
                                 % (relpath, self._path))
 
81
            relpath = [relpath]
179
82
        basepath = self._path.split('/')
180
83
        if len(basepath) > 0 and basepath[-1] == '':
181
84
            basepath = basepath[:-1]
182
 
        for p in relpath_parts:
 
85
 
 
86
        for p in relpath:
183
87
            if p == '..':
184
 
                if len(basepath) == 0:
 
88
                if len(basepath) < 0:
185
89
                    # In most filesystems, a request for the parent
186
90
                    # of root, just returns root.
187
91
                    continue
188
92
                basepath.pop()
189
 
            elif p == '.' or p == '':
 
93
            elif p == '.':
190
94
                continue # No-op
191
95
            else:
192
96
                basepath.append(p)
 
97
 
193
98
        # Possibly, we could use urlparse.urljoin() here, but
194
99
        # I'm concerned about when it chooses to strip the last
195
100
        # portion of the path, and when it doesn't.
196
101
        path = '/'.join(basepath)
197
 
        if path == '':
198
 
            path = '/'
199
 
        result = urlparse.urlunparse((self._qualified_proto,
200
 
                                    self._host, path, '', '', ''))
201
 
        return result
 
102
        return urlparse.urlunparse((self._proto,
 
103
                self._host, path, '', '', ''))
202
104
 
203
 
    def _real_abspath(self, relpath):
204
 
        """Produce absolute path, adjusting protocol if needed"""
205
 
        abspath = self.abspath(relpath)
206
 
        qp = self._qualified_proto
207
 
        rp = self._proto
208
 
        if self._qualified_proto != self._proto:
209
 
            abspath = rp + abspath[len(qp):]
210
 
        if not isinstance(abspath, str):
211
 
            # escaping must be done at a higher level
212
 
            abspath = abspath.encode('ascii')
213
 
        return abspath
 
105
    def relpath(self, abspath):
 
106
        if not abspath.startswith(self.base):
 
107
            raise NonRelativePath('path %r is not under base URL %r'
 
108
                           % (abspath, self.base))
 
109
        pl = len(self.base)
 
110
        return abspath[pl:].lstrip('/')
214
111
 
215
112
    def has(self, relpath):
216
 
        raise NotImplementedError("has() is abstract on %r" % self)
217
 
 
218
 
    def get(self, relpath):
 
113
        """Does the target location exist?
 
114
 
 
115
        TODO: HttpTransport.has() should use a HEAD request,
 
116
        not a full GET request.
 
117
 
 
118
        TODO: This should be changed so that we don't use
 
119
        urllib2 and get an exception, the code path would be
 
120
        cleaner if we just do an http HEAD request, and parse
 
121
        the return code.
 
122
        """
 
123
        try:
 
124
            f = get_url(self.abspath(relpath))
 
125
            # Without the read and then close()
 
126
            # we tend to have busy sockets.
 
127
            f.read()
 
128
            f.close()
 
129
            return True
 
130
        except BzrError:
 
131
            return False
 
132
        except urllib2.URLError:
 
133
            return False
 
134
        except IOError, e:
 
135
            if e.errno == errno.ENOENT:
 
136
                return False
 
137
            raise HttpTransportError(orig_error=e)
 
138
 
 
139
    def get(self, relpath, decode=False):
219
140
        """Get the file at the given relative path.
220
141
 
221
142
        :param relpath: The relative path to the file
222
143
        """
223
 
        code, response_file = self._get(relpath, None)
224
 
        return response_file
225
 
 
226
 
    def _get(self, relpath, ranges):
227
 
        """Get a file, or part of a file.
228
 
 
229
 
        :param relpath: Path relative to transport base URL
230
 
        :param byte_range: None to get the whole file;
231
 
            or [(start,end)] to fetch parts of a file.
232
 
 
233
 
        :returns: (http_code, result_file)
234
 
 
235
 
        Note that the current http implementations can only fetch one range at
236
 
        a time through this call.
237
 
        """
238
 
        raise NotImplementedError(self._get)
239
 
 
240
 
    def readv(self, relpath, offsets):
241
 
        """Get parts of the file at the given relative path.
242
 
 
243
 
        :param offsets: A list of (offset, size) tuples.
244
 
        :param return: A list or generator of (offset, data) tuples
245
 
        """
246
 
        ranges = self.offsets_to_ranges(offsets)
247
 
        mutter('http readv of %s collapsed %s offsets => %s',
248
 
                relpath, len(offsets), ranges)
249
 
        code, f = self._get(relpath, ranges)
250
 
        for start, size in offsets:
251
 
            f.seek(start, (start < 0) and 2 or 0)
252
 
            start = f.tell()
253
 
            data = f.read(size)
254
 
            assert len(data) == size
255
 
            yield start, data
256
 
 
257
 
    @staticmethod
258
 
    def offsets_to_ranges(offsets):
259
 
        """Turn a list of offsets and sizes into a list of byte ranges.
260
 
 
261
 
        :param offsets: A list of tuples of (start, size).  An empty list
262
 
            is not accepted.
263
 
        :return: a list of inclusive byte ranges (start, end) 
264
 
            Adjacent ranges will be combined.
265
 
        """
266
 
        # Make sure we process sorted offsets
267
 
        offsets = sorted(offsets)
268
 
 
269
 
        prev_end = None
270
 
        combined = []
271
 
 
272
 
        for start, size in offsets:
273
 
            end = start + size - 1
274
 
            if prev_end is None:
275
 
                combined.append([start, end])
276
 
            elif start <= prev_end + 1:
277
 
                combined[-1][1] = end
278
 
            else:
279
 
                combined.append([start, end])
280
 
            prev_end = end
281
 
 
282
 
        return combined
283
 
 
284
 
    def put(self, relpath, f, mode=None):
 
144
        try:
 
145
            return get_url(self.abspath(relpath))
 
146
        except (BzrError, urllib2.URLError, IOError), e:
 
147
            raise NoSuchFile(orig_error=e)
 
148
        except Exception,e:
 
149
            raise HttpTransportError(orig_error=e)
 
150
 
 
151
    def get_partial(self, relpath, start, length=None):
 
152
        """Get just part of a file.
 
153
 
 
154
        :param relpath: Path to the file, relative to base
 
155
        :param start: The starting position to read from
 
156
        :param length: The length to read. A length of None indicates
 
157
                       read to the end of the file.
 
158
        :return: A file-like object containing at least the specified bytes.
 
159
                 Some implementations may return objects which can be read
 
160
                 past this length, but this is not guaranteed.
 
161
        """
 
162
        # TODO: You can make specialized http requests for just
 
163
        # a portion of the file. Figure out how to do that.
 
164
        # For now, urllib2 returns files that cannot seek() so
 
165
        # we just read bytes off the beginning, until we
 
166
        # get to the point that we care about.
 
167
        f = self.get(relpath)
 
168
        # TODO: read in smaller chunks, in case things are
 
169
        # buffered internally.
 
170
        f.read(start)
 
171
        return f
 
172
 
 
173
    def put(self, relpath, f):
285
174
        """Copy the file-like or string object into the location.
286
175
 
287
176
        :param relpath: Location to put the contents, relative to base.
289
178
        """
290
179
        raise TransportNotPossible('http PUT not supported')
291
180
 
292
 
    def mkdir(self, relpath, mode=None):
 
181
    def mkdir(self, relpath):
293
182
        """Create a directory at the given path."""
294
183
        raise TransportNotPossible('http does not support mkdir()')
295
184
 
296
 
    def rmdir(self, relpath):
297
 
        """See Transport.rmdir."""
298
 
        raise TransportNotPossible('http does not support rmdir()')
299
 
 
300
185
    def append(self, relpath, f):
301
186
        """Append the text in the file-like object into the final
302
187
        location.
307
192
        """Copy the item at rel_from to the location at rel_to"""
308
193
        raise TransportNotPossible('http does not support copy()')
309
194
 
310
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
195
    def copy_to(self, relpaths, other, pb=None):
311
196
        """Copy a set of entries from self into another Transport.
312
197
 
313
198
        :param relpaths: A list/generator of entries to be copied.
318
203
        # At this point HttpTransport might be able to check and see if
319
204
        # the remote location is the same, and rather than download, and
320
205
        # then upload, it could just issue a remote copy_this command.
321
 
        if isinstance(other, HttpTransportBase):
 
206
        if isinstance(other, HttpTransport):
322
207
            raise TransportNotPossible('http cannot be the target of copy_to()')
323
208
        else:
324
 
            return super(HttpTransportBase, self).\
325
 
                    copy_to(relpaths, other, mode=mode, pb=pb)
 
209
            return super(HttpTransport, self).copy_to(relpaths, other, pb=pb)
326
210
 
327
211
    def move(self, rel_from, rel_to):
328
212
        """Move the item at rel_from to the location at rel_to"""
332
216
        """Delete the item at relpath"""
333
217
        raise TransportNotPossible('http does not support delete()')
334
218
 
335
 
    def is_readonly(self):
336
 
        """See Transport.is_readonly."""
337
 
        return True
338
 
 
339
 
    def listable(self):
340
 
        """See Transport.listable."""
341
 
        return False
 
219
    def list_dir(self, relpath):
 
220
        """Return a list of all files at the given location.
 
221
        WARNING: many transports do not support this, so trying avoid using
 
222
        it if at all possible.
 
223
        """
 
224
        raise TransportNotPossible('http does not support list_dir()')
342
225
 
343
226
    def stat(self, relpath):
344
227
        """Return the stat information for a file.
366
249
        """
367
250
        raise TransportNotPossible('http does not support lock_write()')
368
251
 
369
 
    def clone(self, offset=None):
370
 
        """Return a new HttpTransportBase with root at self.base + offset
371
 
        For now HttpTransportBase does not actually connect, so just return
372
 
        a new HttpTransportBase object.
373
 
        """
374
 
        if offset is None:
375
 
            return self.__class__(self.base)
376
 
        else:
377
 
            return self.__class__(self.abspath(offset))
378
 
 
379
 
    @staticmethod
380
 
    def range_header(ranges, tail_amount):
381
 
        """Turn a list of bytes ranges into a HTTP Range header value.
382
 
 
383
 
        :param offsets: A list of byte ranges, (start, end). An empty list
384
 
        is not accepted.
385
 
 
386
 
        :return: HTTP range header string.
387
 
        """
388
 
        strings = []
389
 
        for start, end in ranges:
390
 
            strings.append('%d-%d' % (start, end))
391
 
 
392
 
        if tail_amount:
393
 
            strings.append('-%d' % tail_amount)
394
 
 
395
 
        return ','.join(strings)
396
 
 
397
 
 
398
 
#---------------- test server facilities ----------------
399
 
# TODO: load these only when running tests
400
 
 
401
 
 
402
 
class WebserverNotAvailable(Exception):
403
 
    pass
404
 
 
405
 
 
406
 
class BadWebserverPath(ValueError):
407
 
    def __str__(self):
408
 
        return 'path %s is not in %s' % self.args
409
 
 
410
 
 
411
 
class TestingHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
412
 
 
413
 
    def log_message(self, format, *args):
414
 
        self.server.test_case.log('webserver - %s - - [%s] %s "%s" "%s"',
415
 
                                  self.address_string(),
416
 
                                  self.log_date_time_string(),
417
 
                                  format % args,
418
 
                                  self.headers.get('referer', '-'),
419
 
                                  self.headers.get('user-agent', '-'))
420
 
 
421
 
    def handle_one_request(self):
422
 
        """Handle a single HTTP request.
423
 
 
424
 
        You normally don't need to override this method; see the class
425
 
        __doc__ string for information on how to handle specific HTTP
426
 
        commands such as GET and POST.
427
 
 
428
 
        """
429
 
        for i in xrange(1,11): # Don't try more than 10 times
430
 
            try:
431
 
                self.raw_requestline = self.rfile.readline()
432
 
            except socket.error, e:
433
 
                if e.args[0] in (errno.EAGAIN, errno.EWOULDBLOCK):
434
 
                    # omitted for now because some tests look at the log of
435
 
                    # the server and expect to see no errors.  see recent
436
 
                    # email thread. -- mbp 20051021. 
437
 
                    ## self.log_message('EAGAIN (%d) while reading from raw_requestline' % i)
438
 
                    time.sleep(0.01)
439
 
                    continue
440
 
                raise
441
 
            else:
442
 
                break
443
 
        if not self.raw_requestline:
444
 
            self.close_connection = 1
445
 
            return
446
 
        if not self.parse_request(): # An error code has been sent, just exit
447
 
            return
448
 
        mname = 'do_' + self.command
449
 
        if not hasattr(self, mname):
450
 
            self.send_error(501, "Unsupported method (%r)" % self.command)
451
 
            return
452
 
        method = getattr(self, mname)
453
 
        method()
454
 
 
455
 
    if sys.platform == 'win32':
456
 
        # On win32 you cannot access non-ascii filenames without
457
 
        # decoding them into unicode first.
458
 
        # However, under Linux, you can access bytestream paths
459
 
        # without any problems. If this function was always active
460
 
        # it would probably break tests when LANG=C was set
461
 
        def translate_path(self, path):
462
 
            """Translate a /-separated PATH to the local filename syntax.
463
 
 
464
 
            For bzr, all url paths are considered to be utf8 paths.
465
 
            On Linux, you can access these paths directly over the bytestream
466
 
            request, but on win32, you must decode them, and access them
467
 
            as Unicode files.
468
 
            """
469
 
            # abandon query parameters
470
 
            path = urlparse.urlparse(path)[2]
471
 
            path = posixpath.normpath(urllib.unquote(path))
472
 
            path = path.decode('utf-8')
473
 
            words = path.split('/')
474
 
            words = filter(None, words)
475
 
            path = os.getcwdu()
476
 
            for word in words:
477
 
                drive, word = os.path.splitdrive(word)
478
 
                head, word = os.path.split(word)
479
 
                if word in (os.curdir, os.pardir): continue
480
 
                path = os.path.join(path, word)
481
 
            return path
482
 
 
483
 
 
484
 
class TestingHTTPServer(BaseHTTPServer.HTTPServer):
485
 
    def __init__(self, server_address, RequestHandlerClass, test_case):
486
 
        BaseHTTPServer.HTTPServer.__init__(self, server_address,
487
 
                                                RequestHandlerClass)
488
 
        self.test_case = test_case
489
 
 
490
 
 
491
 
class HttpServer(Server):
492
 
    """A test server for http transports."""
493
 
 
494
 
    # used to form the url that connects to this server
495
 
    _url_protocol = 'http'
496
 
 
497
 
    def _http_start(self):
498
 
        httpd = None
499
 
        httpd = TestingHTTPServer(('localhost', 0),
500
 
                                  TestingHTTPRequestHandler,
501
 
                                  self)
502
 
        host, port = httpd.socket.getsockname()
503
 
        self._http_base_url = '%s://localhost:%s/' % (self._url_protocol, port)
504
 
        self._http_starting.release()
505
 
        httpd.socket.settimeout(0.1)
506
 
 
507
 
        while self._http_running:
508
 
            try:
509
 
                httpd.handle_request()
510
 
            except socket.timeout:
511
 
                pass
512
 
 
513
 
    def _get_remote_url(self, path):
514
 
        path_parts = path.split(os.path.sep)
515
 
        if os.path.isabs(path):
516
 
            if path_parts[:len(self._local_path_parts)] != \
517
 
                   self._local_path_parts:
518
 
                raise BadWebserverPath(path, self.test_dir)
519
 
            remote_path = '/'.join(path_parts[len(self._local_path_parts):])
520
 
        else:
521
 
            remote_path = '/'.join(path_parts)
522
 
 
523
 
        self._http_starting.acquire()
524
 
        self._http_starting.release()
525
 
        return self._http_base_url + remote_path
526
 
 
527
 
    def log(self, format, *args):
528
 
        """Capture Server log output."""
529
 
        self.logs.append(format % args)
530
 
 
531
 
    def setUp(self):
532
 
        """See bzrlib.transport.Server.setUp."""
533
 
        self._home_dir = os.getcwdu()
534
 
        self._local_path_parts = self._home_dir.split(os.path.sep)
535
 
        self._http_starting = threading.Lock()
536
 
        self._http_starting.acquire()
537
 
        self._http_running = True
538
 
        self._http_base_url = None
539
 
        self._http_thread = threading.Thread(target=self._http_start)
540
 
        self._http_thread.setDaemon(True)
541
 
        self._http_thread.start()
542
 
        self._http_proxy = os.environ.get("http_proxy")
543
 
        if self._http_proxy is not None:
544
 
            del os.environ["http_proxy"]
545
 
        self.logs = []
546
 
 
547
 
    def tearDown(self):
548
 
        """See bzrlib.transport.Server.tearDown."""
549
 
        self._http_running = False
550
 
        self._http_thread.join()
551
 
        if self._http_proxy is not None:
552
 
            import os
553
 
            os.environ["http_proxy"] = self._http_proxy
554
 
 
555
 
    def get_url(self):
556
 
        """See bzrlib.transport.Server.get_url."""
557
 
        return self._get_remote_url(self._home_dir)
558
 
        
559
 
    def get_bogus_url(self):
560
 
        """See bzrlib.transport.Server.get_bogus_url."""
561
 
        # this is chosen to try to prevent trouble with proxies, wierd dns,
562
 
        # etc
563
 
        return 'http://127.0.0.1:1/'
564
 
 
 
252
register_transport('http://', HttpTransport)
 
253
register_transport('https://', HttpTransport)