/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
                           TransportError, ConnectionError, InvalidURL)
40
40
from bzrlib.branch import Branch
41
41
from bzrlib.trace import mutter
42
 
from bzrlib.transport import Transport, register_transport, Server
 
42
from bzrlib.transport import (
 
43
    get_transport,
 
44
    register_transport,
 
45
    Server,
 
46
    smart,
 
47
    Transport,
 
48
    )
43
49
from bzrlib.transport.http.response import (HttpMultipartRangeResponse,
44
50
                                            HttpRangeResponse)
45
51
from bzrlib.ui import ui_factory
119
125
    return m
120
126
 
121
127
 
122
 
class HttpTransportBase(Transport):
 
128
class HttpTransportBase(Transport, smart.SmartClientMedium):
123
129
    """Base class for http implementations.
124
130
 
125
131
    Does URL parsing, etc, but not any network IO.
169
175
        else:
170
176
            # TODO: Don't call this with an array - no magic interfaces
171
177
            relpath_parts = relpath[:]
172
 
        if len(relpath_parts) > 1:
173
 
            # TODO: Check that the "within branch" part of the
174
 
            # error messages below is relevant in all contexts
175
 
            if relpath_parts[0] == '':
176
 
                raise ValueError("path %r within branch %r seems to be absolute"
177
 
                                 % (relpath, self._path))
178
 
            # read only transports never manipulate directories
179
 
            if self.is_readonly() and relpath_parts[-1] == '':
 
178
        if relpath.startswith('/'):
 
179
            basepath = []
 
180
        else:
 
181
            # Except for the root, no trailing slashes are allowed
 
182
            if len(relpath_parts) > 1 and relpath_parts[-1] == '':
180
183
                raise ValueError("path %r within branch %r seems to be a directory"
181
184
                                 % (relpath, self._path))
182
 
        basepath = self._path.split('/')
183
 
        if len(basepath) > 0 and basepath[-1] == '':
184
 
            basepath = basepath[:-1]
 
185
            basepath = self._path.split('/')
 
186
            if len(basepath) > 0 and basepath[-1] == '':
 
187
                basepath = basepath[:-1]
 
188
 
185
189
        for p in relpath_parts:
186
190
            if p == '..':
187
191
                if len(basepath) == 0:
240
244
        """
241
245
        raise NotImplementedError(self._get)
242
246
 
 
247
    def get_request(self):
 
248
        return SmartClientHTTPMediumRequest(self)
 
249
 
 
250
    def get_smart_medium(self):
 
251
        """See Transport.get_smart_medium.
 
252
 
 
253
        HttpTransportBase directly implements the minimal interface of
 
254
        SmartMediumClient, so this returns self.
 
255
        """
 
256
        return self
 
257
 
243
258
    def readv(self, relpath, offsets):
244
259
        """Get parts of the file at the given relative path.
245
260
 
254
269
            f.seek(start, (start < 0) and 2 or 0)
255
270
            start = f.tell()
256
271
            data = f.read(size)
257
 
            assert len(data) == size
 
272
            if len(data) != size:
 
273
                raise errors.ShortReadvError(relpath, start, size,
 
274
                                             actual=len(data))
258
275
            yield start, data
259
276
 
260
277
    @staticmethod
284
301
 
285
302
        return combined
286
303
 
 
304
    def _post(self, body_bytes):
 
305
        """POST body_bytes to .bzr/smart on this transport.
 
306
        
 
307
        :returns: (response code, response body file-like object).
 
308
        """
 
309
        # TODO: Requiring all the body_bytes to be available at the beginning of
 
310
        # the POST may require large client buffers.  It would be nice to have
 
311
        # an interface that allows streaming via POST when possible (and
 
312
        # degrades to a local buffer when not).
 
313
        raise NotImplementedError(self._post)
 
314
 
287
315
    def put_file(self, relpath, f, mode=None):
288
316
        """Copy the file-like object into the location.
289
317
 
371
399
 
372
400
    def clone(self, offset=None):
373
401
        """Return a new HttpTransportBase with root at self.base + offset
374
 
        For now HttpTransportBase does not actually connect, so just return
375
 
        a new HttpTransportBase object.
 
402
 
 
403
        We leave the daughter classes take advantage of the hint
 
404
        that it's a cloning not a raw creation.
376
405
        """
377
406
        if offset is None:
378
 
            return self.__class__(self.base)
 
407
            return self.__class__(self.base, self)
379
408
        else:
380
 
            return self.__class__(self.abspath(offset))
 
