~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/HTTPTestUtil.py

  • Committer: Ian Clatworthy
  • Date: 2007-12-17 01:45:32 UTC
  • mto: (3119.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3120.
  • Revision ID: ian.clatworthy@internode.on.net-20071217014532-dmbv2mm72nzq0ai6
follow-up tweaks to bzr.dev integration

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
from cStringIO import StringIO
18
18
import errno
19
19
import md5
 
20
from SimpleHTTPServer import SimpleHTTPRequestHandler
20
21
import re
21
22
import sha
22
23
import socket
23
 
import threading
24
24
import time
25
25
import urllib2
26
26
import urlparse
27
27
 
28
 
 
29
 
from bzrlib import (
30
 
    tests,
31
 
    transport,
32
 
    )
33
28
from bzrlib.smart import protocol
34
 
from bzrlib.tests import http_server
35
 
 
36
 
 
37
 
class HTTPServerWithSmarts(http_server.HttpServer):
 
29
from bzrlib.tests import TestCaseWithTransport
 
30
from bzrlib.tests.HttpServer import (
 
31
    HttpServer,
 
32
    TestingHTTPRequestHandler,
 
33
    )
 
34
from bzrlib.transport import (
 
35
    get_transport,
 
36
    )
 
37
 
 
38
 
 
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):
38
112
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
39
113
    trigger a smart server to execute with a transport rooted at the rootdir of
40
114
    the HTTP server.
41
115
    """
42
116
 
43
 
    def __init__(self, protocol_version=None):
44
 
        http_server.HttpServer.__init__(self, SmartRequestHandler,
45
 
                                        protocol_version=protocol_version)
46
 
 
47
 
 
48
 
class SmartRequestHandler(http_server.TestingHTTPRequestHandler):
 
117
    def __init__(self):
 
118
        HttpServer.__init__(self, SmartRequestHandler)
 
119
 
 
120
 
 
121
class SmartRequestHandler(TestingHTTPRequestHandler):
49
122
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
50
123
 
51
124
    def do_POST(self):
52
125
        """Hand the request off to a smart server instance."""
53
126
        self.send_response(200)
54
127
        self.send_header("Content-type", "application/octet-stream")
55
 
        t = transport.get_transport(self.server.test_case_server._home_dir)
 
128
        transport = get_transport(self.server.test_case_server._home_dir)
56
129
        # TODO: We might like to support streaming responses.  1.0 allows no
57
130
        # Content-length in this case, so for integrity we should perform our
58
131
        # own chunking within the stream.
62
135
        # HTTP trailer facility which may not be widely available.
63
136
        out_buffer = StringIO()
64
137
        smart_protocol_request = protocol.SmartServerRequestProtocolOne(
65
 
                t, out_buffer.write)
 
138
                transport, out_buffer.write)
66
139
        # if this fails, we should return 400 bad request, but failure is
67
140
        # failure for now - RBC 20060919
68
141
        data_length = int(self.headers['Content-Length'])
77
150
        self.wfile.write(out_buffer.getvalue())
78
151
 
79
152
 
80
 
class TestCaseWithWebserver(tests.TestCaseWithTransport):
 
153
class LimitedRangeRequestHandler(TestingHTTPRequestHandler):
 
154
    """Errors out when range specifiers exceed the limit"""
 
155
 
 
156
    def get_multiple_ranges(self, file, file_size, ranges):
 
157
        """Refuses the multiple ranges request"""
 
158
        tcs = self.server.test_case_server
 
159
        if tcs.range_limit is not None and len(ranges) > tcs.range_limit:
 
160
            file.close()
 
161
            # Emulate apache behavior
 
162
            self.send_error(400, "Bad Request")
 
163
            return
 
164
        return TestingHTTPRequestHandler.get_multiple_ranges(self, file,
 
165
                                                             file_size, ranges)
 
166
 
 
167
 
 
168
class LimitedRangeHTTPServer(HttpServer):
 
169
    """An HttpServer erroring out on requests with too much range specifiers"""
 
170
 
 
171
    def __init__(self, request_handler=LimitedRangeRequestHandler,
 
172
                 range_limit=None):
 
173
        HttpServer.__init__(self, request_handler)
 
174
        self.range_limit = range_limit
 
175
 
 
176
 
 
177
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
 
178
    """Always reply to range request as if they were single.
 
