blob: 05e6561f7ee4f487b769119345e8922d00affb01 [file] [log] [blame]
initial.commit94958cf2008-07-26 22:42:52 +00001#!/usr/bin/python2.4
license.botf3378c22008-08-24 00:55:55 +00002# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
initial.commit94958cf2008-07-26 22:42:52 +00005
6"""This is a simple HTTP server used for testing Chrome.
7
8It supports several test URLs, as specified by the handlers in TestPageHandler.
9It defaults to living on localhost:8888.
10It can use https if you specify the flag --https=CERT where CERT is the path
11to a pem file containing the certificate and private key that should be used.
12To shut it down properly, visit localhost:8888/kill.
13"""
14
15import base64
16import BaseHTTPServer
17import cgi
18import md5
19import optparse
20import os
21import re
22import SocketServer
23import sys
24import time
25import tlslite
26import tlslite.api
27
28debug_output = sys.stderr
29def debug(str):
30 debug_output.write(str + "\n")
31 debug_output.flush()
32
33class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
34 """This is a specialization of of BaseHTTPServer to allow it
35 to be exited cleanly (by setting its "stop" member to True)."""
36
37 def serve_forever(self):
38 self.stop = False
39 self.nonce = None
40 while not self.stop:
41 self.handle_request()
42 self.socket.close()
43
44class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
45 """This is a specialization of StoppableHTTPerver that add https support."""
46
47 def __init__(self, server_address, request_hander_class, cert_path):
48 s = open(cert_path).read()
49 x509 = tlslite.api.X509()
50 x509.parse(s)
51 self.cert_chain = tlslite.api.X509CertChain([x509])
52 s = open(cert_path).read()
53 self.private_key = tlslite.api.parsePEMKey(s, private=True)
54
55 self.session_cache = tlslite.api.SessionCache()
56 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
57
58 def handshake(self, tlsConnection):
59 """Creates the SSL connection."""
60 try:
61 tlsConnection.handshakeServer(certChain=self.cert_chain,
62 privateKey=self.private_key,
63 sessionCache=self.session_cache)
64 tlsConnection.ignoreAbruptClose = True
65 return True
66 except tlslite.api.TLSError, error:
67 print "Handshake failure:", str(error)
68 return False
69
70class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
71
72 def __init__(self, request, client_address, socket_server):
73 self._get_handlers = [
74 self.KillHandler,
75 self.NoCacheMaxAgeTimeHandler,
76 self.NoCacheTimeHandler,
77 self.CacheTimeHandler,
78 self.CacheExpiresHandler,
79 self.CacheProxyRevalidateHandler,
80 self.CachePrivateHandler,
81 self.CachePublicHandler,
82 self.CacheSMaxAgeHandler,
83 self.CacheMustRevalidateHandler,
84 self.CacheMustRevalidateMaxAgeHandler,
85 self.CacheNoStoreHandler,
86 self.CacheNoStoreMaxAgeHandler,
87 self.CacheNoTransformHandler,
88 self.DownloadHandler,
89 self.DownloadFinishHandler,
90 self.EchoHeader,
91 self.FileHandler,
92 self.RealFileWithCommonHeaderHandler,
93 self.RealBZ2FileWithCommonHeaderHandler,
94 self.AuthBasicHandler,
95 self.AuthDigestHandler,
96 self.SlowServerHandler,
97 self.ContentTypeHandler,
98 self.ServerRedirectHandler,
99 self.ClientRedirectHandler,
100 self.DefaultResponseHandler]
101 self._post_handlers = [
102 self.EchoTitleHandler,
103 self.EchoAllHandler,
104 self.EchoHandler] + self._get_handlers
105
106 self._mime_types = { 'gif': 'image/gif', 'jpeg' : 'image/jpeg', 'jpg' : 'image/jpeg' }
107 self._default_mime_type = 'text/html'
108
109 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, socket_server)
110
111 def GetMIMETypeFromName(self, file_name):
112 """Returns the mime type for the specified file_name. So far it only looks
113 at the file extension."""
114
115 (shortname, extension) = os.path.splitext(file_name)
116 if len(extension) == 0:
117 # no extension.
118 return self._default_mime_type
119
120 return self._mime_types.get(extension, self._default_mime_type)
121
122 def KillHandler(self):
123 """This request handler kills the server, for use when we're done"
124 with the a particular test."""
125
126 if (self.path.find("kill") < 0):
127 return False
128
129 self.send_response(200)
130 self.send_header('Content-type', 'text/html')
131 self.send_header('Cache-Control', 'max-age=0')
132 self.end_headers()
133 self.wfile.write("Time to die")
134 self.server.stop = True
135
136 return True
137
138 def NoCacheMaxAgeTimeHandler(self):
139 """This request handler yields a page with the title set to the current
140 system time, and no caching requested."""
141
142 if (self.path.find("/nocachetime/maxage") != 0):
143 return False
144
145 self.send_response(200)
146 self.send_header('Cache-Control', 'max-age=0')
147 self.send_header('Content-type', 'text/html')
148 self.end_headers()
149
150 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
151
152 return True
153
154 def NoCacheTimeHandler(self):
155 """This request handler yields a page with the title set to the current
156 system time, and no caching requested."""
157
158 if (self.path.find("/nocachetime") != 0):
159 return False
160
161 self.send_response(200)
162 self.send_header('Cache-Control', 'no-cache')
163 self.send_header('Content-type', 'text/html')
164 self.end_headers()
165
166 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
167
168 return True
169
170 def CacheTimeHandler(self):
171 """This request handler yields a page with the title set to the current
172 system time, and allows caching for one minute."""
173
174 if self.path.find("/cachetime") != 0:
175 return False
176
177 self.send_response(200)
178 self.send_header('Cache-Control', 'max-age=60')
179 self.send_header('Content-type', 'text/html')
180 self.end_headers()
181
182 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
183
184 return True
185
186 def CacheExpiresHandler(self):
187 """This request handler yields a page with the title set to the current
188 system time, and set the page to expire on 1 Jan 2099."""
189
190 if (self.path.find("/cache/expires") != 0):
191 return False
192
193 self.send_response(200)
194 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
195 self.send_header('Content-type', 'text/html')
196 self.end_headers()
197
198 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
199
200 return True
201
202 def CacheProxyRevalidateHandler(self):
203 """This request handler yields a page with the title set to the current
204 system time, and allows caching for 60 seconds"""
205
206 if (self.path.find("/cache/proxy-revalidate") != 0):
207 return False
208
209 self.send_response(200)
210 self.send_header('Content-type', 'text/html')
211 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
212 self.end_headers()
213
214 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
215
216 return True
217
218 def CachePrivateHandler(self):
219 """This request handler yields a page with the title set to the current
220 system time, and allows caching for 5 seconds."""
221
222 if (self.path.find("/cache/private") != 0):
223 return False
224
225 self.send_response(200)
226 self.send_header('Content-type', 'text/html')
227 self.send_header('Cache-Control', 'max-age=5, private')
228 self.end_headers()
229
230 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
231
232 return True
233
234 def CachePublicHandler(self):
235 """This request handler yields a page with the title set to the current
236 system time, and allows caching for 5 seconds."""
237
238 if (self.path.find("/cache/public") != 0):
239 return False
240
241 self.send_response(200)
242 self.send_header('Content-type', 'text/html')
243 self.send_header('Cache-Control', 'max-age=5, public')
244 self.end_headers()
245
246 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
247
248 return True
249
250 def CacheSMaxAgeHandler(self):
251 """This request handler yields a page with the title set to the current
252 system time, and does not allow for caching."""
253
254 if (self.path.find("/cache/s-maxage") != 0):
255 return False
256
257 self.send_response(200)
258 self.send_header('Content-type', 'text/html')
259 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
260 self.end_headers()
261
262 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
263
264 return True
265
266 def CacheMustRevalidateHandler(self):
267 """This request handler yields a page with the title set to the current
268 system time, and does not allow caching."""
269
270 if (self.path.find("/cache/must-revalidate") != 0):
271 return False
272
273 self.send_response(200)
274 self.send_header('Content-type', 'text/html')
275 self.send_header('Cache-Control', 'must-revalidate')
276 self.end_headers()
277
278 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
279
280 return True
281
282 def CacheMustRevalidateMaxAgeHandler(self):
283 """This request handler yields a page with the title set to the current
284 system time, and does not allow caching event though max-age of 60
285 seconds is specified."""
286
287 if (self.path.find("/cache/must-revalidate/max-age") != 0):
288 return False
289
290 self.send_response(200)
291 self.send_header('Content-type', 'text/html')
292 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
293 self.end_headers()
294
295 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
296
297 return True
298
299
300 def CacheNoStoreHandler(self):
301 """This request handler yields a page with the title set to the current
302 system time, and does not allow the page to be stored."""
303
304 if (self.path.find("/cache/no-store") != 0):
305 return False
306
307 self.send_response(200)
308 self.send_header('Content-type', 'text/html')
309 self.send_header('Cache-Control', 'no-store')
310 self.end_headers()
311
312 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
313
314 return True
315
316 def CacheNoStoreMaxAgeHandler(self):
317 """This request handler yields a page with the title set to the current
318 system time, and does not allow the page to be stored even though max-age
319 of 60 seconds is specified."""
320
321 if (self.path.find("/cache/no-store/max-age") != 0):
322 return False
323
324 self.send_response(200)
325 self.send_header('Content-type', 'text/html')
326 self.send_header('Cache-Control', 'max-age=60, no-store')
327 self.end_headers()
328
329 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
330
331 return True
332
333
334 def CacheNoTransformHandler(self):
335 """This request handler yields a page with the title set to the current
336 system time, and does not allow the content to transformed during
337 user-agent caching"""
338
339 if (self.path.find("/cache/no-transform") != 0):
340 return False
341
342 self.send_response(200)
343 self.send_header('Content-type', 'text/html')
344 self.send_header('Cache-Control', 'no-transform')
345 self.end_headers()
346
347 self.wfile.write('<html><head><title>%s</title></head></html>' % time.time())
348
349 return True
350
351 def EchoHeader(self):
352 """This handler echoes back the value of a specific request header."""
353
354 if self.path.find("/echoheader") != 0:
355 return False
356
357 query_char = self.path.find('?')
358 if query_char != -1:
359 header_name = self.path[query_char+1:]
360
361 self.send_response(200)
362 self.send_header('Content-type', 'text/plain')
363 self.send_header('Cache-control', 'max-age=60000')
364 # insert a vary header to properly indicate that the cachability of this
365 # request is subject to value of the request header being echoed.
366 if len(header_name) > 0:
367 self.send_header('Vary', header_name)
368 self.end_headers()
369
370 if len(header_name) > 0:
371 self.wfile.write(self.headers.getheader(header_name))
372
373 return True
374
375 def EchoHandler(self):
376 """This handler just echoes back the payload of the request, for testing
377 form submission."""
378
379 if self.path.find("/echo") != 0:
380 return False
381
382 self.send_response(200)
383 self.send_header('Content-type', 'text/html')
384 self.end_headers()
385 length = int(self.headers.getheader('content-length'))
386 request = self.rfile.read(length)
387 self.wfile.write(request)
388 return True
389
390 def EchoTitleHandler(self):
391 """This handler is like Echo, but sets the page title to the request."""
392
393 if self.path.find("/echotitle") != 0:
394 return False
395
396 self.send_response(200)
397 self.send_header('Content-type', 'text/html')
398 self.end_headers()
399 length = int(self.headers.getheader('content-length'))
400 request = self.rfile.read(length)
401 self.wfile.write('<html><head><title>')
402 self.wfile.write(request)
403 self.wfile.write('</title></head></html>')
404 return True
405
406 def EchoAllHandler(self):
407 """This handler yields a (more) human-readable page listing information
408 about the request header & contents."""
409
410 if self.path.find("/echoall") != 0:
411 return False
412
413 self.send_response(200)
414 self.send_header('Content-type', 'text/html')
415 self.end_headers()
416 self.wfile.write('<html><head><style>'
417 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
418 '</style></head><body>'
419 '<div style="float: right">'
420 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
421 '<h1>Request Body:</h1><pre>')
422 length = int(self.headers.getheader('content-length'))
423 qs = self.rfile.read(length)
424 params = cgi.parse_qs(qs, keep_blank_values=1)
425
426 for param in params:
427 self.wfile.write('%s=%s\n' % (param, params[param][0]))
428
429 self.wfile.write('</pre>')
430
431 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
432
433 self.wfile.write('</body></html>')
434 return True
435
436 def DownloadHandler(self):
437 """This handler sends a downloadable file with or without reporting
438 the size (6K)."""
439
440 if self.path.startswith("/download-unknown-size"):
441 send_length = False
442 elif self.path.startswith("/download-known-size"):
443 send_length = True
444 else:
445 return False
446
447 #
448 # The test which uses this functionality is attempting to send
449 # small chunks of data to the client. Use a fairly large buffer
450 # so that we'll fill chrome's IO buffer enough to force it to
451 # actually write the data.
452 # See also the comments in the client-side of this test in
453 # download_uitest.cc
454 #
455 size_chunk1 = 35*1024
456 size_chunk2 = 10*1024
457
458 self.send_response(200)
459 self.send_header('Content-type', 'application/octet-stream')
460 self.send_header('Cache-Control', 'max-age=0')
461 if send_length:
462 self.send_header('Content-Length', size_chunk1 + size_chunk2)
463 self.end_headers()
464
465 # First chunk of data:
466 self.wfile.write("*" * size_chunk1)
467 self.wfile.flush()
468
469 # handle requests until one of them clears this flag.
470 self.server.waitForDownload = True
471 while self.server.waitForDownload:
472 self.server.handle_request()
473
474 # Second chunk of data:
475 self.wfile.write("*" * size_chunk2)
476 return True
477
478 def DownloadFinishHandler(self):
479 """This handler just tells the server to finish the current download."""
480
481 if not self.path.startswith("/download-finish"):
482 return False
483
484 self.server.waitForDownload = False
485 self.send_response(200)
486 self.send_header('Content-type', 'text/html')
487 self.send_header('Cache-Control', 'max-age=0')
488 self.end_headers()
489 return True
490
491 def FileHandler(self):
492 """This handler sends the contents of the requested file. Wow, it's like
493 a real webserver!"""
494
495 prefix='/files/'
496 if not self.path.startswith(prefix):
497 return False
498
499 file = self.path[len(prefix):]
500 entries = file.split('/');
501 path = os.path.join(self.server.data_dir, *entries)
502
503 if not os.path.isfile(path):
504 print "File not found " + file + " full path:" + path
505 self.send_error(404)
506 return True
507
508 f = open(path, "rb")
509 data = f.read()
510 f.close()
511
512 # If file.mock-http-headers exists, it contains the headers we
513 # should send. Read them in and parse them.
514 headers_path = path + '.mock-http-headers'
515 if os.path.isfile(headers_path):
516 f = open(headers_path, "r")
517
518 # "HTTP/1.1 200 OK"
519 response = f.readline()
520 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
521 self.send_response(int(status_code))
522
523 for line in f:
524 # "name: value"
525 name, value = re.findall('(\S+):\s*(.*)', line)[0]
526 self.send_header(name, value)
527 f.close()
528 else:
529 # Could be more generic once we support mime-type sniffing, but for
530 # now we need to set it explicitly.
531 self.send_response(200)
532 self.send_header('Content-type', self.GetMIMETypeFromName(file))
533 self.send_header('Content-Length', len(data))
534 self.end_headers()
535
536 self.wfile.write(data)
537
538 return True
539
540 def RealFileWithCommonHeaderHandler(self):
541 """This handler sends the contents of the requested file without the pseudo
542 http head!"""
543
544 prefix='/realfiles/'
545 if not self.path.startswith(prefix):
546 return False
547
548 file = self.path[len(prefix):]
549 path = os.path.join(self.server.data_dir, file)
550
551 try:
552 f = open(path, "rb")
553 data = f.read()
554 f.close()
555
556 # just simply set the MIME as octal stream
557 self.send_response(200)
558 self.send_header('Content-type', 'application/octet-stream')
559 self.end_headers()
560
561 self.wfile.write(data)
562 except:
563 self.send_error(404)
564
565 return True
566
567 def RealBZ2FileWithCommonHeaderHandler(self):
568 """This handler sends the bzip2 contents of the requested file with
569 corresponding Content-Encoding field in http head!"""
570
571 prefix='/realbz2files/'
572 if not self.path.startswith(prefix):
573 return False
574
575 parts = self.path.split('?')
576 file = parts[0][len(prefix):]
577 path = os.path.join(self.server.data_dir, file) + '.bz2'
578
579 if len(parts) > 1:
580 options = parts[1]
581 else:
582 options = ''
583
584 try:
585 self.send_response(200)
586 accept_encoding = self.headers.get("Accept-Encoding")
587 if accept_encoding.find("bzip2") != -1:
588 f = open(path, "rb")
589 data = f.read()
590 f.close()
591 self.send_header('Content-Encoding', 'bzip2')
592 self.send_header('Content-type', 'application/x-bzip2')
593 self.end_headers()
594 if options == 'incremental-header':
595 self.wfile.write(data[:1])
596 self.wfile.flush()
597 time.sleep(1.0)
598 self.wfile.write(data[1:])
599 else:
600 self.wfile.write(data)
601 else:
602 """client do not support bzip2 format, send pseudo content
603 """
604 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
605 self.end_headers()
606 self.wfile.write("you do not support bzip2 encoding")
607 except:
608 self.send_error(404)
609
610 return True
611
612 def AuthBasicHandler(self):
613 """This handler tests 'Basic' authentication. It just sends a page with
614 title 'user/pass' if you succeed."""
615
616 if not self.path.startswith("/auth-basic"):
617 return False
618
619 username = userpass = password = b64str = ""
620
621 auth = self.headers.getheader('authorization')
622 try:
623 if not auth:
624 raise Exception('no auth')
625 b64str = re.findall(r'Basic (\S+)', auth)[0]
626 userpass = base64.b64decode(b64str)
627 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
628 if password != 'secret':
629 raise Exception('wrong password')
630 except Exception, e:
631 # Authentication failed.
632 self.send_response(401)
633 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
634 self.send_header('Content-type', 'text/html')
635 self.end_headers()
636 self.wfile.write('<html><head>')
637 self.wfile.write('<title>Denied: %s</title>' % e)
638 self.wfile.write('</head><body>')
639 self.wfile.write('auth=%s<p>' % auth)
640 self.wfile.write('b64str=%s<p>' % b64str)
641 self.wfile.write('username: %s<p>' % username)
642 self.wfile.write('userpass: %s<p>' % userpass)
643 self.wfile.write('password: %s<p>' % password)
644 self.wfile.write('You sent:<br>%s<p>' % self.headers)
645 self.wfile.write('</body></html>')
646 return True
647
648 # Authentication successful. (Return a cachable response to allow for
649 # testing cached pages that require authentication.)
650 if_none_match = self.headers.getheader('if-none-match')
651 if if_none_match == "abc":
652 self.send_response(304)
653 self.end_headers()
654 else:
655 self.send_response(200)
656 self.send_header('Content-type', 'text/html')
657 self.send_header('Cache-control', 'max-age=60000')
658 self.send_header('Etag', 'abc')
659 self.end_headers()
660 self.wfile.write('<html><head>')
661 self.wfile.write('<title>%s/%s</title>' % (username, password))
662 self.wfile.write('</head><body>')
663 self.wfile.write('auth=%s<p>' % auth)
664 self.wfile.write('</body></html>')
665
666 return True
667
668 def AuthDigestHandler(self):
669 """This handler tests 'Digest' authentication. It just sends a page with
670 title 'user/pass' if you succeed."""
671
672 if not self.path.startswith("/auth-digest"):
673 return False
674
675 # Periodically generate a new nonce. Technically we should incorporate
676 # the request URL into this, but we don't care for testing.
677 nonce_life = 10
678 stale = False
679 if not self.server.nonce or (time.time() - self.server.nonce_time > nonce_life):
680 if self.server.nonce:
681 stale = True
682 self.server.nonce_time = time.time()
683 self.server.nonce = \
684 md5.new(time.ctime(self.server.nonce_time) + 'privatekey').hexdigest()
685
686 nonce = self.server.nonce
687 opaque = md5.new('opaque').hexdigest()
688 password = 'secret'
689 realm = 'testrealm'
690
691 auth = self.headers.getheader('authorization')
692 pairs = {}
693 try:
694 if not auth:
695 raise Exception('no auth')
696 if not auth.startswith('Digest'):
697 raise Exception('not digest')
698 # Pull out all the name="value" pairs as a dictionary.
699 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
700
701 # Make sure it's all valid.
702 if pairs['nonce'] != nonce:
703 raise Exception('wrong nonce')
704 if pairs['opaque'] != opaque:
705 raise Exception('wrong opaque')
706
707 # Check the 'response' value and make sure it matches our magic hash.
708 # See http://www.ietf.org/rfc/rfc2617.txt
709 hash_a1 = md5.new(':'.join([pairs['username'], realm, password])).hexdigest()
710 hash_a2 = md5.new(':'.join([self.command, pairs['uri']])).hexdigest()
711 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
712 response = md5.new(':'.join([hash_a1, nonce, pairs['nc'],
713 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
714 else:
715 response = md5.new(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
716
717 if pairs['response'] != response:
718 raise Exception('wrong password')
719 except Exception, e:
720 # Authentication failed.
721 self.send_response(401)
722 hdr = ('Digest '
723 'realm="%s", '
724 'domain="/", '
725 'qop="auth", '
726 'algorithm=MD5, '
727 'nonce="%s", '
728 'opaque="%s"') % (realm, nonce, opaque)
729 if stale:
730 hdr += ', stale="TRUE"'
731 self.send_header('WWW-Authenticate', hdr)
732 self.send_header('Content-type', 'text/html')
733 self.end_headers()
734 self.wfile.write('<html><head>')
735 self.wfile.write('<title>Denied: %s</title>' % e)
736 self.wfile.write('</head><body>')
737 self.wfile.write('auth=%s<p>' % auth)
738 self.wfile.write('pairs=%s<p>' % pairs)
739 self.wfile.write('You sent:<br>%s<p>' % self.headers)
740 self.wfile.write('We are replying:<br>%s<p>' % hdr)
741 self.wfile.write('</body></html>')
742 return True
743
744 # Authentication successful.
745 self.send_response(200)
746 self.send_header('Content-type', 'text/html')
747 self.end_headers()
748 self.wfile.write('<html><head>')
749 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
750 self.wfile.write('</head><body>')
751 self.wfile.write('auth=%s<p>' % auth)
752 self.wfile.write('pairs=%s<p>' % pairs)
753 self.wfile.write('</body></html>')
754
755 return True
756
757 def SlowServerHandler(self):
758 """Wait for the user suggested time before responding. The syntax is
759 /slow?0.5 to wait for half a second."""
760 if not self.path.startswith("/slow"):
761 return False
762 query_char = self.path.find('?')
763 wait_sec = 1.0
764 if query_char >= 0:
765 try:
766 wait_sec = int(self.path[query_char + 1:])
767 except ValueError:
768 pass
769 time.sleep(wait_sec)
770 self.send_response(200)
771 self.send_header('Content-type', 'text/plain')
772 self.end_headers()
773 self.wfile.write("waited %d seconds" % wait_sec)
774 return True
775
776 def ContentTypeHandler(self):
777 """Returns a string of html with the given content type. E.g.,
778 /contenttype?text/css returns an html file with the Content-Type
779 header set to text/css."""
780 if not self.path.startswith('/contenttype'):
781 return False
782 query_char = self.path.find('?')
783 content_type = self.path[query_char + 1:].strip()
784 if not content_type:
785 content_type = 'text/html'
786 self.send_response(200)
787 self.send_header('Content-Type', content_type)
788 self.end_headers()
789 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
790 return True
791
792 def ServerRedirectHandler(self):
793 """Sends a server redirect to the given URL. The syntax is
794 '/server-redirect?http://foo.bar/asdf' to redirect to 'http://foo.bar/asdf'"""
795
796 test_name = "/server-redirect"
797 if not self.path.startswith(test_name):
798 return False
799
800 query_char = self.path.find('?')
801 if query_char < 0 or len(self.path) <= query_char + 1:
802 self.sendRedirectHelp(test_name)
803 return True
804 dest = self.path[query_char + 1:]
805
806 self.send_response(301) # moved permanently
807 self.send_header('Location', dest)
808 self.send_header('Content-type', 'text/html')
809 self.end_headers()
810 self.wfile.write('<html><head>')
811 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
812
813 return True;
814
815 def ClientRedirectHandler(self):
816 """Sends a client redirect to the given URL. The syntax is
817 '/client-redirect?http://foo.bar/asdf' to redirect to 'http://foo.bar/asdf'"""
818
819 test_name = "/client-redirect"
820 if not self.path.startswith(test_name):
821 return False
822
823 query_char = self.path.find('?');
824 if query_char < 0 or len(self.path) <= query_char + 1:
825 self.sendRedirectHelp(test_name)
826 return True
827 dest = self.path[query_char + 1:]
828
829 self.send_response(200)
830 self.send_header('Content-type', 'text/html')
831 self.end_headers()
832 self.wfile.write('<html><head>')
833 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
834 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
835
836 return True
837
838 def DefaultResponseHandler(self):
839 """This is the catch-all response handler for requests that aren't handled
840 by one of the special handlers above.
841 Note that we specify the content-length as without it the https connection
842 is not closed properly (and the browser keeps expecting data)."""
843
844 contents = "Default response given for path: " + self.path
845 self.send_response(200)
846 self.send_header('Content-type', 'text/html')
847 self.send_header("Content-Length", len(contents))
848 self.end_headers()
849 self.wfile.write(contents)
850 return True
851
852 def do_GET(self):
853 for handler in self._get_handlers:
854 if (handler()):
855 return
856
857 def do_POST(self):
858 for handler in self._post_handlers:
859 if(handler()):
860 return
861
862 # called by the redirect handling function when there is no parameter
863 def sendRedirectHelp(self, redirect_name):
864 self.send_response(200)
865 self.send_header('Content-type', 'text/html')
866 self.end_headers()
867 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
868 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
869 self.wfile.write('</body></html>')
870
871def main(options, args):
872 # redirect output to a log file so it doesn't spam the unit test output
873 logfile = open('testserver.log', 'w')
874 sys.stderr = sys.stdout = logfile
875
876 port = options.port
877
878 if options.cert:
879 # let's make sure the cert file exists.
880 if not os.path.isfile(options.cert):
881 print 'specified cert file not found: ' + options.cert + ' exiting...'
882 return
883 server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert)
884 print 'HTTPS server started on port %d...' % port
885 else:
886 server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler)
887 print 'HTTP server started on port %d...' % port
888
889 if options.data_dir:
890 if not os.path.isdir(options.data_dir):
891 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
892 return
893 server.data_dir = options.data_dir
894 else:
895 # Create the default path to our data dir, relative to the exe dir.
896 server.data_dir = os.path.dirname(sys.argv[0])
897 server.data_dir = os.path.join(server.data_dir, "..", "..", "..", "..",
898 "test", "data")
899
900 try:
901 server.serve_forever()
902 except KeyboardInterrupt:
903 print 'shutting down server'
904 server.stop = True
905
906if __name__ == '__main__':
907 option_parser = optparse.OptionParser()
908 option_parser.add_option('', '--port', default='8888', type='int',
909 help='Port used by the server')
910 option_parser.add_option('', '--data-dir', dest='data_dir',
911 help='Directory from which to read the files')
912 option_parser.add_option('', '--https', dest='cert',
913 help='Specify that https should be used, specify '
914 'the path to the cert containing the private key '
915 'the server should use')
916 options, args = option_parser.parse_args()
917
918 sys.exit(main(options, args))
license.botf3378c22008-08-24 00:55:55 +0000919