~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/HTTPTestUtil.py

  • Committer: Vincent Ladeuil
  • Date: 2007-06-06 13:52:02 UTC
  • mto: (2485.8.44 bzr.connection.sharing)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070606135202-mqhxcv6z57uce434
Fix merge multiple connections. Test suite *not* passing (sftp
refactoring pending but unrelated to merge).

* bzrlib/builtins.py:
(cmd_merge.run): Fix the multiple connections bug by reusing the
tramsport used to check for a bundle and keep all other used
transports in possible_transports.
(_merge_helper): Add a possible_transports parameter for
reuse.

* bzrlib/transport/__init__.py:
(Transport._reuse_for): By default, Transports are not reusable.
(ConnectedTransport._reuse_for): ConnectedTransports are reusable
under certain conditions.
(_urlRE): Fix misleading group name.
(_try_transport_factories): Moved after get_transport (another use
case for moved lines). The do_catching_redirections was
incorrectly inserted between get_transport and
_try_transport_factories.

* bzrlib/tests/test_transport.py:
(TestReusedTransports.test_reuse_same_transport)
(TestReusedTransports.test_don_t_reuse_different_transport): Add
more tests.

* bzrlib/merge.py:
(_get_tree, Merger.set_other): Add a possible_transports parameter
for reuse.

* bzrlib/bzrdir.py:
(BzrDir.open_containing): Add a possible_transports parameter for
reuse.

* bzrlib/branch.py:
(Branch.open_containing): Add a possible_transports parameter for
reuse.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from cStringIO import StringIO
18
18
import errno
 
19
import md5
 
20
from SimpleHTTPServer import SimpleHTTPRequestHandler
19
21
import re
 
22
import sha
20
23
import socket
21
 
import threading
22
24
import time
23
25
import urllib2
24
26
import urlparse
25
27
 
26
 
 
27
 
from bzrlib import (
28
 
    errors,
29
 
    osutils,
30
 
    tests,
 
28
from bzrlib.smart import protocol
 
29
from bzrlib.tests import TestCaseWithTransport
 
30
from bzrlib.tests.HttpServer import (
 
31
    HttpServer,
 
32
    TestingHTTPRequestHandler,
31
33
    )
32
 
from bzrlib.smart import medium, protocol
33
 
from bzrlib.tests import http_server
34
34
from bzrlib.transport import (
35
 
    chroot,
36
35
    get_transport,
37
36
    )
38
37
 
39
38
 
40
 
class HTTPServerWithSmarts(http_server.HttpServer):
 
39
class WallRequestHandler(TestingHTTPRequestHandler):
 
40
    """Whatever request comes in, close the connection"""
 
41
 
 
42
    def handle_one_request(self):
 
43
        """Handle a single HTTP request, by abruptly closing the connection"""
 
44
        self.close_connection = 1
 
45
 
 
46
 
 
47
class BadStatusRequestHandler(TestingHTTPRequestHandler):
 
48
    """Whatever request comes in, returns a bad status"""
 
49
 
 
50
    def parse_request(self):
 
51
        """Fakes handling a single HTTP request, returns a bad status"""
 
52
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
53
        try:
 
54
            self.send_response(0, "Bad status")
 
55
            self.end_headers()
 
56
        except socket.error, e:
 
57
            # We don't want to pollute the test results with
 
58
            # spurious server errors while test succeed. In our
 
59
            # case, it may occur that the test has already read
 
60
            # the 'Bad Status' and closed the socket while we are
 
61
            # still trying to send some headers... So the test is
 
62
            # ok, but if we raise the exception, the output is
 
63
            # dirty. So we don't raise, but we close the
 
64
            # connection, just to be safe :)
 
65
            spurious = [errno.EPIPE,
 
66
                        errno.ECONNRESET,
 
67
                        errno.ECONNABORTED,
 
68
                        ]
 
69
            if (len(e.args) > 0) and (e.args[0] in spurious):
 
70
                self.close_connection = 1
 
71
                pass
 
72
            else:
 
73
                raise
 
74
        return False
 
75
 
 
76
 
 
77
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
 
78
    """Whatever request comes in, returns am invalid status"""
 
79
 
 
80
    def parse_request(self):
 
81
        """Fakes handling a single HTTP request, returns a bad status"""
 
82
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
83
        self.wfile.write("Invalid status line\r\n")
 
84
        return False
 
85
 
 
86
 
 
87
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
 
88
    """Whatever request comes in, returns a bad protocol version"""
 
89
 
 
90
    def parse_request(self):
 
91
        """Fakes handling a single HTTP request, returns a bad status"""
 
92
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
93
        # Returns an invalid protocol version, but curl just
 
94
        # ignores it and those cannot be tested.
 
95
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
96
                                           404,
 
97
                                           'Look at my protocol version'))
 