179
 
 
180
    Don't be explicit about it, just to annoy the clients.
 
181
    """
 
182
 
 
183
    def get_multiple_ranges(self, file, file_size, ranges):
 
184
        """Answer as if it was a single range request and ignores the rest"""
 
185
        (start, end) = ranges[0]
 
186
        return self.get_single_range(file, file_size, start, end)
 
187
 
 
188
 
 
189
class SingleOnlyRangeRequestHandler(TestingHTTPRequestHandler):
 
190
    """Only reply to simple range requests, errors out on multiple"""
 
191
 
 
192
    def get_multiple_ranges(self, file, file_size, ranges):
 
193
        """Refuses the multiple ranges request"""
 
194
        if len(ranges) > 1:
 
195
            file.close()
 
196
            self.send_error(416, "Requested range not satisfiable")
 
197
            return
 
198
        (start, end) = ranges[0]
 
199
        return self.get_single_range(file, file_size, start, end)
 
200
 
 
201
 
 
202
class NoRangeRequestHandler(TestingHTTPRequestHandler):
 
203
    """Ignore range requests without notice"""
 
204
 
 
205
    def do_GET(self):
 
206
        # Update the statistics
 
207
        self.server.test_case_server.GET_request_nb += 1
 
208
        # Just bypass the range handling done by TestingHTTPRequestHandler
 
209
        return SimpleHTTPRequestHandler.do_GET(self)
 
210
 
 
211
 
 
212
class TestCaseWithWebserver(TestCaseWithTransport):
81
213
    """A support class that provides readonly urls that are http://.
82
214
 
83
215
    This is done by forcing the readonly server to be an http
86
218
    """
87
219
    def setUp(self):
88
220
        super(TestCaseWithWebserver, self).setUp()
89
 
        self.transport_readonly_server = http_server.HttpServer
 
221
        self.transport_readonly_server = HttpServer
90
222
 
91
223
 
92
224
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
97
229
    """
98
230
    def setUp(self):
99
231
        super(TestCaseWithTwoWebservers, self).setUp()
100
 
        self.transport_secondary_server = http_server.HttpServer
 
232
        self.transport_secondary_server = HttpServer
101
233
        self.__secondary_server = None
102
234
 
103
235
    def create_transport_secondary_server(self):
116
248
        return self.__secondary_server
117
249
 
118
250
 
119
 
class ProxyServer(http_server.HttpServer):
 
251
class ProxyServer(HttpServer):
120
252
    """A proxy test server for http transports."""
121
253
 
122
254
    proxy_requests = True
123
255
 
124
256
 
125
 
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
 
257
class RedirectRequestHandler(TestingHTTPRequestHandler):
126
258
    """Redirect all request to the specified server"""
127
259
 
128
260
    def parse_request(self):
129
261
        """Redirect a single HTTP request to another host"""
130
 
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
 
262
        valid = TestingHTTPRequestHandler.parse_request(self)
131
263
        if valid:
132
264
            tcs = self.server.test_case_server
133
265
            code, target = tcs.is_redirected(self.path)
135
267
                # Redirect as instructed
136
268
                self.send_response(code)
137
269
                self.send_header('Location', target)
138
 
                # We do not send a body
139
 
                self.send_header('Content-Length', '0')
140
270
                self.end_headers()
141
271
                return False # The job is done
142
272
            else:
145
275
        return valid
146
276
 
147
277
 
148
 
class HTTPServerRedirecting(http_server.HttpServer):
 
278
class HTTPServerRedirecting(HttpServer):
149
279
    """An HttpServer redirecting to another server """
150
280
 
151
 
    def __init__(self, request_handler=RedirectRequestHandler,
152
 
                 protocol_version=None):
153
 
        http_server.HttpServer.__init__(self, request_handler,
154
 
                                        protocol_version=protocol_version)
 