409
            return self.__class__(self.abspath(offset), self)
381
410
 
382
411
    @staticmethod
383
412
    def range_header(ranges, tail_amount):
397
426
 
398
427
        return ','.join(strings)
399
428
 
 
429
    def send_http_smart_request(self, bytes):
 
430
        code, body_filelike = self._post(bytes)
 
431
        assert code == 200, 'unexpected HTTP response code %r' % (code,)
 
432
        return body_filelike
 
433
 
 
434
 
 
435
class SmartClientHTTPMediumRequest(smart.SmartClientMediumRequest):
 
436
    """A SmartClientMediumRequest that works with an HTTP medium."""
 
437
 
 
438
    def __init__(self, medium):
 
439
        smart.SmartClientMediumRequest.__init__(self, medium)
 
440
        self._buffer = ''
 
441
 
 
442
    def _accept_bytes(self, bytes):
 
443
        self._buffer += bytes
 
444
 
 
445
    def _finished_writing(self):
 
446
        data = self._medium.send_http_smart_request(self._buffer)
 
447
        self._response_body = data
 
448
 
 
449
    def _read_bytes(self, count):
 
450
        return self._response_body.read(count)
 
451
        
 
452
    def _finished_reading(self):
 
453
        """See SmartClientMediumRequest._finished_reading."""
 
454
        pass
 
455
        
400
456
 
401
457
#---------------- test server facilities ----------------
402
458
# TODO: load these only when running tests
449
505
        if not self.parse_request(): # An error code has been sent, just exit
450
506
            return
451
507
        mname = 'do_' + self.command
452
 
        if not hasattr(self, mname):
 
508
        if getattr(self, mname, None) is None:
453
509
            self.send_error(501, "Unsupported method (%r)" % self.command)
454
510
            return
455
511
        method = getattr(self, mname)
502
558
        Server.__init__(self)
503
559
        self.request_handler = request_handler
504
560
 
505
 
    def _http_start(self):
506
 
        httpd = None
507
 
        httpd = TestingHTTPServer(('localhost', 0),
 
561
    def _get_httpd(self):
 
562
        return TestingHTTPServer(('localhost', 0),
508
563
                                  self.request_handler,
509
564
                                  self)
 
565
 
 
566
    def _http_start(self):
 
567
        httpd = self._get_httpd()
510
568
        host, port = httpd.socket.getsockname()
511
569
        self._http_base_url = '%s://localhost:%s/' % (self._url_protocol, port)
512
570
        self._http_starting.release()
570
628
        # etc
571
629
        return 'http://127.0.0.1:1/'
572
630
 
 
631
 
 
632
class HTTPServerWithSmarts(HttpServer):
 
633
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
 
634
    trigger a smart server to execute with a transport rooted at the rootdir of
 
635
    the HTTP server.
 
636
    """
 
637
 
 
638
    def __init__(self):
 
639
        HttpServer.__init__(self, SmartRequestHandler)
 
640
 
 
641
 
 
642
class SmartRequestHandler(TestingHTTPRequestHandler):
 
643
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
 
644
 
 
645
    def do_POST(self):
 
646
        """Hand the request off to a smart server instance."""
 
647
        self.send_response(200)
 
648
        self.send_header("Content-type", "application/octet-stream")
 
649
        transport = get_transport(self.server.test_case._home_dir)
 
650
        # TODO: We might like to support streaming responses.  1.0 allows no
 
651
        # Content-length in this case, so for integrity we should perform our
 
652
        # own chunking within the stream.
 
653
        # 1.1 allows chunked responses, and in this case we could chunk using
 
654
        # the HTTP chunking as this will allow HTTP persistence safely, even if
 
655
        # we have to stop early due to error, but we would also have to use the
 
656
        # HTTP trailer facility which may not be widely available.
 
657
        out_buffer = StringIO()
 
658
        smart_protocol_request = smart.SmartServerRequestProtocolOne(
 
659
                transport, out_buffer.write)
 
660
        # if this fails, we should return 400 bad request, but failure is
 
661
        # failure for now - RBC 20060919
 
662
        data_length = int(self.headers['Content-Length'])
 
663
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
 
664
        # feeding the bytes in the http request to the smart_protocol_request,
 
665
        # but for now it's simpler to just feed the bytes directly.
 
666
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
667
        assert smart_protocol_request.next_read_size() == 0, (
 
668
            "not finished reading, but all data sent to protocol.")
 
669
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
 
670
        self.end_headers()
 
671
        self.wfile.write(out_buffer.getvalue())
 
672