/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/tests/HTTPTestUtil.py

Merge from bzr.dev, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
from cStringIO import StringIO
 
18
import errno
 
19
from SimpleHTTPServer import SimpleHTTPRequestHandler
 
20
import re
 
21
import socket
 
22
import urlparse
 
23
 
 
24
from bzrlib import smart
 
25
import bzrlib.smart.request
 
26
from bzrlib.tests import TestCaseWithTransport
 
27
from bzrlib.tests.HttpServer import (
 
28
    HttpServer,
 
29
    TestingHTTPRequestHandler,
 
30
    )
 
31
from bzrlib.transport import (
 
32
    get_transport,
 
33
    )
 
34
from bzrlib.smart import protocol
 
35
 
 
36
 
 
37
class WallRequestHandler(TestingHTTPRequestHandler):
 
38
    """Whatever request comes in, close the connection"""
 
39
 
 
40
    def handle_one_request(self):
 
41
        """Handle a single HTTP request, by abruptly closing the connection"""
 
42
        self.close_connection = 1
 
43
 
 
44
 
 
45
class BadStatusRequestHandler(TestingHTTPRequestHandler):
 
46
    """Whatever request comes in, returns a bad status"""
 
47
 
 
48
    def parse_request(self):
 
49
        """Fakes handling a single HTTP request, returns a bad status"""
 
50
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
51
        try:
 
52
            self.send_response(0, "Bad status")
 
53
            self.end_headers()
 
54
        except socket.error, e:
 
55
            # We don't want to pollute the test results with
 
56
            # spurious server errors while test succeed. In our
 
57
            # case, it may occur that the test has already read
 
58
            # the 'Bad Status' and closed the socket while we are
 
59
            # still trying to send some headers... So the test is
 
60
            # ok, but if we raise the exception, the output is
 
61
            # dirty. So we don't raise, but we close the
 
62
            # connection, just to be safe :)
 
63
            spurious = [errno.EPIPE,
 
64
                        errno.ECONNRESET,
 
65
                        errno.ECONNABORTED,
 
66
                        ]
 
67
            if (len(e.args) > 0) and (e.args[0] in spurious):
 
68
                self.close_connection = 1
 
69
                pass
 
70
            else:
 
71
                raise
 
72
        return False
 
73
 
 
74
 
 
75
class InvalidStatusRequestHandler(TestingHTTPRequestHandler):
 
76
    """Whatever request comes in, returns am invalid status"""
 
77
 
 
78
    def parse_request(self):
 
79
        """Fakes handling a single HTTP request, returns a bad status"""
 
80
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
81
        self.wfile.write("Invalid status line\r\n")
 
82
        return False
 
83
 
 
84
 
 
85
class BadProtocolRequestHandler(TestingHTTPRequestHandler):
 
86
    """Whatever request comes in, returns a bad protocol version"""
 
87
 
 
88
    def parse_request(self):
 
89
        """Fakes handling a single HTTP request, returns a bad status"""
 
90
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
91
        # Returns an invalid protocol version, but curl just
 
92
        # ignores it and those cannot be tested.
 
93
        self.wfile.write("%s %d %s\r\n" % ('HTTP/0.0',
 
94
                                           404,
 
95
                                           'Look at my protocol version'))
 
96
        return False
 
97
 
 
98
 
 
99
class ForbiddenRequestHandler(TestingHTTPRequestHandler):
 
100
    """Whatever request comes in, returns a 403 code"""
 
101
 
 
102
    def parse_request(self):
 
103
        """Handle a single HTTP request, by replying we cannot handle it"""
 
104
        ignored = TestingHTTPRequestHandler.parse_request(self)
 
105
        self.send_error(403)
 
106
        return False
 
107
 
 
108
 
 
109
class HTTPServerWithSmarts(HttpServer):
 
110
    """HTTPServerWithSmarts extends the HttpServer with POST methods that will
 
111
    trigger a smart server to execute with a transport rooted at the rootdir of
 
112
    the HTTP server.
 
113
    """
 
114
 
 
115
    def __init__(self):
 
116
        HttpServer.__init__(self, SmartRequestHandler)
 
117
 
 
118
 
 
119
class SmartRequestHandler(TestingHTTPRequestHandler):
 
120
    """Extend TestingHTTPRequestHandler to support smart client POSTs."""
 