98
        return False
 
99
 
 
100
 
 
101
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
 
102
    """Whatever request comes in, returns a 403 code"""
 
103
 
 
104
    def parse_request(self):
 
105
        """Handle a single HTTP request, by replying we cannot handle it"""
 
106
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
107
        self.send_error(403)
 
108
        return False
 
109
 
 
110
 
 
111
class HTTPServerWithSmarts(HttpServer):
41
112
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
42
113
    trigger a smart server to execute with a transport rooted at the rootdir of
43
114
    the HTTP server.
44
115
    """
45
116
 
46
 
    def __init__(self, protocol_version=None):
47
 
        http_server.HttpServer.__init__(self, SmartRequestHandler,
48
 
                                        protocol_version=protocol_version)
49
 
 
50
 
 
51
 
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
52
 
    """Extend TestingHTTPRequestHandler to support smart client POSTs.
53
 
    
54
 
    XXX: This duplicates a fair bit of the logic in bzrlib.transport.http.wsgi.
55
 
    """
 
117
    def __init__(self):
 
118
        HttpServer.__init__(self, SmartRequestHandler)
 
119
 
 
120
 
 
121
class SmartRequestHandler(TestingHTTPRequestHandler):
 
122
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
56
123
 
57
124
    def do_POST(self):
58
125
        """Hand the request off to a smart server instance."""
59
 
        backing = get_transport(self.server.test_case_server._home_dir)
60
 
        chroot_server = chroot.ChrootServer(backing)
61
 
        chroot_server.setUp()
62
 
        try:
63
 
            t = get_transport(chroot_server.get_url())
64
 
            self.do_POST_inner(t)
65
 
        finally:
66
 
            chroot_server.tearDown()
67
 
 
68
 
    def do_POST_inner(self, chrooted_transport):
69
126
        self.send_response(200)
70
127
        self.send_header("Content-type", "application/octet-stream")
71
 
        if not self.path.endswith('.bzr/smart'):
72
 
            raise AssertionError(
73
 
                'POST to path not ending in .bzr/smart: %r' % (self.path,))
74
 
        t = chrooted_transport.clone(self.path[:-len('.bzr/smart')])
75
 
        # if this fails, we should return 400 bad request, but failure is
76
 
        # failure for now - RBC 20060919
77
 
        data_length = int(self.headers['Content-Length'])
 
128
        transport = get_transport(self.server.test_case_server._home_dir)
78
129
        # TODO: We might like to support streaming responses.  1.0 allows no
79
130
        # Content-length in this case, so for integrity we should perform our
80
131
        # own chunking within the stream.
82
133
        # the HTTP chunking as this will allow HTTP persistence safely, even if
83
134
        # we have to stop early due to error, but we would also have to use the
84
135
        # HTTP trailer facility which may not be widely available.
85
 
        request_bytes = self.rfile.read(data_length)
86
 
        protocol_factory, unused_bytes = medium._get_protocol_factory_for_bytes(
87
 
            request_bytes)
88
136
        out_buffer = StringIO()
89
 
        smart_protocol_request = protocol_factory(t, out_buffer.write, '/')
 
137
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
 
138
                transport, out_buffer.write)
 
139
        # if this fails, we should return 400 bad request, but failure is
 
140
        # failure for now - RBC 20060919
 
141
        data_length = int(self.headers['Content-Length'])
90
142
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
91
143
        # feeding the bytes in the http request to the smart_protocol_request,
92
144
        # but for now it's simpler to just feed the bytes directly.
93
 
        smart_protocol_request.accept_bytes(unused_bytes)
94
 
        if not (smart_protocol_request.next_read_size() == 0):
95
 
            raise errors.SmartProtocolError(
96
 
                "not finished reading, but all data sent to protocol.")
 
145
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
146
        assert smart_protocol_request.next_read_size() == 0, (
 
147
            "not finished reading, but all data sent to protocol.")
97
148
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
98
149
        self.end_headers()
99
150
        self.wfile.write(out_buffer.getvalue())
100
151
 
101
152
 
102
 
class TestCaseWithWebserver(tests.TestCaseWithTransport):
 
153
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
 
154
    """Always reply to range request as if they were single.
 
