| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2010 Google Inc. All Rights Reserved. | |
| 3 # | |
| 4 # Licensed under the Apache License, Version 2.0 (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 | |
| 7 # | |
| 8 # http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 # | |
| 10 # Unless required by applicable law or agreed to in writing, software | |
| 11 # distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 # See the License for the specific language governing permissions and | |
| 14 # limitations under the License. | |
| 15 | |
| 16 """Handle special HTTP requests. | |
| 17 | |
| 18 /web-page-replay-generate-[RESPONSE_CODE] | |
| 19 - Return the given RESPONSE_CODE. | |
| 20 /web-page-replay-post-image-[FILENAME] | |
| 21 - Save the posted image to local disk. | |
| 22 /web-page-replay-command-[record|replay|status] | |
| 23 - Optional. Enable by calling custom_handlers.add_server_manager_handler(...). | |
| 24 - Change the server mode to either record or replay. | |
| 25 + When switching to record, the http_archive is cleared. | |
| 26 + When switching to replay, the http_archive is maintained. | |
| 27 """ | |
| 28 | |
| 29 import base64 | |
| 30 import httparchive | |
| 31 import json | |
| 32 import logging | |
| 33 import os | |
| 34 | |
| 35 COMMON_URL_PREFIX = '/web-page-replay-' | |
| 36 COMMAND_URL_PREFIX = COMMON_URL_PREFIX + 'command-' | |
| 37 GENERATOR_URL_PREFIX = COMMON_URL_PREFIX + 'generate-' | |
| 38 POST_IMAGE_URL_PREFIX = COMMON_URL_PREFIX + 'post-image-' | |
| 39 IMAGE_DATA_PREFIX = 'data:image/png;base64,' | |
| 40 | |
| 41 | |
| 42 def SimpleResponse(status): | |
| 43 """Return a ArchivedHttpResponse with |status| code and a simple text body.""" | |
| 44 return httparchive.create_response(status) | |
| 45 | |
| 46 | |
| 47 def JsonResponse(data): | |
| 48 """Return a ArchivedHttpResponse with |data| encoded as json in the body.""" | |
| 49 status = 200 | |
| 50 reason = 'OK' | |
| 51 headers = [('content-type', 'application/json')] | |
| 52 body = json.dumps(data) | |
| 53 return httparchive.create_response(status, reason, headers, body) | |
| 54 | |
| 55 | |
| 56 class CustomHandlers(object): | |
| 57 | |
| 58 def __init__(self, options, http_archive): | |
| 59 """Initialize CustomHandlers. | |
| 60 | |
| 61 Args: | |
| 62 options: original options passed to the server. | |
| 63 http_archive: reference to the HttpArchive object. | |
| 64 """ | |
| 65 self.server_manager = None | |
| 66 self.options = options | |
| 67 self.http_archive = http_archive | |
| 68 self.handlers = [ | |
| 69 (GENERATOR_URL_PREFIX, self.get_generator_url_response_code)] | |
| 70 # screenshot_dir is a path to which screenshots are saved. | |
| 71 if options.screenshot_dir: | |
| 72 if not os.path.exists(options.screenshot_dir): | |
| 73 try: | |
| 74 os.makedirs(options.screenshot_dir) | |
| 75 except IOError: | |
| 76 logging.error('Unable to create screenshot dir: %s', | |
| 77 options.screenshot_dir) | |
| 78 options.screenshot_dir = None | |
| 79 if options.screenshot_dir: | |
| 80 self.screenshot_dir = options.screenshot_dir | |
| 81 self.handlers.append( | |
| 82 (POST_IMAGE_URL_PREFIX, self.handle_possible_post_image)) | |
| 83 | |
| 84 def handle(self, request): | |
| 85 """Dispatches requests to matching handlers. | |
| 86 | |
| 87 Args: | |
| 88 request: an http request | |
| 89 Returns: | |
| 90 ArchivedHttpResponse or None. | |
| 91 """ | |
| 92 for prefix, handler in self.handlers: | |
| 93 if request.full_path.startswith(prefix): | |
| 94 return handler(request, request.full_path[len(prefix):]) | |
| 95 return None | |
| 96 | |
| 97 def get_generator_url_response_code(self, request, url_suffix): | |
| 98 """Parse special generator URLs for the embedded response code. | |
| 99 | |
| 100 Args: | |
| 101 request: an ArchivedHttpRequest instance | |
| 102 url_suffix: string that is after the handler prefix (e.g. 304) | |
| 103 Returns: | |
| 104 On a match, an ArchivedHttpResponse. | |
| 105 Otherwise, None. | |
| 106 """ | |
| 107 del request | |
| 108 try: | |
| 109 response_code = int(url_suffix) | |
| 110 return SimpleResponse(response_code) | |
| 111 except ValueError: | |
| 112 return None | |
| 113 | |
| 114 def handle_possible_post_image(self, request, url_suffix): | |
| 115 """If sent, saves embedded image to local directory. | |
| 116 | |
| 117 Expects a special url containing the filename. If sent, saves the base64 | |
| 118 encoded request body as a PNG image locally. This feature is enabled by | |
| 119 passing in screenshot_dir to the initializer for this class. | |
| 120 | |
| 121 Args: | |
| 122 request: an ArchivedHttpRequest instance | |
| 123 url_suffix: string that is after the handler prefix (e.g. 'foo.png') | |
| 124 Returns: | |
| 125 On a match, an ArchivedHttpResponse. | |
| 126 Otherwise, None. | |
| 127 """ | |
| 128 basename = url_suffix | |
| 129 if not basename: | |
| 130 return None | |
| 131 | |
| 132 data = request.request_body | |
| 133 if not data.startswith(IMAGE_DATA_PREFIX): | |
| 134 logging.error('Unexpected image format for: %s', basename) | |
| 135 return SimpleResponse(400) | |
| 136 | |
| 137 data = data[len(IMAGE_DATA_PREFIX):] | |
| 138 png = base64.b64decode(data) | |
| 139 filename = os.path.join(self.screenshot_dir, | |
| 140 '%s-%s.png' % (request.host, basename)) | |
| 141 if not os.access(self.screenshot_dir, os.W_OK): | |
| 142 logging.error('Unable to write to: %s', filename) | |
| 143 return SimpleResponse(400) | |
| 144 | |
| 145 with file(filename, 'w') as f: | |
| 146 f.write(png) | |
| 147 return SimpleResponse(200) | |
| 148 | |
| 149 def add_server_manager_handler(self, server_manager): | |
| 150 """Add the ability to change the server mode (e.g. to record mode). | |
| 151 Args: | |
| 152 server_manager: a servermanager.ServerManager instance. | |
| 153 """ | |
| 154 self.server_manager = server_manager | |
| 155 self.handlers.append( | |
| 156 (COMMAND_URL_PREFIX, self.handle_server_manager_command)) | |
| 157 | |
| 158 def handle_server_manager_command(self, request, url_suffix): | |
| 159 """Parse special URLs for the embedded server manager command. | |
| 160 | |
| 161 Clients like webpagetest.org can use URLs of this form to change | |
| 162 the replay server from record mode to replay mode. | |
| 163 | |
| 164 This handler is not in the default list of handlers. Call | |
| 165 add_server_manager_handler to add it. | |
| 166 | |
| 167 In the future, this could be expanded to save or serve archive files. | |
| 168 | |
| 169 Args: | |
| 170 request: an ArchivedHttpRequest instance | |
| 171 url_suffix: string that is after the handler prefix (e.g. 'record') | |
| 172 Returns: | |
| 173 On a match, an ArchivedHttpResponse. | |
| 174 Otherwise, None. | |
| 175 """ | |
| 176 command = url_suffix | |
| 177 if command == 'record': | |
| 178 self.server_manager.SetRecordMode() | |
| 179 return SimpleResponse(200) | |
| 180 elif command == 'replay': | |
| 181 self.server_manager.SetReplayMode() | |
| 182 return SimpleResponse(200) | |
| 183 elif command == 'status': | |
| 184 status = {} | |
| 185 is_record_mode = self.server_manager.IsRecordMode() | |
| 186 status['is_record_mode'] = is_record_mode | |
| 187 status['options'] = json.loads(str(self.options)) | |
| 188 archive_stats = self.http_archive.stats() | |
| 189 if archive_stats: | |
| 190 status['archive_stats'] = json.loads(archive_stats) | |
| 191 return JsonResponse(status) | |
| 192 elif command == 'exit': | |
| 193 self.server_manager.should_exit = True | |
| 194 return SimpleResponse(200) | |
| 195 elif command == 'log': | |
| 196 logging.info('log command: %s', str(request.request_body)[:1000000]) | |
| 197 return SimpleResponse(200) | |
| 198 return None | |
| OLD | NEW |