121
 
 
122
    def do_POST(self):
 
123
        """Hand the request off to a smart server instance."""
 
124
        self.send_response(200)
 
125
        self.send_header("Content-type", "application/octet-stream")
 
126
        transport = get_transport(self.server.test_case_server._home_dir)
 
127
        # TODO: We might like to support streaming responses.  1.0 allows no
 
128
        # Content-length in this case, so for integrity we should perform our
 
129
        # own chunking within the stream.
 
130
        # 1.1 allows chunked responses, and in this case we could chunk using
 
131
        # the HTTP chunking as this will allow HTTP persistence safely, even if
 
132
        # we have to stop early due to error, but we would also have to use the
 
133
        # HTTP trailer facility which may not be widely available.
 
134
        out_buffer = StringIO()
 
135
        smart_protocol_request = smart.protocol.SmartServerRequestProtocolOne(
 
136
                transport, out_buffer.write)
 
137
        # if this fails, we should return 400 bad request, but failure is
 
138
        # failure for now - RBC 20060919
 
139
        data_length = int(self.headers['Content-Length'])
 
140
        # Perhaps there should be a SmartServerHTTPMedium that takes care of
 
141
        # feeding the bytes in the http request to the smart_protocol_request,
 
142
        # but for now it's simpler to just feed the bytes directly.
 
143
        smart_protocol_request.accept_bytes(self.rfile.read(data_length))
 
144
        assert smart_protocol_request.next_read_size() == 0, (
 
145
            "not finished reading, but all data sent to protocol.")
 
146
        self.send_header("Content-Length", str(len(out_buffer.getvalue())))
 
147
        self.end_headers()
 
148
        self.wfile.write(out_buffer.getvalue())
 
149
 
 
150
 
 
151
class SingleRangeRequestHandler(TestingHTTPRequestHandler):
 
152
    """Always reply to range request as if they were single.
 
153
 
 
154
    Don't be explicit about it, just to annoy the clients.
 
155
    """
 
156
 
 
157
    def get_multiple_ranges(self, file, file_size, ranges):
 
158
        """Answer as if it was a single range request and ignores the rest"""
 
159
        (start, end) = ranges[0]
 
160
        return self.get_single_range(file, file_size, start, end)
 
161
 
 
162
 
 
163
class NoRangeRequestHandler(TestingHTTPRequestHandler):
 
164
    """Ignore range requests without notice"""
 
165
 
 
166
    # Just bypass the range handling done by TestingHTTPRequestHandler
 
167
    do_GET = SimpleHTTPRequestHandler.do_GET
 
168
 
 
169
 
 
170
class TestCaseWithWebserver(TestCaseWithTransport):
 
171
    """A support class that provides readonly urls that are http://.
 
172
 
 
173
    This is done by forcing the readonly server to be an http
 
174
    one. This will currently fail if the primary transport is not
 
175
    backed by regular disk files.
 
176
    """
 
177
    def setUp(self):
 
178
        super(TestCaseWithWebserver, self).setUp()
 
179
        self.transport_readonly_server = HttpServer
 
180
 
 
181
 
 
182
class TestCaseWithTwoWebservers(TestCaseWithWebserver):
 
183
    """A support class providing readonly urls on two servers that are http://.
 
184
 
 
185
    We set up two webservers to allows various tests involving
 
186
    proxies or redirections from one server to the other.
 
187
    """
 
188
    def setUp(self):
 
189
        super(TestCaseWithTwoWebservers, self).setUp()
 
190
        self.transport_secondary_server = HttpServer
 
191
        self.__secondary_server = None
 
192
 
 
193
    def create_transport_secondary_server(self):
 
194
        """Create a transport server from class defined at init.
 
195
 
 
196
        This is mostly a hook for daughter classes.
 
197
        """
 
198
        return self.transport_secondary_server()
 
199
 
 
200
    def get_secondary_server(self):
 
201
        """Get the server instance for the secondary transport."""
 
202
        if self.__secondary_server is None:
 
203
            self.__secondary_server = self.create_transport_secondary_server()
 
204
            self.__secondary_server.setUp()
 
205
            self.addCleanup(self.__secondary_server.tearDown)
 
206
        return self.__secondary_server
 
207
 
 
208
 
 
209
class FakeProxyRequestHandler(TestingHTTPRequestHandler):
 
210
    """Append a '-proxied' suffix to file served"""
 