155
 
 
156
    Don't be explicit about it, just to annoy the clients.
 
157
    """
 
158
 
 
159
    def get_multiple_ranges(self, file, file_size, ranges):
 
160
        """Answer as if it was a single range request and ignores the rest"""
 
161
        (start, end) = ranges[0]
 
162
        return self.get_single_range(file, file_size, start, end)
 
163
 
 
164
 
 
165
class NoRangeRequestHandler(TestingHTTPRequestHandler):
 
166
    """Ignore range requests without notice"""
 
167
 
 
168
    # Just bypass the range handling done by TestingHTTPRequestHandler
 
169
    do_GET = SimpleHTTPRequestHandler.do_GET
 
170
 
 
171
 
 
172
class TestCaseWithWebserver(TestCaseWithTransport):
103
173
    """A support class that provides readonly urls that are http://.
104
174
 
105
175
    This is done by forcing the readonly server to be an http
108
178
    """
109
179
    def setUp(self):
110
180
        super(TestCaseWithWebserver, self).setUp()
111
 
        self.transport_readonly_server = http_server.HttpServer
 
181
        self.transport_readonly_server = HttpServer
112
182
 
113
183
 
114
184
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
119
189
    """
120
190
    def setUp(self):
121
191
        super(TestCaseWithTwoWebservers, self).setUp()
122
 
        self.transport_secondary_server = http_server.HttpServer
 
192
        self.transport_secondary_server = HttpServer
123
193
        self.__secondary_server = None
124
194
 
125
195
    def create_transport_secondary_server(self):
138
208
        return self.__secondary_server
139
209
 
140
210
 
141
 
class ProxyServer(http_server.HttpServer):
 
211
class ProxyServer(HttpServer):
142
212
    """A proxy test server for http transports."""
143
213
 
144
214
    proxy_requests = True
145
215
 
146
216
 
147
 
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
 
217
class RedirectRequestHandler(TestingHTTPRequestHandler):
148
218
    """Redirect all request to the specified server"""
149
219
 
150
220
    def parse_request(self):
151
221
        """Redirect a single HTTP request to another host"""
152
 
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
 
222
        valid = TestingHTTPRequestHandler.parse_request(self)
153
223
        if valid:
154
224
            tcs = self.server.test_case_server
155
225
            code, target = tcs.is_redirected(self.path)
157
227
                # Redirect as instructed
158
228
                self.send_response(code)
159
229
                self.send_header('Location', target)
160
 
                # We do not send a body
161
 
                self.send_header('Content-Length', '0')
162
230
                self.end_headers()
163
231
                return False # The job is done
164
232
            else:
167
235
        return valid
168
236
 
169
237
 
170
 
class HTTPServerRedirecting(http_server.HttpServer):
 
238
class HTTPServerRedirecting(HttpServer):
171
239
    """An HttpServer redirecting to another server """
172
240
 
173
 
    def __init__(self, request_handler=RedirectRequestHandler,
174
 
                 protocol_version=None):
175
 
        http_server.HttpServer.__init__(self, request_handler,
176
 
                                        protocol_version=protocol_version)
 
241
    def __init__(self, request_handler=RedirectRequestHandler):
 
242
        HttpServer.__init__(self, request_handler)
177
243
        # redirections is a list of tuples (source, target, code)
178
244
        # - source is a regexp for the paths requested
179
245
        # - target is a replacement for re.sub describing where
231
297
       self.old_server = self.get_secondary_server()
232
298
 
233
299
 
234
 
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
 
