~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/HTTPTestUtil.py

  • Committer: Daniel Watkins
  • Date: 2007-11-17 17:22:08 UTC
  • mto: This revision was merged to the branch mainline in revision 3013.
  • Revision ID: d.m.watkins@warwick.ac.uk-20071117172208-m47tdnnatvm2ir2u
Modified fix as suggested by John on-list.

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
    def do_GET(self):
 
168
        tcs = self.server.test_case_server
 
169
        tcs.GET_request_nb += 1
 
170
        return TestingHTTPRequestHandler.do_GET(self)
 
171
 
 
172
 
 
173
class LimitedRangeHTTPServer(HttpServer):
 
174
    """An HttpServer erroring out on requests with too much range specifiers"""
 
175
 
 
176
    def __init__(self, request_handler=LimitedRangeRequestHandler,
 
177
                 range_limit=None):
 
178
        HttpServer.__init__(self, request_handler)
 
179
        self.range_limit = range_limit
 
180
        self.GET_request_nb = 0
 
181
 
 
182
 
 
183
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
 
184
    """Always reply to range request as if they were single.
 
185
 
 
186
    Don't be explicit about it, just to annoy the clients.
 
187
    """
 
188
 
 
189
    def get_multiple_ranges(self, file, file_size, ranges):
 
190
        """Answer as if it was a single range request and ignores the rest"""
 
191
        (start, end) = ranges[0]
 
192
        return self.get_single_range(file, file_size, start, end)
 
193
 
 
194
 
 
195
class SingleOnlyRangeRequestHandler(TestingHTTPRequestHandler):
 
196
    """Only reply to simple range requests, errors out on multiple"""
 
197
 
 
198
    def get_multiple_ranges(self, file, file_size, ranges):
 
199
        """Refuses the multiple ranges request"""
 
200
        if len(ranges) > 1:
 
201
            file.close()
 
202
            self.send_error(416, "Requested range not satisfiable")
 
203
            return
 
204
        (start, end) = ranges[0]
 
205
        return self.get_single_range(file, file_size, start, end)
 
206
 
 
207
 
 
208
class NoRangeRequestHandler(TestingHTTPRequestHandler):
 
209
    """Ignore range requests without notice"""
 
210
 
 
211
    # Just bypass the range handling done by TestingHTTPRequestHandler
 
212
    do_GET = SimpleHTTPRequestHandler.do_GET
 
213
 
 
214
 
 
215
class TestCaseWithWebserver(TestCaseWithTransport):
81
216
    """A support class that provides readonly urls that are http://.
82
217
 
83
218
    This is done by forcing the readonly server to be an http
86
221
    """
87
222
    def setUp(self):
88
223
        super(TestCaseWithWebserver, self).setUp()
89
 
        self.transport_readonly_server = http_server.HttpServer
 
224
        self.transport_readonly_server = HttpServer
90
225
 
91
226
 
92
227
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
97
232
    """
98
233
    def setUp(self):
99
234
        super(TestCaseWithTwoWebservers, self).setUp()
100
 
        self.transport_secondary_server = http_server.HttpServer
 
235
        self.transport_secondary_server = HttpServer
101
236
        self.__secondary_server = None
102
237
 
103
238
    def create_transport_secondary_server(self):
116
251
        return self.__secondary_server
117
252
 
118
253
 
119
 
class ProxyServer(http_server.HttpServer):
 
254
class ProxyServer(HttpServer):
120
255
    """A proxy test server for http transports."""
121
256
 
122
257
    proxy_requests = True
123
258
 
124
259
 
125
 
class RedirectRequestHandler(http_server.TestingHTTPRequestHandler):
 
260
class RedirectRequestHandler(TestingHTTPRequestHandler):
126
261
    """Redirect all request to the specified server"""
127
262
 
128
263
    def parse_request(self):
129
264
        """Redirect a single HTTP request to another host"""
130
 
        valid = http_server.TestingHTTPRequestHandler.parse_request(self)
 
265
        valid = TestingHTTPRequestHandler.parse_request(self)
131
266
        if valid:
132
267
            tcs = self.server.test_case_server
133
268
            code, target = tcs.is_redirected(self.path)
135
270
                # Redirect as instructed
136
271
                self.send_response(code)
137
272
                self.send_header('Location', target)
138
 
                # We do not send a body
139
 
                self.send_header('Content-Length', '0')
140
273
                self.end_headers()
141
274
                return False # The job is done
142
275
            else:
145
278
        return valid
146
279
 
147
280
 
148
 
class HTTPServerRedirecting(http_server.HttpServer):
 
281
class HTTPServerRedirecting(HttpServer):
149
282
    """An HttpServer redirecting to another server """
150
283
 
151
 
    def __init__(self, request_handler=RedirectRequestHandler,
152
 
                 protocol_version=None):
153
 
        http_server.HttpServer.__init__(self, request_handler,
154
 
                                        protocol_version=protocol_version)
 
284
    def __init__(self, request_handler=RedirectRequestHandler):
 
285
        HttpServer.__init__(self, request_handler)
155
286
        # redirections is a list of tuples (source, target, code)
156
287
        # - source is a regexp for the paths requested
157
288
        # - target is a replacement for re.sub describing where
209
340
       self.old_server = self.get_secondary_server()
210
341
 
211
342
 
212
 
class AuthRequestHandler(http_server.TestingHTTPRequestHandler):
 
343
class AuthRequestHandler(TestingHTTPRequestHandler):
213
344
    """Requires an authentication to process requests.
