~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/__init__.py

  • Committer: Martin Pool
  • Date: 2006-03-10 06:29:53 UTC
  • mfrom: (1608 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1611.
  • Revision ID: mbp@sourcefrog.net-20060310062953-bc1c7ade75c89a7a
[merge] bzr.dev; pycurl not updated for readv yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
import errno
23
23
import os
 
24
from collections import deque
24
25
from cStringIO import StringIO
25
26
import re
26
27
import urlparse
40
41
 
41
42
 
42
43
def extract_auth(url, password_manager):
43
 
    """
44
 
    Extract auth parameters from am HTTP/HTTPS url and add them to the given
 
44
    """Extract auth parameters from am HTTP/HTTPS url and add them to the given
45
45
    password manager.  Return the url, minus those auth parameters (which
46
46
    confuse urllib2).
47
47
    """
48
 
    assert re.match(r'^(https?)(\+\w+)?://', url)
 
48
    assert re.match(r'^(https?)(\+\w+)?://', url), \
 
49
            'invalid absolute url %r' % url
49
50
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
50
51
    
51
52
    if '@' in netloc:
67
68
        password_manager.add_password(None, host, username, password)
68
69
    url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
69
70
    return url
70
 
    
 
71
 
71
72
 
72
73
class HttpTransportBase(Transport):
73
74
    """Base class for http implementations.
163
164
    def has(self, relpath):
164
165
        raise NotImplementedError("has() is abstract on %r" % self)
165
166
 
166
 
    def stat(self, relpath):
167
 
        """Return the stat information for a file.
168
 
        """
169
 
        raise TransportNotPossible('http does not support stat()')
170
 
 
171
 
    def lock_read(self, relpath):
172
 
        """Lock the given file for shared (read) access.
173
 
        :return: A lock object, which should be passed to Transport.unlock()
174
 
        """
175
 
        # The old RemoteBranch ignore lock for reading, so we will
176
 
        # continue that tradition and return a bogus lock object.
177
 
        class BogusLock(object):
178
 
            def __init__(self, path):
179
 
                self.path = path
180
 
            def unlock(self):
181
 
                pass
182
 
        return BogusLock(relpath)
183
 
 
184
 
    def lock_write(self, relpath):
185
 
        """Lock the given file for exclusive (write) access.
186
 
        WARNING: many transports do not support this, so trying avoid using it
187
 
 
188
 
        :return: A lock object, which should be passed to Transport.unlock()
189
 
        """
190
 
        raise TransportNotPossible('http does not support lock_write()')
191
 
 
192
 
    def clone(self, offset=None):
193
 
        """Return a new HttpTransportBase with root at self.base + offset
194
 
        For now HttpTransportBase does not actually connect, so just return
195
 
        a new HttpTransportBase object.
196
 
        """
197
 
        if offset is None:
198
 
            return self.__class__(self.base)
199
 
        else:
200
 
            return self.__class__(self.abspath(offset))
201
 
 
202
 
    def listable(self):
203
 
        """Returns false - http has no reliable way to list directories."""
204
 
        # well, we could try DAV...
205
 
        return False
 
167
    def get(self, relpath):
 
168
        """Get the file at the given relative path.
 
169
 
 
170
        :param relpath: The relative path to the file
 
171
        """
 
172
        return self._get(relpath, [])
 
173
 
 
174
    def _get(self, relpath, ranges):
 
175
        """GET a file over http
 
176
 
 
177
        :param relpath: URL relative to base of this Transport
 
178
        :param ranges: None to fetch the whole resource; or a string giving the bytes
 
179
             to fetch.
 
180
        """   
 
181
        raise NotImplementedError(self._get)
 
182
 
 
183
    def readv(self, relpath, offsets):
 
184
        """Get parts of the file at the given relative path.
 
185
 
 
186
        :param offsets: A list of (offset, size) tuples.
 
187
        :return: A list or generator of (offset, data) tuples
 
188
        """
 
189
        # this is not quite regular enough to have a single driver routine and
 
190
        # helper method in Transport.
 
191
        def do_combined_read(combined_offsets):
 
192
            # read one coalesced block
 
193
            total_size = 0
 
194
            for offset, size in combined_offsets:
 
195
                total_size += size
 
196
            mutter('readv coalesced %d reads.', len(combined_offsets))
 
197
            offset = combined_offsets[0][0]
 
198
            ranges = 'bytes=%d-%d' % (offset, offset + total_size - 1)
 
199
            response = self._get(relpath, ranges=ranges)
 
200
            if response.code == 206:
 
201
                for off, size in combined_offsets:
 
202
                    yield off, response.read(size)
 
203
            elif response.code == 200:
 
204
                data = response.read(offset + total_size)[offset:offset + total_size]
 
205
                pos = 0
 
206
                for offset, size in combined_offsets:
 
207
                    yield offset, data[pos:pos + size]
 
208
                    pos += size
 
209
                del data
 
210
 
 
211
        if not len(offsets):
 
212
            return
 
213
        pending_offsets = deque(offsets)
 
214
        combined_offsets = []
 
215
        while len(pending_offsets):
 
216
            offset, size = pending_offsets.popleft()
 
217
            if not combined_offsets:
 
218
                combined_offsets = [[offset, size]]
 
219
            else:
 
220
                if (len (combined_offsets) < 500 and
 
221
                    combined_offsets[-1][0] + combined_offsets[-1][1] == offset):
 
222
                    # combatible offset:
 
223
                    combined_offsets.append([offset, size])
 
224
                else:
 
225
                    # incompatible, or over the threshold issue a read and yield
 
226
                    pending_offsets.appendleft((offset, size))
 
227
                    for result in do_combined_read(combined_offsets):
 
228
                        yield result
 
229
                    combined_offsets = []
 
230
        # whatever is left is a single coalesced request
 
231
        if len(combined_offsets):
 
232
            for result in do_combined_read(combined_offsets):
 
233
                yield result
206
234
 
207
235
    def put(self, relpath, f, mode=None):
208
236
        """Copy the file-like or string object into the location.
238
266
        TODO: if other is LocalTransport, is it possible to
239
267
              do better than put(get())?
240
268
        """
241
 
        # At this point HttpTransportBase might be able to check and see if
 
269
        # At this point HttpTransport might be able to check and see if
242
270
        # the remote location is the same, and rather than download, and
243
271
        # then upload, it could just issue a remote copy_this command.
244
272
        if isinstance(other, HttpTransportBase):
245
273
            raise TransportNotPossible('http cannot be the target of copy_to()')
246
274
        else:
247
 
            return super(HttpTransportBase, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
275
            return super(HttpTransportBase, self).\
 
276
                    copy_to(relpaths, other, mode=mode, pb=pb)
248
277
 
249
278
    def move(self, rel_from, rel_to):
250
279
        """Move the item at rel_from to the location at rel_to"""
288
317
        """
289
318
        raise TransportNotPossible('http does not support lock_write()')
290
319
 
 
320
    def clone(self, offset=None):
 
321
        """Return a new HttpTransportBase with root at self.base + offset
 
322
        For now HttpTransportBase does not actually connect, so just return
 
323
        a new HttpTransportBase object.
 
324
        """
 
325
        if offset is None:
 
326
            return self.__class__(self.base)
 
327
        else:
 
328
            return self.__class__(self.abspath(offset))
291
329
 
292
330
#---------------- test server facilities ----------------
293
331
# TODO: load these only when running tests
352
390
                                                RequestHandlerClass)
353
391
        self.test_case = test_case
354
392
 
355
 
 
356
393
class HttpServer(Server):
357
394
    """A test server for http transports."""
358
395
 
424
461
    def get_bogus_url(self):
425
462
        """See bzrlib.transport.Server.get_bogus_url."""
426
463
        return 'http://jasldkjsalkdjalksjdkljasd'
 
464