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