300
class AuthRequestHandler(TestingHTTPRequestHandler):
235
301
    """Requires an authentication to process requests.
236
302
 
237
303
    This is intended to be used with a server that always and
246
312
 
247
313
    def do_GET(self):
248
314
        if self.authorized():
249
 
            return http_server.TestingHTTPRequestHandler.do_GET(self)
 
315
            return TestingHTTPRequestHandler.do_GET(self)
250
316
        else:
251
317
            # Note that we must update test_case_server *before*
252
318
            # sending the error or the client may try to read it
255
321
            tcs.auth_required_errors += 1
256
322
            self.send_response(tcs.auth_error_code)
257
323
            self.send_header_auth_reqed()
258
 
            # We do not send a body
259
 
            self.send_header('Content-Length', '0')
260
324
            self.end_headers()
261
325
            return
262
326
 
 
327
        TestingHTTPRequestHandler.do_GET(self)
 
328
 
263
329
 
264
330
class BasicAuthRequestHandler(AuthRequestHandler):
265
331
    """Implements the basic authentication of a request"""
314
380
    def send_header_auth_reqed(self):
315
381
        tcs = self.server.test_case_server
316
382
        header = 'Digest realm="%s", ' % tcs.auth_realm
317
 
        header += 'nonce="%s", algorithm="%s", qop="auth"' % (tcs.auth_nonce,
318
 
                                                              'MD5')
 
383
        header += 'nonce="%s", algorithm=%s, qop=auth' % (tcs.auth_nonce, 'MD5')
319
384
        self.send_header(tcs.auth_header_sent,header)
320
385
 
321
386
 
322
 
class AuthServer(http_server.HttpServer):
 
387
class AuthServer(HttpServer):
323
388
    """Extends HttpServer with a dictionary of passwords.
324
389
 
325
390
    This is used as a base class for various schemes which should
336
401
    auth_error_code = None
337
402
    auth_realm = "Thou should not pass"
338
403
 
339
 
    def __init__(self, request_handler, auth_scheme,
340
 
                 protocol_version=None):
341
 
        http_server.HttpServer.__init__(self, request_handler,
342
 
                                        protocol_version=protocol_version)
 
404
    def __init__(self, request_handler, auth_scheme):
 
405
        HttpServer.__init__(self, request_handler)
343
406
        self.auth_scheme = auth_scheme
344
407
        self.password_of = {}
345
408
        self.auth_required_errors = 0
359
422
 
360
423
 
361
424
# FIXME: There is some code duplication with
362
 
# _urllib2_wrappers.py.DigestAuthHandler. If that duplication
 
425
# _urllib2_wrappers.py.DigestAuthHandler. If that duplciation
363
426
# grows, it may require a refactoring. Also, we don't implement
364
427
# SHA algorithm nor MD5-sess here, but that does not seem worth
365
428
# it.
368
431
 
369
432
    auth_nonce = 'now!'
370
433
 
371
 
    def __init__(self, request_handler, auth_scheme,
372
 
                 protocol_version=None):
373
 
        AuthServer.__init__(self, request_handler, auth_scheme,
374
 
                            protocol_version=protocol_version)
 
434
    def __init__(self, request_handler, auth_scheme):
 
435
        AuthServer.__init__(self, request_handler, auth_scheme)
375
436
 
376
437
    def digest_authorized(self, auth, command):
377
438
        nonce = auth['nonce']
397
458
        A1 = '%s:%s:%s' % (user, realm, password)
398
459
        A2 = '%s:%s' % (command, auth['uri'])
399
460
 
400
 
        H = lambda x: osutils.md5(x).hexdigest()
 
461
        H = lambda x: md5.new(x).hexdigest()
401
462
        KD = lambda secret, data: H("%s:%s" % (secret, data))
402
463
 
403
464
        nonce_count = int(auth['nc'], 16)
432
493
class HTTPBasicAuthServer(HTTPAuthServer):
433
494
    """An HTTP server requiring basic authentication"""
434
495
 
435
 
    def __init__(self, protocol_version=None):
436
 
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
437
 
                                protocol_version=protocol_version)
 
496
    def __init__(self):
 
497
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
438
498
        self.init_http_auth()
439
499
 
440
500
 
441
501
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
442
502
    """An HTTP server requiring digest authentication"""
443
503
 
444
 
    def __init__(self, protocol_version=None):
445
 
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
446
 
                                  protocol_version=protocol_version)
 
504
    def __init__(self):
 
505
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
447
506
        self.init_http_auth()
448
507
 
449
508
 
450
509
class ProxyBasicAuthServer(ProxyAuthServer):
451
510
    """A proxy server requiring basic authentication"""
452
511
 
453
 
    def __init__(self, protocol_version=None):
454
 
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
455
 
                                 protocol_version=protocol_version)
 
512
    def __init__(self):
 
513
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
456
514
        self.init_proxy_auth()
457
515
 
458
516
 
459
517
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
460
518
    """A proxy server requiring basic authentication"""
461
519
 
462
 
    def __init__(self, protocol_version=None):
463
 
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
464
 
                                 protocol_version=protocol_version)
 
520
    def __init__(self):
 
521
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
465
522
        self.init_proxy_auth()
466
523
 
467
524