OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2010 Google Inc. All Rights Reserved. | 2 # Copyright 2010 Google Inc. All Rights Reserved. |
3 # | 3 # |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
5 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
6 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
7 # | 7 # |
8 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
9 # | 9 # |
10 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
(...skipping 21 matching lines...) Expand all Loading... |
32 resources not in the recorded archive. | 32 resources not in the recorded archive. |
33 | 33 |
34 Network simulation examples: | 34 Network simulation examples: |
35 # 128KByte/s uplink bandwidth, 4Mbps/s downlink bandwidth with 100ms RTT time | 35 # 128KByte/s uplink bandwidth, 4Mbps/s downlink bandwidth with 100ms RTT time |
36 $ sudo ./replay.py --up 128KByte/s --down 4Mbit/s --delay_ms=100 archive.wpr | 36 $ sudo ./replay.py --up 128KByte/s --down 4Mbit/s --delay_ms=100 archive.wpr |
37 | 37 |
38 # 1% packet loss rate | 38 # 1% packet loss rate |
39 $ sudo ./replay.py --packet_loss_rate=0.01 archive.wpr | 39 $ sudo ./replay.py --packet_loss_rate=0.01 archive.wpr |
40 """ | 40 """ |
41 | 41 |
| 42 import argparse |
42 import json | 43 import json |
43 import logging | 44 import logging |
44 import optparse | |
45 import os | 45 import os |
46 import socket | 46 import socket |
47 import sys | 47 import sys |
48 import traceback | 48 import traceback |
49 | 49 |
50 import customhandlers | 50 import customhandlers |
51 import dnsproxy | 51 import dnsproxy |
52 import httparchive | 52 import httparchive |
53 import httpclient | 53 import httpclient |
54 import httpproxy | 54 import httpproxy |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
170 server_manager.AppendTrafficShaper( | 170 server_manager.AppendTrafficShaper( |
171 trafficshaper.TrafficShaper, host=host, | 171 trafficshaper.TrafficShaper, host=host, |
172 use_loopback=not options.server_mode and host == '127.0.0.1', | 172 use_loopback=not options.server_mode and host == '127.0.0.1', |
173 **options.shaping_dummynet) | 173 **options.shaping_dummynet) |
174 | 174 |
175 | 175 |
176 class OptionsWrapper(object): | 176 class OptionsWrapper(object): |
177 """Add checks, updates, and methods to option values. | 177 """Add checks, updates, and methods to option values. |
178 | 178 |
179 Example: | 179 Example: |
180 options, args = option_parser.parse_args() | 180 options, args = arg_parser.parse_args() |
181 options = OptionsWrapper(options, option_parser) # run checks and updates | 181 options = OptionsWrapper(options, arg_parser) # run checks and updates |
182 if options.record and options.HasTrafficShaping(): | 182 if options.record and options.HasTrafficShaping(): |
183 [...] | 183 [...] |
184 """ | 184 """ |
185 _TRAFFICSHAPING_OPTIONS = { | 185 _TRAFFICSHAPING_OPTIONS = { |
186 'down', 'up', 'delay_ms', 'packet_loss_rate', 'init_cwnd', 'net'} | 186 'down', 'up', 'delay_ms', 'packet_loss_rate', 'init_cwnd', 'net'} |
187 _CONFLICTING_OPTIONS = ( | 187 _CONFLICTING_OPTIONS = ( |
188 ('record', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net', | 188 ('record', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net', |
189 'spdy', 'use_server_delay')), | 189 'spdy', 'use_server_delay')), |
190 ('append', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net', | 190 ('append', ('down', 'up', 'delay_ms', 'packet_loss_rate', 'net', |
191 'use_server_delay')), # same as --record | 191 'use_server_delay')), # same as --record |
192 ('net', ('down', 'up', 'delay_ms')), | 192 ('net', ('down', 'up', 'delay_ms')), |
193 ('server', ('server_mode',)), | 193 ('server', ('server_mode',)), |
194 ) | 194 ) |
195 | 195 |
196 def __init__(self, options, parser): | 196 def __init__(self, options, parser): |
197 self._options = options | 197 self._options = options |
198 self._parser = parser | 198 self._parser = parser |
199 self._nondefaults = set([ | 199 self._nondefaults = set([ |
200 name for name, value in parser.defaults.items() | 200 action.dest for action in parser._optionals._actions |
201 if getattr(options, name) != value]) | 201 if getattr(options, action.dest, action.default) is not action.default]) |
202 self._CheckConflicts() | 202 self._CheckConflicts() |
203 self._CheckValidIp('host') | 203 self._CheckValidIp('host') |
204 self._CheckFeatureSupport() | 204 self._CheckFeatureSupport() |
205 self._MassageValues() | 205 self._MassageValues() |
206 | 206 |
207 def _CheckConflicts(self): | 207 def _CheckConflicts(self): |
208 """Give an error if mutually exclusive options are used.""" | 208 """Give an error if mutually exclusive options are used.""" |
209 for option, bad_options in self._CONFLICTING_OPTIONS: | 209 for option, bad_options in self._CONFLICTING_OPTIONS: |
210 if option in self._nondefaults: | 210 if option in self._nondefaults: |
211 for bad_option in bad_options: | 211 for bad_option in bad_options: |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
366 except Exception: | 366 except Exception: |
367 logging.critical(traceback.format_exc()) | 367 logging.critical(traceback.format_exc()) |
368 exit_status = 2 | 368 exit_status = 2 |
369 | 369 |
370 if options.record: | 370 if options.record: |
371 http_archive.Persist(replay_filename) | 371 http_archive.Persist(replay_filename) |
372 logging.info('Saved %d responses to %s', len(http_archive), replay_filename) | 372 logging.info('Saved %d responses to %s', len(http_archive), replay_filename) |
373 return exit_status | 373 return exit_status |
374 | 374 |
375 | 375 |
376 def GetOptionParser(): | 376 def GetParser(): |
377 class PlainHelpFormatter(optparse.IndentedHelpFormatter): | 377 arg_parser = argparse.ArgumentParser( |
378 def format_description(self, description): | 378 usage='%(prog)s [options] replay_file', |
379 if description: | |
380 return description + '\n' | |
381 else: | |
382 return '' | |
383 option_parser = optparse.OptionParser( | |
384 usage='%prog [options] replay_file', | |
385 formatter=PlainHelpFormatter(), | |
386 description=__doc__, | 379 description=__doc__, |
| 380 formatter_class=argparse.RawDescriptionHelpFormatter, |
387 epilog='http://code.google.com/p/web-page-replay/') | 381 epilog='http://code.google.com/p/web-page-replay/') |
388 | 382 |
389 option_parser.add_option('-r', '--record', default=False, | 383 arg_parser.add_argument('replay_filename', type=str, help='Replay file', |
| 384 nargs='?') |
| 385 |
| 386 arg_parser.add_argument('-r', '--record', default=False, |
390 action='store_true', | 387 action='store_true', |
391 help='Download real responses and record them to replay_file') | 388 help='Download real responses and record them to replay_file') |
392 option_parser.add_option('--append', default=False, | 389 arg_parser.add_argument('--append', default=False, |
393 action='store_true', | 390 action='store_true', |
394 help='Append responses to replay_file.') | 391 help='Append responses to replay_file.') |
395 option_parser.add_option('-l', '--log_level', default='debug', | 392 arg_parser.add_argument('-l', '--log_level', default='debug', |
396 action='store', | 393 action='store', |
397 type='choice', | 394 type=str, |
398 choices=('debug', 'info', 'warning', 'error', 'critical'), | 395 choices=('debug', 'info', 'warning', 'error', 'critical'), |
399 help='Minimum verbosity level to log') | 396 help='Minimum verbosity level to log') |
400 option_parser.add_option('-f', '--log_file', default=None, | 397 arg_parser.add_argument('-f', '--log_file', default=None, |
401 action='store', | 398 action='store', |
402 type='string', | 399 type=str, |
403 help='Log file to use in addition to writting logs to stderr.') | 400 help='Log file to use in addition to writting logs to stderr.') |
404 | 401 |
405 network_group = optparse.OptionGroup(option_parser, | 402 network_group = arg_parser.add_argument_group( |
406 'Network Simulation Options', | 403 title='Network Simulation Options', |
407 'These options configure the network simulation in replay mode') | 404 description=('These options configure the network simulation in ' |
408 network_group.add_option('-u', '--up', default='0', | 405 'replay mode')) |
| 406 network_group.add_argument('-u', '--up', default='0', |
409 action='store', | 407 action='store', |
410 type='string', | 408 type=str, |
411 help='Upload Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.') | 409 help='Upload Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.') |
412 network_group.add_option('-d', '--down', default='0', | 410 network_group.add_argument('-d', '--down', default='0', |
413 action='store', | 411 action='store', |
414 type='string', | 412 type=str, |
415 help='Download Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.') | 413 help='Download Bandwidth in [K|M]{bit/s|Byte/s}. Zero means unlimited.') |
416 network_group.add_option('-m', '--delay_ms', default='0', | 414 network_group.add_argument('-m', '--delay_ms', default='0', |
417 action='store', | 415 action='store', |
418 type='string', | 416 type=str, |
419 help='Propagation delay (latency) in milliseconds. Zero means no delay.') | 417 help='Propagation delay (latency) in milliseconds. Zero means no delay.') |
420 network_group.add_option('-p', '--packet_loss_rate', default='0', | 418 network_group.add_argument('-p', '--packet_loss_rate', default='0', |
421 action='store', | 419 action='store', |
422 type='string', | 420 type=str, |
423 help='Packet loss rate in range [0..1]. Zero means no loss.') | 421 help='Packet loss rate in range [0..1]. Zero means no loss.') |
424 network_group.add_option('-w', '--init_cwnd', default='0', | 422 network_group.add_argument('-w', '--init_cwnd', default='0', |
425 action='store', | 423 action='store', |
426 type='string', | 424 type=str, |
427 help='Set initial cwnd (linux only, requires kernel patch)') | 425 help='Set initial cwnd (linux only, requires kernel patch)') |
428 network_group.add_option('--net', default=None, | 426 network_group.add_argument('--net', default=None, |
429 action='store', | 427 action='store', |
430 type='choice', | 428 type=str, |
431 choices=net_configs.NET_CONFIG_NAMES, | 429 choices=net_configs.NET_CONFIG_NAMES, |
432 help='Select a set of network options: %s.' % ', '.join( | 430 help='Select a set of network options: %s.' % ', '.join( |
433 net_configs.NET_CONFIG_NAMES)) | 431 net_configs.NET_CONFIG_NAMES)) |
434 network_group.add_option('--shaping_type', default='dummynet', | 432 network_group.add_argument('--shaping_type', default='dummynet', |
435 action='store', | 433 action='store', |
436 choices=('dummynet', 'proxy'), | 434 choices=('dummynet', 'proxy'), |
437 help='When shaping is configured (i.e. --up, --down, etc.) decides ' | 435 help='When shaping is configured (i.e. --up, --down, etc.) decides ' |
438 'whether to use |dummynet| (default), or |proxy| servers.') | 436 'whether to use |dummynet| (default), or |proxy| servers.') |
439 option_parser.add_option_group(network_group) | |
440 | 437 |
441 harness_group = optparse.OptionGroup(option_parser, | 438 harness_group = arg_parser.add_argument_group( |
442 'Replay Harness Options', | 439 title='Replay Harness Options', |
443 'These advanced options configure various aspects of the replay harness') | 440 description=('These advanced options configure various aspects ' |
444 harness_group.add_option('-S', '--server', default=None, | 441 'of the replay harness')) |
| 442 harness_group.add_argument('-S', '--server', default=None, |
445 action='store', | 443 action='store', |
446 type='string', | 444 type=str, |
447 help='IP address of host running "replay.py --server_mode". ' | 445 help='IP address of host running "replay.py --server_mode". ' |
448 'This only changes the primary DNS nameserver to use the given IP.') | 446 'This only changes the primary DNS nameserver to use the given IP.') |
449 harness_group.add_option('-M', '--server_mode', default=False, | 447 harness_group.add_argument('-M', '--server_mode', default=False, |
450 action='store_true', | 448 action='store_true', |
451 help='Run replay DNS & http proxies, and trafficshaping on --port ' | 449 help='Run replay DNS & http proxies, and trafficshaping on --port ' |
452 'without changing the primary DNS nameserver. ' | 450 'without changing the primary DNS nameserver. ' |
453 'Other hosts may connect to this using "replay.py --server" ' | 451 'Other hosts may connect to this using "replay.py --server" ' |
454 'or by pointing their DNS to this server.') | 452 'or by pointing their DNS to this server.') |
455 harness_group.add_option('-i', '--inject_scripts', default='deterministic.js', | 453 harness_group.add_argument('-i', '--inject_scripts', default='deterministic.js
', |
456 action='store', | 454 action='store', |
457 dest='inject_scripts', | 455 dest='inject_scripts', |
458 help='A comma separated list of JavaScript sources to inject in all ' | 456 help='A comma separated list of JavaScript sources to inject in all ' |
459 'pages. By default a script is injected that eliminates sources ' | 457 'pages. By default a script is injected that eliminates sources ' |
460 'of entropy such as Date() and Math.random() deterministic. ' | 458 'of entropy such as Date() and Math.random() deterministic. ' |
461 'CAUTION: Without deterministic.js, many pages will not replay.') | 459 'CAUTION: Without deterministic.js, many pages will not replay.') |
462 harness_group.add_option('-D', '--no-diff_unknown_requests', default=True, | 460 harness_group.add_argument('-D', '--no-diff_unknown_requests', default=True, |
463 action='store_false', | 461 action='store_false', |
464 dest='diff_unknown_requests', | 462 dest='diff_unknown_requests', |
465 help='During replay, do not show a diff of unknown requests against ' | 463 help='During replay, do not show a diff of unknown requests against ' |
466 'their nearest match in the archive.') | 464 'their nearest match in the archive.') |
467 harness_group.add_option('-C', '--use_closest_match', default=False, | 465 harness_group.add_argument('-C', '--use_closest_match', default=False, |
468 action='store_true', | 466 action='store_true', |
469 dest='use_closest_match', | 467 dest='use_closest_match', |
470 help='During replay, if a request is not found, serve the closest match' | 468 help='During replay, if a request is not found, serve the closest match' |
471 'in the archive instead of giving a 404.') | 469 'in the archive instead of giving a 404.') |
472 harness_group.add_option('-U', '--use_server_delay', default=False, | 470 harness_group.add_argument('-U', '--use_server_delay', default=False, |
473 action='store_true', | 471 action='store_true', |
474 dest='use_server_delay', | 472 dest='use_server_delay', |
475 help='During replay, simulate server delay by delaying response time to' | 473 help='During replay, simulate server delay by delaying response time to' |
476 'requests.') | 474 'requests.') |
477 harness_group.add_option('-I', '--screenshot_dir', default=None, | 475 harness_group.add_argument('-I', '--screenshot_dir', default=None, |
478 action='store', | 476 action='store', |
479 type='string', | 477 type=str, |
480 help='Save PNG images of the loaded page in the given directory.') | 478 help='Save PNG images of the loaded page in the given directory.') |
481 harness_group.add_option('-P', '--no-dns_private_passthrough', default=True, | 479 harness_group.add_argument('-P', '--no-dns_private_passthrough', default=True, |
482 action='store_false', | 480 action='store_false', |
483 dest='dns_private_passthrough', | 481 dest='dns_private_passthrough', |
484 help='Don\'t forward DNS requests that resolve to private network ' | 482 help='Don\'t forward DNS requests that resolve to private network ' |
485 'addresses. CAUTION: With this option important services like ' | 483 'addresses. CAUTION: With this option important services like ' |
486 'Kerberos will resolve to the HTTP proxy address.') | 484 'Kerberos will resolve to the HTTP proxy address.') |
487 harness_group.add_option('-x', '--no-dns_forwarding', default=True, | 485 harness_group.add_argument('-x', '--no-dns_forwarding', default=True, |
488 action='store_false', | 486 action='store_false', |
489 dest='dns_forwarding', | 487 dest='dns_forwarding', |
490 help='Don\'t forward DNS requests to the local replay server. ' | 488 help='Don\'t forward DNS requests to the local replay server. ' |
491 'CAUTION: With this option an external mechanism must be used to ' | 489 'CAUTION: With this option an external mechanism must be used to ' |
492 'forward traffic to the replay server.') | 490 'forward traffic to the replay server.') |
493 harness_group.add_option('--host', default=None, | 491 harness_group.add_argument('--host', default=None, |
494 action='store', | 492 action='store', |
495 type='str', | 493 type=str, |
496 help='The IP address to bind all servers to. Defaults to 0.0.0.0 or ' | 494 help='The IP address to bind all servers to. Defaults to 0.0.0.0 or ' |
497 '127.0.0.1, depending on --server_mode and platform.') | 495 '127.0.0.1, depending on --server_mode and platform.') |
498 harness_group.add_option('-o', '--port', default=80, | 496 harness_group.add_argument('-o', '--port', default=80, |
499 action='store', | 497 action='store', |
500 type='int', | 498 type=int, |
501 help='Port number to listen on.') | 499 help='Port number to listen on.') |
502 harness_group.add_option('--ssl_port', default=443, | 500 harness_group.add_argument('--ssl_port', default=443, |
503 action='store', | 501 action='store', |
504 type='int', | 502 type=int, |
505 help='SSL port number to listen on.') | 503 help='SSL port number to listen on.') |
506 harness_group.add_option('--http_to_https_port', default=None, | 504 harness_group.add_argument('--http_to_https_port', default=None, |
507 action='store', | 505 action='store', |
508 type='int', | 506 type=int, |
509 help='Port on which WPR will listen for HTTP requests that it will send ' | 507 help='Port on which WPR will listen for HTTP requests that it will send ' |
510 'along as HTTPS requests.') | 508 'along as HTTPS requests.') |
511 harness_group.add_option('--dns_port', default=53, | 509 harness_group.add_argument('--dns_port', default=53, |
512 action='store', | 510 action='store', |
513 type='int', | 511 type=int, |
514 help='DNS port number to listen on.') | 512 help='DNS port number to listen on.') |
515 harness_group.add_option('-c', '--https_root_ca_cert_path', default=None, | 513 harness_group.add_argument('-c', '--https_root_ca_cert_path', default=None, |
516 action='store', | 514 action='store', |
517 type='string', | 515 type=str, |
518 help='Certificate file to use with SSL (gets auto-generated if needed).') | 516 help='Certificate file to use with SSL (gets auto-generated if needed).') |
519 harness_group.add_option('--no-ssl', default=True, | 517 harness_group.add_argument('--no-ssl', default=True, |
520 action='store_false', | 518 action='store_false', |
521 dest='ssl', | 519 dest='ssl', |
522 help='Do not setup an SSL proxy.') | 520 help='Do not setup an SSL proxy.') |
523 option_parser.add_option_group(harness_group) | 521 harness_group.add_argument('--should_generate_certs', default=False, |
524 harness_group.add_option('--should_generate_certs', default=False, | |
525 action='store_true', | 522 action='store_true', |
526 help='Use OpenSSL to generate certificate files for requested hosts.') | 523 help='Use OpenSSL to generate certificate files for requested hosts.') |
527 harness_group.add_option('--no-admin-check', default=True, | 524 harness_group.add_argument('--no-admin-check', default=True, |
528 action='store_false', | 525 action='store_false', |
529 dest='admin_check', | 526 dest='admin_check', |
530 help='Do not check if administrator access is needed.') | 527 help='Do not check if administrator access is needed.') |
531 harness_group.add_option('--scramble_images', default=False, | 528 harness_group.add_argument('--scramble_images', default=False, |
532 action='store_true', | 529 action='store_true', |
533 dest='scramble_images', | 530 dest='scramble_images', |
534 help='Scramble image responses.') | 531 help='Scramble image responses.') |
535 harness_group.add_option('--rules_path', default=None, | 532 harness_group.add_argument('--rules_path', default=None, |
536 action='store', | 533 action='store', |
537 help='Path of file containing Python rules.') | 534 help='Path of file containing Python rules.') |
538 harness_group.add_option('--allowed_rule_imports', default='rules', | 535 harness_group.add_argument('--allowed_rule_imports', default='rules', |
539 action='store', | 536 action='store', |
540 help='A comma-separate list of allowed rule imports, or \'*\' to allow' | 537 help='A comma-separate list of allowed rule imports, or \'*\' to allow' |
541 ' all packages. Defaults to \'%default\'.') | 538 ' all packages. Defaults to %(default)s.') |
542 return option_parser | 539 return arg_parser |
543 | 540 |
544 | 541 |
545 def main(): | 542 def main(): |
546 option_parser = GetOptionParser() | 543 arg_parser = GetParser() |
547 options, args = option_parser.parse_args() | 544 options = arg_parser.parse_args() |
548 options = OptionsWrapper(options, option_parser) | 545 options = OptionsWrapper(options, arg_parser) |
549 | 546 |
550 if options.server: | 547 if options.server: |
551 replay_filename = None | 548 options.replay_filename = None |
552 elif len(args) != 1: | 549 elif options.replay_filename is None: |
553 option_parser.error('Must specify a replay_file') | 550 arg_parser.error('Must specify a replay_file') |
554 else: | 551 return replay(options, options.replay_filename) |
555 replay_filename = args[0] | |
556 | |
557 return replay(options, replay_filename) | |
558 | 552 |
559 | 553 |
560 if __name__ == '__main__': | 554 if __name__ == '__main__': |
561 sys.exit(main()) | 555 sys.exit(main()) |
OLD | NEW |