2 # Copyright 2010 Google Inc. All Rights Reserved.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
16 """Handle special HTTP requests.
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.
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,'
42 def SimpleResponse(status
):
43 """Return a ArchivedHttpResponse with |status| code and a simple text body."""
44 return httparchive
.create_response(status
)
47 def JsonResponse(data
):
48 """Return a ArchivedHttpResponse with |data| encoded as json in the body."""
51 headers
= [('content-type', 'application/json')]
52 body
= json
.dumps(data
)
53 return httparchive
.create_response(status
, reason
, headers
, body
)
56 class CustomHandlers(object):
58 def __init__(self
, options
, http_archive
):
59 """Initialize CustomHandlers.
62 options: original options passed to the server.
63 http_archive: reference to the HttpArchive object.
65 self
.server_manager
= None
66 self
.options
= options
67 self
.http_archive
= http_archive
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
):
74 os
.makedirs(options
.screenshot_dir
)
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
82 (POST_IMAGE_URL_PREFIX
, self
.handle_possible_post_image
))
84 def handle(self
, request
):
85 """Dispatches requests to matching handlers.
88 request: an http request
90 ArchivedHttpResponse or None.
92 for prefix
, handler
in self
.handlers
:
93 if request
.full_path
.startswith(prefix
):
94 return handler(request
, request
.full_path
[len(prefix
):])
97 def get_generator_url_response_code(self
, request
, url_suffix
):
98 """Parse special generator URLs for the embedded response code.
101 request: an ArchivedHttpRequest instance
102 url_suffix: string that is after the handler prefix (e.g. 304)
104 On a match, an ArchivedHttpResponse.
109 response_code
= int(url_suffix
)
110 return SimpleResponse(response_code
)
114 def handle_possible_post_image(self
, request
, url_suffix
):
115 """If sent, saves embedded image to local directory.
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.
122 request: an ArchivedHttpRequest instance
123 url_suffix: string that is after the handler prefix (e.g. 'foo.png')
125 On a match, an ArchivedHttpResponse.
128 basename
= url_suffix
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)
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)
145 with
file(filename
, 'w') as f
:
147 return SimpleResponse(200)
149 def add_server_manager_handler(self
, server_manager
):
150 """Add the ability to change the server mode (e.g. to record mode).
152 server_manager: a servermanager.ServerManager instance.
154 self
.server_manager
= server_manager
155 self
.handlers
.append(
156 (COMMAND_URL_PREFIX
, self
.handle_server_manager_command
))
158 def handle_server_manager_command(self
, request
, url_suffix
):
159 """Parse special URLs for the embedded server manager command.
161 Clients like webpagetest.org can use URLs of this form to change
162 the replay server from record mode to replay mode.
164 This handler is not in the default list of handlers. Call
165 add_server_manager_handler to add it.
167 In the future, this could be expanded to save or serve archive files.
170 request: an ArchivedHttpRequest instance
171 url_suffix: string that is after the handler prefix (e.g. 'record')
173 On a match, an ArchivedHttpResponse.
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':
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()
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)