211
 
 
212
    def translate_path(self, path):
 
213
        # We need to act as a proxy and accept absolute urls,
 
214
        # which SimpleHTTPRequestHandler (grand parent) is not
 
215
        # ready for. So we just drop the protocol://host:port
 
216
        # part in front of the request-url (because we know we
 
217
        # would not forward the request to *another* proxy).
 
218
 
 
219
        # So we do what SimpleHTTPRequestHandler.translate_path
 
220
        # do beginning with python 2.4.3: abandon query
 
221
        # parameters, scheme, host port, etc (which ensure we
 
222
        # provide the right behaviour on all python versions).
 
223
        path = urlparse.urlparse(path)[2]
 
224
        # And now, we can apply *our* trick to proxy files
 
225
        self.path += '-proxied'
 
226
        # An finally we leave our mother class do whatever it
 
227
        # wants with the path
 
228
        return TestingHTTPRequestHandler.translate_path(self, path)
 
229
 
 
230
 
 
231
class RedirectRequestHandler(TestingHTTPRequestHandler):
 
232
    """Redirect all request to the specified server"""
 
233
 
 
234
    def parse_request(self):
 
235
        """Redirect a single HTTP request to another host"""
 
236
        valid = TestingHTTPRequestHandler.parse_request(self)
 
237
        if valid:
 
238
            tcs = self.server.test_case_server
 
239
            code, target = tcs.is_redirected(self.path)
 
240
            if code is not None and target is not None:
 
241
                # Redirect as instructed
 
242
                self.send_response(code)
 
243
                self.send_header('Location', target)
 
244
                self.end_headers()
 
245
                return False # The job is done
 
246
            else:
 
247
                # We leave the parent class serve the request
 
248
                pass
 
249
        return valid
 
250
 
 
251
 
 
252
class HTTPServerRedirecting(HttpServer):
 
253
    """An HttpServer redirecting to another server """
 
254
 
 
255
    def __init__(self, request_handler=RedirectRequestHandler):
 
256
        HttpServer.__init__(self, request_handler)
 
257
        # redirections is a list of tuples (source, target, code)
 
258
        # - source is a regexp for the paths requested
 
259
        # - target is a replacement for re.sub describing where
 
260
        #   the request will be redirected
 
261
        # - code is the http error code associated to the
 
262
        #   redirection (301 permanent, 302 temporarry, etc
 
263
        self.redirections = []
 
264
 
 
265
    def redirect_to(self, host, port):
 
266
        """Redirect all requests to a specific host:port"""
 
267
        self.redirections = [('(.*)',
 
268
                              r'http://%s:%s\1' % (host, port) ,
 
269
                              301)]
 
270
 
 
271
    def is_redirected(self, path):
 
272
        """Is the path redirected by this server.
 
273
 
 
274
        :param path: the requested relative path
 
275
 
 
276
        :returns: a tuple (code, target) if a matching
 
277
             redirection is found, (None, None) otherwise.
 
278
        """
 
279
        code = None
 
280
        target = None
 
281
        for (rsource, rtarget, rcode) in self.redirections:
 
282
            target, match = re.subn(rsource, rtarget, path)
 
283
            if match:
 
284
                code = rcode
 
285
                break # The first match wins
 
286
            else:
 
287
                target = None
 
288
        return code, target
 
289
 
 
290
 
 
291
class TestCaseWithRedirectedWebserver(TestCaseWithTwoWebservers):
 
292
   """A support class providing redirections from one server to another.
 
293
 
 
294
   We set up two webservers to allows various tests involving
 
295
   redirections.
 
296
   The 'old' server is redirected to the 'new' server.
 
297
   """
 
298
 
 
299
   def create_transport_secondary_server(self):
 
300
       """Create the secondary server redirecting to the primary server"""
 
301
       new = self.get_readonly_server()
 
302
       redirecting = HTTPServerRedirecting()
 
303
       redirecting.redirect_to(new.host, new.port)
 
304
       return redirecting
 
305
 
 
306
   def setUp(self):
 
307
       super(TestCaseWithRedirectedWebserver, self).setUp()
 
308
       # The redirections will point to the new server
 
309
       self.new_server = self.get_readonly_server()
 
310
       # The requests to the old server will be redirected
 
311
       self.old_server = self.get_secondary_server()
 
312
 
 
313