blob: aeee5e2a076097f14d6c6aca246c15b91bdee7d0 [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
initial.commit94958cf2008-07-26 22:42:52 +000018import optparse
19import os
20import re
stoyan@chromium.org372692c2009-01-30 17:01:52 +000021import shutil
initial.commit94958cf2008-07-26 22:42:52 +000022import SocketServer
23import sys
24import time
25import tlslite
26import tlslite.api
erikkay@google.comd5182ff2009-01-08 20:45:27 +000027import pyftpdlib.ftpserver
28
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +000029try:
30 import hashlib
31 _new_md5 = hashlib.md5
32except ImportError:
33 import md5
34 _new_md5 = md5.new
35
maruel@chromium.org756cf982009-03-05 12:46:38 +000036SERVER_HTTP = 0
erikkay@google.comd5182ff2009-01-08 20:45:27 +000037SERVER_FTP = 1
initial.commit94958cf2008-07-26 22:42:52 +000038
39debug_output = sys.stderr
40def debug(str):
41 debug_output.write(str + "\n")
42 debug_output.flush()
43
44class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
45 """This is a specialization of of BaseHTTPServer to allow it
46 to be exited cleanly (by setting its "stop" member to True)."""
47
48 def serve_forever(self):
49 self.stop = False
50 self.nonce = None
51 while not self.stop:
52 self.handle_request()
53 self.socket.close()
54
55class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
56 """This is a specialization of StoppableHTTPerver that add https support."""
57
58 def __init__(self, server_address, request_hander_class, cert_path):
59 s = open(cert_path).read()
60 x509 = tlslite.api.X509()
61 x509.parse(s)
62 self.cert_chain = tlslite.api.X509CertChain([x509])
63 s = open(cert_path).read()
64 self.private_key = tlslite.api.parsePEMKey(s, private=True)
65
66 self.session_cache = tlslite.api.SessionCache()
67 StoppableHTTPServer.__init__(self, server_address, request_hander_class)
68
69 def handshake(self, tlsConnection):
70 """Creates the SSL connection."""
71 try:
72 tlsConnection.handshakeServer(certChain=self.cert_chain,
73 privateKey=self.private_key,
74 sessionCache=self.session_cache)
75 tlsConnection.ignoreAbruptClose = True
76 return True
77 except tlslite.api.TLSError, error:
78 print "Handshake failure:", str(error)
79 return False
80
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +000081class ForkingHTTPServer(SocketServer.ForkingMixIn, StoppableHTTPServer):
82 """This is a specialization of of StoppableHTTPServer which serves each
83 request in a separate process"""
84 pass
85
86class ForkingHTTPSServer(SocketServer.ForkingMixIn, HTTPSServer):
87 """This is a specialization of of HTTPSServer which serves each
88 request in a separate process"""
89 pass
90
initial.commit94958cf2008-07-26 22:42:52 +000091class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
92
93 def __init__(self, request, client_address, socket_server):
wtc@chromium.org743d77b2009-02-11 02:48:15 +000094 self._connect_handlers = [
95 self.RedirectConnectHandler,
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +000096 self.ServerAuthConnectHandler,
wtc@chromium.org743d77b2009-02-11 02:48:15 +000097 self.DefaultConnectResponseHandler]
initial.commit94958cf2008-07-26 22:42:52 +000098 self._get_handlers = [
99 self.KillHandler,
100 self.NoCacheMaxAgeTimeHandler,
101 self.NoCacheTimeHandler,
102 self.CacheTimeHandler,
103 self.CacheExpiresHandler,
104 self.CacheProxyRevalidateHandler,
105 self.CachePrivateHandler,
106 self.CachePublicHandler,
107 self.CacheSMaxAgeHandler,
108 self.CacheMustRevalidateHandler,
109 self.CacheMustRevalidateMaxAgeHandler,
110 self.CacheNoStoreHandler,
111 self.CacheNoStoreMaxAgeHandler,
112 self.CacheNoTransformHandler,
113 self.DownloadHandler,
114 self.DownloadFinishHandler,
115 self.EchoHeader,
ericroman@google.coma47622b2008-11-15 04:36:51 +0000116 self.EchoAllHandler,
initial.commit94958cf2008-07-26 22:42:52 +0000117 self.FileHandler,
118 self.RealFileWithCommonHeaderHandler,
119 self.RealBZ2FileWithCommonHeaderHandler,
120 self.AuthBasicHandler,
121 self.AuthDigestHandler,
122 self.SlowServerHandler,
123 self.ContentTypeHandler,
124 self.ServerRedirectHandler,
125 self.ClientRedirectHandler,
126 self.DefaultResponseHandler]
127 self._post_handlers = [
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000128 self.WriteFile,
initial.commit94958cf2008-07-26 22:42:52 +0000129 self.EchoTitleHandler,
130 self.EchoAllHandler,
131 self.EchoHandler] + self._get_handlers
132
maruel@google.come250a9b2009-03-10 17:39:46 +0000133 self._mime_types = {
134 'gif': 'image/gif',
135 'jpeg' : 'image/jpeg',
136 'jpg' : 'image/jpeg'
137 }
initial.commit94958cf2008-07-26 22:42:52 +0000138 self._default_mime_type = 'text/html'
139
maruel@google.come250a9b2009-03-10 17:39:46 +0000140 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
141 client_address,
142 socket_server)
initial.commit94958cf2008-07-26 22:42:52 +0000143
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000144 def _ShouldHandleRequest(self, handler_name):
145 """Determines if the path can be handled by the handler.
146
147 We consider a handler valid if the path begins with the
148 handler name. It can optionally be followed by "?*", "/*".
149 """
150
151 pattern = re.compile('%s($|\?|/).*' % handler_name)
152 return pattern.match(self.path)
153
initial.commit94958cf2008-07-26 22:42:52 +0000154 def GetMIMETypeFromName(self, file_name):
155 """Returns the mime type for the specified file_name. So far it only looks
156 at the file extension."""
157
158 (shortname, extension) = os.path.splitext(file_name)
159 if len(extension) == 0:
160 # no extension.
161 return self._default_mime_type
162
ericroman@google.comc17ca532009-05-07 03:51:05 +0000163 # extension starts with a dot, so we need to remove it
164 return self._mime_types.get(extension[1:], self._default_mime_type)
initial.commit94958cf2008-07-26 22:42:52 +0000165
166 def KillHandler(self):
167 """This request handler kills the server, for use when we're done"
168 with the a particular test."""
169
170 if (self.path.find("kill") < 0):
171 return False
172
173 self.send_response(200)
174 self.send_header('Content-type', 'text/html')
175 self.send_header('Cache-Control', 'max-age=0')
176 self.end_headers()
177 self.wfile.write("Time to die")
178 self.server.stop = True
179
180 return True
181
182 def NoCacheMaxAgeTimeHandler(self):
183 """This request handler yields a page with the title set to the current
184 system time, and no caching requested."""
185
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000186 if not self._ShouldHandleRequest("/nocachetime/maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000187 return False
188
189 self.send_response(200)
190 self.send_header('Cache-Control', 'max-age=0')
191 self.send_header('Content-type', 'text/html')
192 self.end_headers()
193
maruel@google.come250a9b2009-03-10 17:39:46 +0000194 self.wfile.write('<html><head><title>%s</title></head></html>' %
195 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000196
197 return True
198
199 def NoCacheTimeHandler(self):
200 """This request handler yields a page with the title set to the current
201 system time, and no caching requested."""
202
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000203 if not self._ShouldHandleRequest("/nocachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000204 return False
205
206 self.send_response(200)
207 self.send_header('Cache-Control', 'no-cache')
208 self.send_header('Content-type', 'text/html')
209 self.end_headers()
210
maruel@google.come250a9b2009-03-10 17:39:46 +0000211 self.wfile.write('<html><head><title>%s</title></head></html>' %
212 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000213
214 return True
215
216 def CacheTimeHandler(self):
217 """This request handler yields a page with the title set to the current
218 system time, and allows caching for one minute."""
219
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000220 if not self._ShouldHandleRequest("/cachetime"):
initial.commit94958cf2008-07-26 22:42:52 +0000221 return False
222
223 self.send_response(200)
224 self.send_header('Cache-Control', 'max-age=60')
225 self.send_header('Content-type', 'text/html')
226 self.end_headers()
227
maruel@google.come250a9b2009-03-10 17:39:46 +0000228 self.wfile.write('<html><head><title>%s</title></head></html>' %
229 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000230
231 return True
232
233 def CacheExpiresHandler(self):
234 """This request handler yields a page with the title set to the current
235 system time, and set the page to expire on 1 Jan 2099."""
236
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000237 if not self._ShouldHandleRequest("/cache/expires"):
initial.commit94958cf2008-07-26 22:42:52 +0000238 return False
239
240 self.send_response(200)
241 self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
242 self.send_header('Content-type', 'text/html')
243 self.end_headers()
244
maruel@google.come250a9b2009-03-10 17:39:46 +0000245 self.wfile.write('<html><head><title>%s</title></head></html>' %
246 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000247
248 return True
249
250 def CacheProxyRevalidateHandler(self):
251 """This request handler yields a page with the title set to the current
252 system time, and allows caching for 60 seconds"""
253
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000254 if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000255 return False
256
257 self.send_response(200)
258 self.send_header('Content-type', 'text/html')
259 self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
260 self.end_headers()
261
maruel@google.come250a9b2009-03-10 17:39:46 +0000262 self.wfile.write('<html><head><title>%s</title></head></html>' %
263 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000264
265 return True
266
267 def CachePrivateHandler(self):
268 """This request handler yields a page with the title set to the current
269 system time, and allows caching for 5 seconds."""
270
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000271 if not self._ShouldHandleRequest("/cache/private"):
initial.commit94958cf2008-07-26 22:42:52 +0000272 return False
273
274 self.send_response(200)
275 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000276 self.send_header('Cache-Control', 'max-age=3, private')
initial.commit94958cf2008-07-26 22:42:52 +0000277 self.end_headers()
278
maruel@google.come250a9b2009-03-10 17:39:46 +0000279 self.wfile.write('<html><head><title>%s</title></head></html>' %
280 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000281
282 return True
283
284 def CachePublicHandler(self):
285 """This request handler yields a page with the title set to the current
286 system time, and allows caching for 5 seconds."""
287
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000288 if not self._ShouldHandleRequest("/cache/public"):
initial.commit94958cf2008-07-26 22:42:52 +0000289 return False
290
291 self.send_response(200)
292 self.send_header('Content-type', 'text/html')
huanr@chromium.orgab5be752009-05-23 02:58:44 +0000293 self.send_header('Cache-Control', 'max-age=3, public')
initial.commit94958cf2008-07-26 22:42:52 +0000294 self.end_headers()
295
maruel@google.come250a9b2009-03-10 17:39:46 +0000296 self.wfile.write('<html><head><title>%s</title></head></html>' %
297 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000298
299 return True
300
301 def CacheSMaxAgeHandler(self):
302 """This request handler yields a page with the title set to the current
303 system time, and does not allow for caching."""
304
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000305 if not self._ShouldHandleRequest("/cache/s-maxage"):
initial.commit94958cf2008-07-26 22:42:52 +0000306 return False
307
308 self.send_response(200)
309 self.send_header('Content-type', 'text/html')
310 self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
311 self.end_headers()
312
maruel@google.come250a9b2009-03-10 17:39:46 +0000313 self.wfile.write('<html><head><title>%s</title></head></html>' %
314 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000315
316 return True
317
318 def CacheMustRevalidateHandler(self):
319 """This request handler yields a page with the title set to the current
320 system time, and does not allow caching."""
321
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000322 if not self._ShouldHandleRequest("/cache/must-revalidate"):
initial.commit94958cf2008-07-26 22:42:52 +0000323 return False
324
325 self.send_response(200)
326 self.send_header('Content-type', 'text/html')
327 self.send_header('Cache-Control', 'must-revalidate')
328 self.end_headers()
329
maruel@google.come250a9b2009-03-10 17:39:46 +0000330 self.wfile.write('<html><head><title>%s</title></head></html>' %
331 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000332
333 return True
334
335 def CacheMustRevalidateMaxAgeHandler(self):
336 """This request handler yields a page with the title set to the current
337 system time, and does not allow caching event though max-age of 60
338 seconds is specified."""
339
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000340 if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000341 return False
342
343 self.send_response(200)
344 self.send_header('Content-type', 'text/html')
345 self.send_header('Cache-Control', 'max-age=60, must-revalidate')
346 self.end_headers()
347
maruel@google.come250a9b2009-03-10 17:39:46 +0000348 self.wfile.write('<html><head><title>%s</title></head></html>' %
349 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000350
351 return True
352
initial.commit94958cf2008-07-26 22:42:52 +0000353 def CacheNoStoreHandler(self):
354 """This request handler yields a page with the title set to the current
355 system time, and does not allow the page to be stored."""
356
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000357 if not self._ShouldHandleRequest("/cache/no-store"):
initial.commit94958cf2008-07-26 22:42:52 +0000358 return False
359
360 self.send_response(200)
361 self.send_header('Content-type', 'text/html')
362 self.send_header('Cache-Control', 'no-store')
363 self.end_headers()
364
maruel@google.come250a9b2009-03-10 17:39:46 +0000365 self.wfile.write('<html><head><title>%s</title></head></html>' %
366 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000367
368 return True
369
370 def CacheNoStoreMaxAgeHandler(self):
371 """This request handler yields a page with the title set to the current
372 system time, and does not allow the page to be stored even though max-age
373 of 60 seconds is specified."""
374
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000375 if not self._ShouldHandleRequest("/cache/no-store/max-age"):
initial.commit94958cf2008-07-26 22:42:52 +0000376 return False
377
378 self.send_response(200)
379 self.send_header('Content-type', 'text/html')
380 self.send_header('Cache-Control', 'max-age=60, no-store')
381 self.end_headers()
382
maruel@google.come250a9b2009-03-10 17:39:46 +0000383 self.wfile.write('<html><head><title>%s</title></head></html>' %
384 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000385
386 return True
387
388
389 def CacheNoTransformHandler(self):
390 """This request handler yields a page with the title set to the current
391 system time, and does not allow the content to transformed during
392 user-agent caching"""
393
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000394 if not self._ShouldHandleRequest("/cache/no-transform"):
initial.commit94958cf2008-07-26 22:42:52 +0000395 return False
396
397 self.send_response(200)
398 self.send_header('Content-type', 'text/html')
399 self.send_header('Cache-Control', 'no-transform')
400 self.end_headers()
401
maruel@google.come250a9b2009-03-10 17:39:46 +0000402 self.wfile.write('<html><head><title>%s</title></head></html>' %
403 time.time())
initial.commit94958cf2008-07-26 22:42:52 +0000404
405 return True
406
407 def EchoHeader(self):
408 """This handler echoes back the value of a specific request header."""
409
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000410 if not self._ShouldHandleRequest("/echoheader"):
initial.commit94958cf2008-07-26 22:42:52 +0000411 return False
412
413 query_char = self.path.find('?')
414 if query_char != -1:
415 header_name = self.path[query_char+1:]
416
417 self.send_response(200)
418 self.send_header('Content-type', 'text/plain')
419 self.send_header('Cache-control', 'max-age=60000')
420 # insert a vary header to properly indicate that the cachability of this
421 # request is subject to value of the request header being echoed.
422 if len(header_name) > 0:
423 self.send_header('Vary', header_name)
424 self.end_headers()
425
426 if len(header_name) > 0:
427 self.wfile.write(self.headers.getheader(header_name))
428
429 return True
430
431 def EchoHandler(self):
432 """This handler just echoes back the payload of the request, for testing
433 form submission."""
434
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000435 if not self._ShouldHandleRequest("/echo"):
initial.commit94958cf2008-07-26 22:42:52 +0000436 return False
437
438 self.send_response(200)
439 self.send_header('Content-type', 'text/html')
440 self.end_headers()
441 length = int(self.headers.getheader('content-length'))
442 request = self.rfile.read(length)
443 self.wfile.write(request)
444 return True
445
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000446 def WriteFile(self):
447 """This is handler dumps the content of POST request to a disk file into
448 the data_dir/dump. Sub-directories are not supported."""
maruel@chromium.org756cf982009-03-05 12:46:38 +0000449
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000450 prefix='/writefile/'
451 if not self.path.startswith(prefix):
452 return False
maruel@chromium.org756cf982009-03-05 12:46:38 +0000453
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000454 file_name = self.path[len(prefix):]
455
456 # do not allow fancy chars in file name
457 re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
458 if len(file_name) and file_name[0] != '.':
459 path = os.path.join(self.server.data_dir, 'dump', file_name);
460 length = int(self.headers.getheader('content-length'))
461 request = self.rfile.read(length)
462 f = open(path, "wb")
463 f.write(request);
464 f.close()
maruel@chromium.org756cf982009-03-05 12:46:38 +0000465
stoyan@chromium.org372692c2009-01-30 17:01:52 +0000466 self.send_response(200)
467 self.send_header('Content-type', 'text/html')
468 self.end_headers()
469 self.wfile.write('<html>%s</html>' % file_name)
470 return True
maruel@chromium.org756cf982009-03-05 12:46:38 +0000471
initial.commit94958cf2008-07-26 22:42:52 +0000472 def EchoTitleHandler(self):
473 """This handler is like Echo, but sets the page title to the request."""
474
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000475 if not self._ShouldHandleRequest("/echotitle"):
initial.commit94958cf2008-07-26 22:42:52 +0000476 return False
477
478 self.send_response(200)
479 self.send_header('Content-type', 'text/html')
480 self.end_headers()
481 length = int(self.headers.getheader('content-length'))
482 request = self.rfile.read(length)
483 self.wfile.write('<html><head><title>')
484 self.wfile.write(request)
485 self.wfile.write('</title></head></html>')
486 return True
487
488 def EchoAllHandler(self):
489 """This handler yields a (more) human-readable page listing information
490 about the request header & contents."""
491
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000492 if not self._ShouldHandleRequest("/echoall"):
initial.commit94958cf2008-07-26 22:42:52 +0000493 return False
494
495 self.send_response(200)
496 self.send_header('Content-type', 'text/html')
497 self.end_headers()
498 self.wfile.write('<html><head><style>'
499 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
500 '</style></head><body>'
501 '<div style="float: right">'
502 '<a href="http://localhost:8888/echo">back to referring page</a></div>'
503 '<h1>Request Body:</h1><pre>')
initial.commit94958cf2008-07-26 22:42:52 +0000504
ericroman@google.coma47622b2008-11-15 04:36:51 +0000505 if self.command == 'POST':
506 length = int(self.headers.getheader('content-length'))
507 qs = self.rfile.read(length)
508 params = cgi.parse_qs(qs, keep_blank_values=1)
509
510 for param in params:
511 self.wfile.write('%s=%s\n' % (param, params[param][0]))
initial.commit94958cf2008-07-26 22:42:52 +0000512
513 self.wfile.write('</pre>')
514
515 self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
516
517 self.wfile.write('</body></html>')
518 return True
519
520 def DownloadHandler(self):
521 """This handler sends a downloadable file with or without reporting
522 the size (6K)."""
523
524 if self.path.startswith("/download-unknown-size"):
525 send_length = False
526 elif self.path.startswith("/download-known-size"):
527 send_length = True
528 else:
529 return False
530
531 #
532 # The test which uses this functionality is attempting to send
533 # small chunks of data to the client. Use a fairly large buffer
534 # so that we'll fill chrome's IO buffer enough to force it to
535 # actually write the data.
536 # See also the comments in the client-side of this test in
537 # download_uitest.cc
538 #
539 size_chunk1 = 35*1024
540 size_chunk2 = 10*1024
541
542 self.send_response(200)
543 self.send_header('Content-type', 'application/octet-stream')
544 self.send_header('Cache-Control', 'max-age=0')
545 if send_length:
546 self.send_header('Content-Length', size_chunk1 + size_chunk2)
547 self.end_headers()
548
549 # First chunk of data:
550 self.wfile.write("*" * size_chunk1)
551 self.wfile.flush()
552
553 # handle requests until one of them clears this flag.
554 self.server.waitForDownload = True
555 while self.server.waitForDownload:
556 self.server.handle_request()
557
558 # Second chunk of data:
559 self.wfile.write("*" * size_chunk2)
560 return True
561
562 def DownloadFinishHandler(self):
563 """This handler just tells the server to finish the current download."""
564
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000565 if not self._ShouldHandleRequest("/download-finish"):
initial.commit94958cf2008-07-26 22:42:52 +0000566 return False
567
568 self.server.waitForDownload = False
569 self.send_response(200)
570 self.send_header('Content-type', 'text/html')
571 self.send_header('Cache-Control', 'max-age=0')
572 self.end_headers()
573 return True
574
575 def FileHandler(self):
576 """This handler sends the contents of the requested file. Wow, it's like
577 a real webserver!"""
578
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000579 prefix = self.server.file_root_url
initial.commit94958cf2008-07-26 22:42:52 +0000580 if not self.path.startswith(prefix):
581 return False
582
darin@chromium.orgc25e5702009-07-23 19:10:23 +0000583 # Consume a request body if present.
584 if self.command == 'POST':
585 self.rfile.read(int(self.headers.getheader('content-length')))
586
initial.commit94958cf2008-07-26 22:42:52 +0000587 file = self.path[len(prefix):]
588 entries = file.split('/');
589 path = os.path.join(self.server.data_dir, *entries)
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +0000590 if os.path.isdir(path):
591 path = os.path.join(path, 'index.html')
initial.commit94958cf2008-07-26 22:42:52 +0000592
593 if not os.path.isfile(path):
594 print "File not found " + file + " full path:" + path
595 self.send_error(404)
596 return True
597
598 f = open(path, "rb")
599 data = f.read()
600 f.close()
601
602 # If file.mock-http-headers exists, it contains the headers we
603 # should send. Read them in and parse them.
604 headers_path = path + '.mock-http-headers'
605 if os.path.isfile(headers_path):
606 f = open(headers_path, "r")
607
608 # "HTTP/1.1 200 OK"
609 response = f.readline()
610 status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
611 self.send_response(int(status_code))
612
613 for line in f:
614 # "name: value"
615 name, value = re.findall('(\S+):\s*(.*)', line)[0]
616 self.send_header(name, value)
617 f.close()
618 else:
619 # Could be more generic once we support mime-type sniffing, but for
620 # now we need to set it explicitly.
621 self.send_response(200)
622 self.send_header('Content-type', self.GetMIMETypeFromName(file))
623 self.send_header('Content-Length', len(data))
624 self.end_headers()
625
626 self.wfile.write(data)
627
628 return True
629
630 def RealFileWithCommonHeaderHandler(self):
631 """This handler sends the contents of the requested file without the pseudo
632 http head!"""
633
634 prefix='/realfiles/'
635 if not self.path.startswith(prefix):
636 return False
637
638 file = self.path[len(prefix):]
639 path = os.path.join(self.server.data_dir, file)
640
641 try:
642 f = open(path, "rb")
643 data = f.read()
644 f.close()
645
646 # just simply set the MIME as octal stream
647 self.send_response(200)
648 self.send_header('Content-type', 'application/octet-stream')
649 self.end_headers()
650
651 self.wfile.write(data)
652 except:
653 self.send_error(404)
654
655 return True
656
657 def RealBZ2FileWithCommonHeaderHandler(self):
658 """This handler sends the bzip2 contents of the requested file with
659 corresponding Content-Encoding field in http head!"""
660
661 prefix='/realbz2files/'
662 if not self.path.startswith(prefix):
663 return False
664
665 parts = self.path.split('?')
666 file = parts[0][len(prefix):]
667 path = os.path.join(self.server.data_dir, file) + '.bz2'
668
669 if len(parts) > 1:
670 options = parts[1]
671 else:
672 options = ''
673
674 try:
675 self.send_response(200)
676 accept_encoding = self.headers.get("Accept-Encoding")
677 if accept_encoding.find("bzip2") != -1:
678 f = open(path, "rb")
679 data = f.read()
680 f.close()
681 self.send_header('Content-Encoding', 'bzip2')
682 self.send_header('Content-type', 'application/x-bzip2')
683 self.end_headers()
684 if options == 'incremental-header':
685 self.wfile.write(data[:1])
686 self.wfile.flush()
687 time.sleep(1.0)
688 self.wfile.write(data[1:])
689 else:
690 self.wfile.write(data)
691 else:
692 """client do not support bzip2 format, send pseudo content
693 """
694 self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
695 self.end_headers()
696 self.wfile.write("you do not support bzip2 encoding")
697 except:
698 self.send_error(404)
699
700 return True
701
702 def AuthBasicHandler(self):
703 """This handler tests 'Basic' authentication. It just sends a page with
704 title 'user/pass' if you succeed."""
705
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000706 if not self._ShouldHandleRequest("/auth-basic"):
initial.commit94958cf2008-07-26 22:42:52 +0000707 return False
708
709 username = userpass = password = b64str = ""
710
ericroman@google.com239b4d82009-03-27 04:00:22 +0000711 set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
712
initial.commit94958cf2008-07-26 22:42:52 +0000713 auth = self.headers.getheader('authorization')
714 try:
715 if not auth:
716 raise Exception('no auth')
717 b64str = re.findall(r'Basic (\S+)', auth)[0]
718 userpass = base64.b64decode(b64str)
719 username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
720 if password != 'secret':
721 raise Exception('wrong password')
722 except Exception, e:
723 # Authentication failed.
724 self.send_response(401)
725 self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
726 self.send_header('Content-type', 'text/html')
ericroman@google.com239b4d82009-03-27 04:00:22 +0000727 if set_cookie_if_challenged:
728 self.send_header('Set-Cookie', 'got_challenged=true')
initial.commit94958cf2008-07-26 22:42:52 +0000729 self.end_headers()
730 self.wfile.write('<html><head>')
731 self.wfile.write('<title>Denied: %s</title>' % e)
732 self.wfile.write('</head><body>')
733 self.wfile.write('auth=%s<p>' % auth)
734 self.wfile.write('b64str=%s<p>' % b64str)
735 self.wfile.write('username: %s<p>' % username)
736 self.wfile.write('userpass: %s<p>' % userpass)
737 self.wfile.write('password: %s<p>' % password)
738 self.wfile.write('You sent:<br>%s<p>' % self.headers)
739 self.wfile.write('</body></html>')
740 return True
741
742 # Authentication successful. (Return a cachable response to allow for
743 # testing cached pages that require authentication.)
744 if_none_match = self.headers.getheader('if-none-match')
745 if if_none_match == "abc":
746 self.send_response(304)
747 self.end_headers()
748 else:
749 self.send_response(200)
750 self.send_header('Content-type', 'text/html')
751 self.send_header('Cache-control', 'max-age=60000')
752 self.send_header('Etag', 'abc')
753 self.end_headers()
754 self.wfile.write('<html><head>')
755 self.wfile.write('<title>%s/%s</title>' % (username, password))
756 self.wfile.write('</head><body>')
757 self.wfile.write('auth=%s<p>' % auth)
ericroman@google.com239b4d82009-03-27 04:00:22 +0000758 self.wfile.write('You sent:<br>%s<p>' % self.headers)
initial.commit94958cf2008-07-26 22:42:52 +0000759 self.wfile.write('</body></html>')
760
761 return True
762
763 def AuthDigestHandler(self):
764 """This handler tests 'Digest' authentication. It just sends a page with
765 title 'user/pass' if you succeed."""
766
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000767 if not self._ShouldHandleRequest("/auth-digest"):
initial.commit94958cf2008-07-26 22:42:52 +0000768 return False
769
770 # Periodically generate a new nonce. Technically we should incorporate
771 # the request URL into this, but we don't care for testing.
772 nonce_life = 10
773 stale = False
maruel@google.come250a9b2009-03-10 17:39:46 +0000774 if (not self.server.nonce or
775 (time.time() - self.server.nonce_time > nonce_life)):
initial.commit94958cf2008-07-26 22:42:52 +0000776 if self.server.nonce:
777 stale = True
778 self.server.nonce_time = time.time()
779 self.server.nonce = \
maruel@google.come250a9b2009-03-10 17:39:46 +0000780 _new_md5(time.ctime(self.server.nonce_time) +
781 'privatekey').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000782
783 nonce = self.server.nonce
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000784 opaque = _new_md5('opaque').hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000785 password = 'secret'
786 realm = 'testrealm'
787
788 auth = self.headers.getheader('authorization')
789 pairs = {}
790 try:
791 if not auth:
792 raise Exception('no auth')
793 if not auth.startswith('Digest'):
794 raise Exception('not digest')
795 # Pull out all the name="value" pairs as a dictionary.
796 pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
797
798 # Make sure it's all valid.
799 if pairs['nonce'] != nonce:
800 raise Exception('wrong nonce')
801 if pairs['opaque'] != opaque:
802 raise Exception('wrong opaque')
803
804 # Check the 'response' value and make sure it matches our magic hash.
805 # See http://www.ietf.org/rfc/rfc2617.txt
maruel@google.come250a9b2009-03-10 17:39:46 +0000806 hash_a1 = _new_md5(
807 ':'.join([pairs['username'], realm, password])).hexdigest()
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000808 hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000809 if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000810 response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
initial.commit94958cf2008-07-26 22:42:52 +0000811 pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
812 else:
thomasvl@chromium.org595be8d2009-03-06 19:59:26 +0000813 response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
initial.commit94958cf2008-07-26 22:42:52 +0000814
815 if pairs['response'] != response:
816 raise Exception('wrong password')
817 except Exception, e:
818 # Authentication failed.
819 self.send_response(401)
820 hdr = ('Digest '
821 'realm="%s", '
822 'domain="/", '
823 'qop="auth", '
824 'algorithm=MD5, '
825 'nonce="%s", '
826 'opaque="%s"') % (realm, nonce, opaque)
827 if stale:
828 hdr += ', stale="TRUE"'
829 self.send_header('WWW-Authenticate', hdr)
830 self.send_header('Content-type', 'text/html')
831 self.end_headers()
832 self.wfile.write('<html><head>')
833 self.wfile.write('<title>Denied: %s</title>' % e)
834 self.wfile.write('</head><body>')
835 self.wfile.write('auth=%s<p>' % auth)
836 self.wfile.write('pairs=%s<p>' % pairs)
837 self.wfile.write('You sent:<br>%s<p>' % self.headers)
838 self.wfile.write('We are replying:<br>%s<p>' % hdr)
839 self.wfile.write('</body></html>')
840 return True
841
842 # Authentication successful.
843 self.send_response(200)
844 self.send_header('Content-type', 'text/html')
845 self.end_headers()
846 self.wfile.write('<html><head>')
847 self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
848 self.wfile.write('</head><body>')
849 self.wfile.write('auth=%s<p>' % auth)
850 self.wfile.write('pairs=%s<p>' % pairs)
851 self.wfile.write('</body></html>')
852
853 return True
854
855 def SlowServerHandler(self):
856 """Wait for the user suggested time before responding. The syntax is
857 /slow?0.5 to wait for half a second."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000858 if not self._ShouldHandleRequest("/slow"):
initial.commit94958cf2008-07-26 22:42:52 +0000859 return False
860 query_char = self.path.find('?')
861 wait_sec = 1.0
862 if query_char >= 0:
863 try:
864 wait_sec = int(self.path[query_char + 1:])
865 except ValueError:
866 pass
867 time.sleep(wait_sec)
868 self.send_response(200)
869 self.send_header('Content-type', 'text/plain')
870 self.end_headers()
871 self.wfile.write("waited %d seconds" % wait_sec)
872 return True
873
874 def ContentTypeHandler(self):
875 """Returns a string of html with the given content type. E.g.,
876 /contenttype?text/css returns an html file with the Content-Type
877 header set to text/css."""
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000878 if not self._ShouldHandleRequest("/contenttype"):
initial.commit94958cf2008-07-26 22:42:52 +0000879 return False
880 query_char = self.path.find('?')
881 content_type = self.path[query_char + 1:].strip()
882 if not content_type:
883 content_type = 'text/html'
884 self.send_response(200)
885 self.send_header('Content-Type', content_type)
886 self.end_headers()
887 self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
888 return True
889
890 def ServerRedirectHandler(self):
891 """Sends a server redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000892 '/server-redirect?http://foo.bar/asdf' to redirect to
893 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000894
895 test_name = "/server-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000896 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000897 return False
898
899 query_char = self.path.find('?')
900 if query_char < 0 or len(self.path) <= query_char + 1:
901 self.sendRedirectHelp(test_name)
902 return True
903 dest = self.path[query_char + 1:]
904
905 self.send_response(301) # moved permanently
906 self.send_header('Location', dest)
907 self.send_header('Content-type', 'text/html')
908 self.end_headers()
909 self.wfile.write('<html><head>')
910 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
911
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000912 return True
initial.commit94958cf2008-07-26 22:42:52 +0000913
914 def ClientRedirectHandler(self):
915 """Sends a client redirect to the given URL. The syntax is
maruel@google.come250a9b2009-03-10 17:39:46 +0000916 '/client-redirect?http://foo.bar/asdf' to redirect to
917 'http://foo.bar/asdf'"""
initial.commit94958cf2008-07-26 22:42:52 +0000918
919 test_name = "/client-redirect"
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +0000920 if not self._ShouldHandleRequest(test_name):
initial.commit94958cf2008-07-26 22:42:52 +0000921 return False
922
923 query_char = self.path.find('?');
924 if query_char < 0 or len(self.path) <= query_char + 1:
925 self.sendRedirectHelp(test_name)
926 return True
927 dest = self.path[query_char + 1:]
928
929 self.send_response(200)
930 self.send_header('Content-type', 'text/html')
931 self.end_headers()
932 self.wfile.write('<html><head>')
933 self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
934 self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
935
936 return True
937
938 def DefaultResponseHandler(self):
939 """This is the catch-all response handler for requests that aren't handled
940 by one of the special handlers above.
941 Note that we specify the content-length as without it the https connection
942 is not closed properly (and the browser keeps expecting data)."""
943
944 contents = "Default response given for path: " + self.path
945 self.send_response(200)
946 self.send_header('Content-type', 'text/html')
947 self.send_header("Content-Length", len(contents))
948 self.end_headers()
949 self.wfile.write(contents)
950 return True
951
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000952 def RedirectConnectHandler(self):
953 """Sends a redirect to the CONNECT request for www.redirect.com. This
954 response is not specified by the RFC, so the browser should not follow
955 the redirect."""
956
957 if (self.path.find("www.redirect.com") < 0):
958 return False
959
960 dest = "http://www.destination.com/foo.js"
961
962 self.send_response(302) # moved temporarily
963 self.send_header('Location', dest)
964 self.send_header('Connection', 'close')
965 self.end_headers()
966 return True
967
wtc@chromium.orgb86c7f92009-02-14 01:45:08 +0000968 def ServerAuthConnectHandler(self):
969 """Sends a 401 to the CONNECT request for www.server-auth.com. This
970 response doesn't make sense because the proxy server cannot request
971 server authentication."""
972
973 if (self.path.find("www.server-auth.com") < 0):
974 return False
975
976 challenge = 'Basic realm="WallyWorld"'
977
978 self.send_response(401) # unauthorized
979 self.send_header('WWW-Authenticate', challenge)
980 self.send_header('Connection', 'close')
981 self.end_headers()
982 return True
wtc@chromium.org743d77b2009-02-11 02:48:15 +0000983
984 def DefaultConnectResponseHandler(self):
985 """This is the catch-all response handler for CONNECT requests that aren't
986 handled by one of the special handlers above. Real Web servers respond
987 with 400 to CONNECT requests."""
988
989 contents = "Your client has issued a malformed or illegal request."
990 self.send_response(400) # bad request
991 self.send_header('Content-type', 'text/html')
992 self.send_header("Content-Length", len(contents))
993 self.end_headers()
994 self.wfile.write(contents)
995 return True
996
997 def do_CONNECT(self):
998 for handler in self._connect_handlers:
999 if handler():
1000 return
1001
initial.commit94958cf2008-07-26 22:42:52 +00001002 def do_GET(self):
1003 for handler in self._get_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001004 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001005 return
1006
1007 def do_POST(self):
1008 for handler in self._post_handlers:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001009 if handler():
initial.commit94958cf2008-07-26 22:42:52 +00001010 return
1011
1012 # called by the redirect handling function when there is no parameter
1013 def sendRedirectHelp(self, redirect_name):
1014 self.send_response(200)
1015 self.send_header('Content-type', 'text/html')
1016 self.end_headers()
1017 self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1018 self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1019 self.wfile.write('</body></html>')
1020
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001021def MakeDumpDir(data_dir):
1022 """Create directory named 'dump' where uploaded data via HTTP POST request
1023 will be stored. If the directory already exists all files and subdirectories
1024 will be deleted."""
1025 dump_dir = os.path.join(data_dir, 'dump');
1026 if os.path.isdir(dump_dir):
1027 shutil.rmtree(dump_dir)
1028 os.mkdir(dump_dir)
1029
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001030def MakeDataDir():
1031 if options.data_dir:
1032 if not os.path.isdir(options.data_dir):
1033 print 'specified data dir not found: ' + options.data_dir + ' exiting...'
1034 return None
1035 my_data_dir = options.data_dir
1036 else:
1037 # Create the default path to our data dir, relative to the exe dir.
1038 my_data_dir = os.path.dirname(sys.argv[0])
1039 my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
1040 "test", "data")
1041
1042 #TODO(ibrar): Must use Find* funtion defined in google\tools
1043 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1044
1045 return my_data_dir
1046
initial.commit94958cf2008-07-26 22:42:52 +00001047def main(options, args):
1048 # redirect output to a log file so it doesn't spam the unit test output
1049 logfile = open('testserver.log', 'w')
1050 sys.stderr = sys.stdout = logfile
1051
1052 port = options.port
1053
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001054 if options.server_type == SERVER_HTTP:
1055 if options.cert:
1056 # let's make sure the cert file exists.
1057 if not os.path.isfile(options.cert):
1058 print 'specified cert file not found: ' + options.cert + ' exiting...'
1059 return
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001060 if options.forking:
1061 server_class = ForkingHTTPSServer
1062 else:
1063 server_class = HTTPSServer
1064 server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001065 print 'HTTPS server started on port %d...' % port
1066 else:
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001067 if options.forking:
1068 server_class = ForkingHTTPServer
1069 else:
1070 server_class = StoppableHTTPServer
1071 server = server_class(('127.0.0.1', port), TestPageHandler)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001072 print 'HTTP server started on port %d...' % port
erikkay@google.com70397b62008-12-30 21:49:21 +00001073
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001074 server.data_dir = MakeDataDir()
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001075 server.file_root_url = options.file_root_url
stoyan@chromium.org372692c2009-01-30 17:01:52 +00001076 MakeDumpDir(server.data_dir)
maruel@chromium.org756cf982009-03-05 12:46:38 +00001077
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001078 # means FTP Server
nsylvain@chromium.org8d5763b2008-12-30 23:44:27 +00001079 else:
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001080 my_data_dir = MakeDataDir()
1081
1082 def line_logger(msg):
1083 if (msg.find("kill") >= 0):
1084 server.stop = True
1085 print 'shutting down server'
1086 sys.exit(0)
1087
1088 # Instantiate a dummy authorizer for managing 'virtual' users
1089 authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1090
1091 # Define a new user having full r/w permissions and a read-only
1092 # anonymous user
1093 authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1094
1095 authorizer.add_anonymous(my_data_dir)
1096
1097 # Instantiate FTP handler class
1098 ftp_handler = pyftpdlib.ftpserver.FTPHandler
1099 ftp_handler.authorizer = authorizer
1100 pyftpdlib.ftpserver.logline = line_logger
1101
1102 # Define a customized banner (string returned when client connects)
maruel@google.come250a9b2009-03-10 17:39:46 +00001103 ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1104 pyftpdlib.ftpserver.__ver__)
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001105
1106 # Instantiate FTP server class and listen to 127.0.0.1:port
1107 address = ('127.0.0.1', port)
1108 server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
1109 print 'FTP server started on port %d...' % port
initial.commit94958cf2008-07-26 22:42:52 +00001110
1111 try:
1112 server.serve_forever()
1113 except KeyboardInterrupt:
1114 print 'shutting down server'
1115 server.stop = True
1116
1117if __name__ == '__main__':
1118 option_parser = optparse.OptionParser()
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001119 option_parser.add_option("-f", '--ftp', action='store_const',
1120 const=SERVER_FTP, default=SERVER_HTTP,
1121 dest='server_type',
1122 help='FTP or HTTP server default HTTP')
phajdan.jr@chromium.org599efb82009-08-14 22:37:35 +00001123 option_parser.add_option('--forking', action='store_true', default=False,
1124 dest='forking',
1125 help='Serve each request in a separate process')
initial.commit94958cf2008-07-26 22:42:52 +00001126 option_parser.add_option('', '--port', default='8888', type='int',
1127 help='Port used by the server')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001128 option_parser.add_option('', '--data-dir', dest='data_dir',
initial.commit94958cf2008-07-26 22:42:52 +00001129 help='Directory from which to read the files')
erikkay@google.comd5182ff2009-01-08 20:45:27 +00001130 option_parser.add_option('', '--https', dest='cert',
initial.commit94958cf2008-07-26 22:42:52 +00001131 help='Specify that https should be used, specify '
1132 'the path to the cert containing the private key '
1133 'the server should use')
ben@chromium.org0c7ac3a2009-04-10 02:37:22 +00001134 option_parser.add_option('', '--file-root-url', default='/files/',
1135 help='Specify a root URL for files served.')
initial.commit94958cf2008-07-26 22:42:52 +00001136 options, args = option_parser.parse_args()
1137
1138 sys.exit(main(options, args))