214
345
 
215
346
    This is intended to be used with a server that always and
224
355
 
225
356
    def do_GET(self):
226
357
        if self.authorized():
227
 
            return http_server.TestingHTTPRequestHandler.do_GET(self)
 
358
            return TestingHTTPRequestHandler.do_GET(self)
228
359
        else:
229
360
            # Note that we must update test_case_server *before*
230
361
            # sending the error or the client may try to read it
233
364
            tcs.auth_required_errors += 1
234
365
            self.send_response(tcs.auth_error_code)
235
366
            self.send_header_auth_reqed()
236
 
            # We do not send a body
237
 
            self.send_header('Content-Length', '0')
238
367
            self.end_headers()
239
368
            return
240
369
 
297
426
        self.send_header(tcs.auth_header_sent,header)
298
427
 
299
428
 
300
 
class AuthServer(http_server.HttpServer):
 
429
class AuthServer(HttpServer):
301
430
    """Extends HttpServer with a dictionary of passwords.
302
431
 
303
432
    This is used as a base class for various schemes which should
314
443
    auth_error_code = None
315
444
    auth_realm = "Thou should not pass"
316
445
 
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)
 
446
    def __init__(self, request_handler, auth_scheme):
 
447
        HttpServer.__init__(self, request_handler)
321
448
        self.auth_scheme = auth_scheme
322
449
        self.password_of = {}
323
450
        self.auth_required_errors = 0
346
473
 
347
474
    auth_nonce = 'now!'
348
475
 
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)
 
476
    def __init__(self, request_handler, auth_scheme):
 
477
        AuthServer.__init__(self, request_handler, auth_scheme)
353
478
 
354
479
    def digest_authorized(self, auth, command):
355
480
        nonce = auth['nonce']
410
535
class HTTPBasicAuthServer(HTTPAuthServer):
411
536
    """An HTTP server requiring basic authentication"""
412
537
 
413
 
    def __init__(self, protocol_version=None):
414
 
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
415
 
                                protocol_version=protocol_version)
 
538
    def __init__(self):
 
539
        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
416
540
        self.init_http_auth()
417
541
 
418
542
 
419
543
class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
420
544
    """An HTTP server requiring digest authentication"""
421
545
 
422
 
    def __init__(self, protocol_version=None):
423
 
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
424
 
                                  protocol_version=protocol_version)
 
546
    def __init__(self):
 
547
        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
425
548
        self.init_http_auth()
426
549
 
427
550
 
428
551
class ProxyBasicAuthServer(ProxyAuthServer):
429
552
    """A proxy server requiring basic authentication"""
430
553
 
431
 
    def __init__(self, protocol_version=None):
432
 
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic',
433
 
                                 protocol_version=protocol_version)
 
554
    def __init__(self):
 
555
        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
434
556
        self.init_proxy_auth()
435
557
 
436
558
 
437
559
class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
438
560
    """A proxy server requiring basic authentication"""
439
561
 
440
 
    def __init__(self, protocol_version=None):
441
 
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest',
442
 
                                 protocol_version=protocol_version)
 
562
    def __init__(self):
 
563
        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
443
564
        self.init_proxy_auth()
444
565
 
445
566