~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-01 08:02:42 UTC
  • mfrom: (5390.3.3 faster-revert-593560)
  • Revision ID: pqm@pqm.ubuntu.com-20100901080242-esg62ody4frwmy66
(spiv) Avoid repeatedly calling self.target.all_file_ids() in
 InterTree.iter_changes. (Andrew Bennetts)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
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
21
21
responses.
22
22
"""
23
23
 
24
 
from __future__ import absolute_import
25
24
 
26
 
import os
27
25
import httplib
28
26
from cStringIO import StringIO
29
27
import rfc822
30
28
 
31
29
from bzrlib import (
32
30
    errors,
 
31
    trace,
33
32
    osutils,
34
33
    )
35
34
 
36
35
 
37
 
class ResponseFile(object):
38
 
    """A wrapper around the http socket containing the result of a GET request.
39
 
 
40
 
    Only read() and seek() (forward) are supported.
41
 
 
42
 
    """
43
 
    def __init__(self, path, infile):
44
 
        """Constructor.
45
 
 
46
 
        :param path: File url, for error reports.
47
 
 
48
 
        :param infile: File-like socket set at body start.
49
 
        """
50
 
        self._path = path
51
 
        self._file = infile
52
 
        self._pos = 0
53
 
 
54
 
    def close(self):
55
 
        """Close this file.
56
 
 
57
 
        Dummy implementation for consistency with the 'file' API.
58
 
        """
59
 
 
60
 
    def read(self, size=-1):
61
 
        """Read size bytes from the current position in the file.
62
 
 
63
 
        :param size:  The number of bytes to read.  Leave unspecified or pass
64
 
            -1 to read to EOF.
65
 
        """
66
 
        data =  self._file.read(size)
67
 
        self._pos += len(data)
68
 
        return data
69
 
 
70
 
    def readline(self):
71
 
        data = self._file.readline()
72
 
        self._pos += len(data)
73
 
        return data
74
 
 
75
 
    def __iter__(self):
76
 
        while True:
77
 
            line = self.readline()
78
 
            if not line:
79
 
                return
80
 
            yield line
81
 
 
82
 
    def tell(self):
83
 
        return self._pos
84
 
 
85
 
    def seek(self, offset, whence=os.SEEK_SET):
86
 
        if whence == os.SEEK_SET:
87
 
            if offset < self._pos:
88
 
                raise AssertionError(
89
 
                    "Can't seek backwards, pos: %s, offset: %s"
90
 
                    % (self._pos, offset))
91
 
            to_discard = offset - self._pos
92
 
        elif whence == os.SEEK_CUR:
93
 
            to_discard = offset
94
 
        else:
95
 
            raise AssertionError("Can't seek backwards")
96
 
        if to_discard:
97
 
            # Just discard the unwanted bytes
98
 
            self.read(to_discard)
99
 
 
100
36
# A RangeFile expects the following grammar (simplified to outline the
101
37
# assumptions we rely upon).
102
38
 
103
 
# file: single_range
 
39
# file: whole_file
 
40
#     | single_range
104
41
#     | multiple_range
105
42
 
 
43
# whole_file: [content_length_header] data
 
44
 
106
45
# single_range: content_range_header data
107
46
 
108
47
# multiple_range: boundary_header boundary (content_range_header data boundary)+
109
48
 
110
 
class RangeFile(ResponseFile):
 
49
class RangeFile(object):
111
50
    """File-like object that allow access to partial available data.
112
51
 
113
52
    All accesses should happen sequentially since the acquisition occurs during
132
71
        """Constructor.
133
72
 
134
73
        :param path: File url, for error reports.
135
 
 
136
74
        :param infile: File-like socket set at body start.
137
75
        """
138
 
        super(RangeFile, self).__init__(path, infile)
 
76
        self._path = path
 
77
        self._file = infile
139
78
        self._boundary = None
140
79
        # When using multi parts response, this will be set with the headers
141
80
        # associated with the range currently read.
170
109
            # To be on the safe side we allow it before any boundary line
171
110
            boundary_line = self._file.readline()
172
111
 
173
 
        if boundary_line == '':
174
 
            # A timeout in the proxy server caused the response to end early.
175
 
            # See launchpad bug 198646.
176
 
            raise errors.HttpBoundaryMissing(
177
 
                self._path,
178
 
                self._boundary)
179
 
 
180
112
        if boundary_line != '--' + self._boundary + '\r\n':
181
113
            # rfc822.unquote() incorrectly unquotes strings enclosed in <>
182
114
            # IIS 6 and 7 incorrectly wrap boundary strings in <>
289
221
                    % (size, self._start, self._size))
290
222
 
291
223
        # read data from file
292
 
        buf = StringIO()
 
224
        buffer = StringIO()
293
225
        limited = size
294
226
        if self._size > 0:
295
227
            # Don't read past the range definition
296
228
            limited = self._start + self._size - self._pos
297
229
            if size >= 0:
298
230
                limited = min(limited, size)
299
 
        osutils.pumpfile(self._file, buf, limited, self._max_read_size)
300
 
        data = buf.getvalue()
 
231
        osutils.pumpfile(self._file, buffer, limited, self._max_read_size)
 
232
        data = buffer.getvalue()
301
233
 
302
234
        # Update _pos respecting the data effectively read
303
235
        self._pos += len(data)
359
291
    :return: A file-like object that can seek()+read() the
360
292
             ranges indicated by the headers.
361
293
    """
 
294
    rfile = RangeFile(url, data)
362
295
    if code == 200:
363
296
        # A whole file
364
 
        rfile = ResponseFile(url, data)
 
297
        size = msg.getheader('content-length', None)
 
298
        if size is None:
 
299
            size = -1
 
300
        else:
 
301
            size = int(size)
 
302
        rfile.set_range(0, size)
365
303
    elif code == 206:
366
 
        rfile = RangeFile(url, data)
367
304
        content_type = msg.getheader('content-type', None)
368
305
        if content_type is None:
369
306
            # When there is no content-type header we treat the response as