281
    def __init__(self, request_handler=RedirectRequestHandler):
 
282
        HttpServer.__init__(self, request_handler)
155
283
        # redirections is a list of tuples (source, target, code)
156
284
        # - source is a regexp for the paths requested
157
285
        # - target is a replacement for re.sub describing where
209
337
       self.old_server = self.get_secondary_server()
210
338
 
211
339
 
212
 
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
 
340
class AuthRequestHandler(TestingHTTPRequestHandler):
213
341
    """Requires an authentication to process requests.
214
342
 
215
343
    This is intended to be used with a server that always and
224
352
 
225
353
    def do_GET(self):
226
354
        if self.authorized():
227
 
            return http_server.TestingHTTPRequestHandler.do_GET(self)
 
355
            return TestingHTTPRequestHandler.do_GET(self)
228
356
        else:
229
357
            # Note that we must update test_case_server *before*
230
358
            # sending the error or the client may try to read it
233
361
            tcs.auth_required_errors += 1
234
362
            self.send_response(tcs.auth_error_code)
235
363
            self.send_header_auth_reqed()
236
 
            # We do not send a body
237
 
            self.send_header('Content-Length', '0')
238
364
            self.end_headers()
239
365
            return
240
366
 
297
423
        self.send_header(tcs.auth_header_sent,header)
298
424
 
299
425
 
300
 
class AuthServer(http_server.HttpServer):
 
426
class AuthServer(HttpServer):
301
427
    """Extends HttpServer with a dictionary of passwords.
302
428
 
303
429
    This is used as a base class for various schemes which should
314
440
    auth_error_code = None
315
441
    auth_realm = "Thou should not pass"
316
442
 
317
 
    def __init__(self, request_handler, auth_scheme,
318
 
                 protocol_version=None):
319
 
        http_server.HttpServer.__init__(self, request_handler,
320
 
                                        protocol_version=protocol_version)
 
443
    def __init__(self, request_handler, auth_scheme):
 
444
        HttpServer.__init__(self, request_handler)
321
445
        self.auth_scheme = auth_scheme
322
446
        self.password_of = {}
323
447
        self.auth_required_errors = 0
346
470
 
347
471
    auth_nonce = 'now!'
348
472
 
349
 
    def __init__(self, request_handler, auth_scheme,
350
 
                 protocol_version=None):
351
 
        AuthServer.__init__(self, request_handler, auth_scheme,
352
 
                            protocol_version=protocol_version)
 
473
    def __init__(self, request_handler, auth_scheme):
 
474
        AuthServer.__init__(self, request_handler, auth_scheme)
353
475
 
354
476
    def digest_authorized(self, auth, command):
355
477
        nonce = auth['nonce']
410
532
class HTTPBasicAuthServer(HTTPAuthServer):
411
533
    """An HTTP server requiring basic authentication"""
412
534
 
413
 
    def __init__(self, protocol_version=None):
414
 
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
415
 
                                protocol_version=protocol_version)
 
535
    def __init__(self):
 
536
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
416
537
        self.init_http_auth()
417
538
 
418
539
 
419
540
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
420
541
    """An HTTP server requiring digest authentication"""
421
542
 
422
 
    def __init__(self, protocol_version=None):
423
 
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
424
 
                                  protocol_version=protocol_version)
 
543
    def __init__(self):
 
544
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
425
545
        self.init_http_auth()
426
546
 
427
547
 
428
548
class ProxyBasicAuthServer(ProxyAuthServer):
429
549
    """A proxy server requiring basic authentication"""
430
550
 
431
 
    def __init__(self, protocol_version=None):
432
 
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
433
 
                                 protocol_version=protocol_version)
 
551
    def __init__(self):
 
552
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
434
553
        self.init_proxy_auth()
435
554
 
436
555
 
437
556
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
438
557
    """A proxy server requiring basic authentication"""
439
558
 
440
 
    def __init__(self, protocol_version=None):
441
 
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
442
 
                                 protocol_version=protocol_version)
 
559
    def __init__(self):
 
560
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
443
561
        self.init_proxy_auth()
444
562
 
445
563