Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(251)

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/response.py

Issue 1154373005: Introduce WPTServe for running W3C Blink Layout tests (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Add executable bit to pass permchecks. Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 from collections import OrderedDict
2 from datetime import datetime, timedelta
3 import Cookie
4 import json
5 import types
6 import uuid
7 import socket
8
9 from constants import response_codes
10 from logger import get_logger
11
12 missing = object()
13
14 class Response(object):
15 """Object representing the response to a HTTP request
16
17 :param handler: RequestHandler being used for this response
18 :param request: Request that this is the response for
19
20 .. attribute:: request
21
22 Request associated with this Response.
23
24 .. attribute:: encoding
25
26 The encoding to use when converting unicode to strings for output.
27
28 .. attribute:: add_required_headers
29
30 Boolean indicating whether mandatory headers should be added to the
31 response.
32
33 .. attribute:: send_body_for_head_request
34
35 Boolean, default False, indicating whether the body content should be
36 sent when the request method is HEAD.
37
38 .. attribute:: explicit_flush
39
40 Boolean indicating whether output should be flushed automatically or only
41 when requested.
42
43 .. attribute:: writer
44
45 The ResponseWriter for this response
46
47 .. attribute:: status
48
49 Status tuple (code, message). Can be set to an integer, in which case the
50 message part is filled in automatically, or a tuple.
51
52 .. attribute:: headers
53
54 List of HTTP headers to send with the response. Each item in the list is a
55 tuple of (name, value).
56
57 .. attribute:: content
58
59 The body of the response. This can either be a string or a iterable of re sponse
60 parts. If it is an iterable, any item may be a string or a function of ze ro
61 parameters which, when called, returns a string."""
62
63 def __init__(self, handler, request):
64 self.request = request
65 self.encoding = "utf8"
66
67 self.add_required_headers = True
68 self.send_body_for_head_request = False
69 self.explicit_flush = False
70 self.close_connection = False
71
72 self.writer = ResponseWriter(handler, self)
73
74 self._status = (200, None)
75 self.headers = ResponseHeaders()
76 self.content = []
77
78 self.logger = get_logger()
79
80 @property
81 def status(self):
82 return self._status
83
84 @status.setter
85 def status(self, value):
86 if hasattr(value, "__len__"):
87 if len(value) != 2:
88 raise ValueError
89 else:
90 self._status = (int(value[0]), str(value[1]))
91 else:
92 self._status = (int(value), None)
93
94 def set_cookie(self, name, value, path="/", domain=None, max_age=None,
95 expires=None, secure=False, httponly=False, comment=None):
96 """Set a cookie to be sent with a Set-Cookie header in the
97 response
98
99 :param name: String name of the cookie
100 :param value: String value of the cookie
101 :param max_age: datetime.timedelta int representing the time (in seconds )
102 until the cookie expires
103 :param path: String path to which the cookie applies
104 :param domain: String domain to which the cookie applies
105 :param secure: Boolean indicating whether the cookie is marked as secure
106 :param httponly: Boolean indicating whether the cookie is marked as
107 HTTP Only
108 :param comment: String comment
109 :param expires: datetime.datetime or datetime.timedelta indicating a
110 time or interval from now when the cookie expires
111
112 """
113 days = dict((i+1, name) for i, name in enumerate(["jan", "feb", "mar",
114 "apr", "may", "jun",
115 "jul", "aug", "sep",
116 "oct", "nov", "dec"]))
117 if value is None:
118 value = ''
119 max_age = 0
120 expires = timedelta(days=-1)
121
122 if isinstance(expires, timedelta):
123 expires = datetime.utcnow() + expires
124
125 if expires is not None:
126 expires_str = expires.strftime("%d %%s %Y %H:%M:%S GMT")
127 expires_str = expires_str % days[expires.month]
128 expires = expires_str
129
130 if max_age is not None:
131 if hasattr(max_age, "total_seconds"):
132 max_age = int(max_age.total_seconds())
133 max_age = "%.0d" % max_age
134
135 m = Cookie.Morsel()
136
137 def maybe_set(key, value):
138 if value is not None and value is not False:
139 m[key] = value
140
141 m.set(name, value, value)
142 maybe_set("path", path)
143 maybe_set("domain", domain)
144 maybe_set("comment", comment)
145 maybe_set("expires", expires)
146 maybe_set("max-age", max_age)
147 maybe_set("secure", secure)
148 maybe_set("httponly", httponly)
149
150 self.headers.append("Set-Cookie", m.OutputString())
151
152 def unset_cookie(self, name):
153 """Remove a cookie from those that are being sent with the response"""
154 cookies = self.headers.get("Set-Cookie")
155 parser = Cookie.BaseCookie()
156 for cookie in cookies:
157 parser.load(cookie)
158
159 if name in parser.keys():
160 del self.headers["Set-Cookie"]
161 for m in parser.values():
162 if m.key != name:
163 self.headers.append(("Set-Cookie", m.OutputString()))
164
165 def delete_cookie(self, name, path="/", domain=None):
166 """Delete a cookie on the client by setting it to the empty string
167 and to expire in the past"""
168 self.set_cookie(name, None, path=path, domain=domain, max_age=0,
169 expires=timedelta(days=-1))
170
171 def iter_content(self):
172 """Iterator returning chunks of response body content.
173
174 If any part of the content is a function, this will be called
175 and the resulting value (if any) returned."""
176 if type(self.content) in types.StringTypes:
177 yield self.content
178 else:
179 for item in self.content:
180 if hasattr(item, "__call__"):
181 value = item()
182 else:
183 value = item
184 if value:
185 yield value
186
187 def write_status_headers(self):
188 """Write out the status line and headers for the response"""
189 self.writer.write_status(*self.status)
190 for item in self.headers:
191 self.writer.write_header(*item)
192 self.writer.end_headers()
193
194 def write_content(self):
195 """Write out the response content"""
196 if self.request.method != "HEAD" or self.send_body_for_head_request:
197 for item in self.iter_content():
198 self.writer.write_content(item)
199
200 def write(self):
201 """Write the whole response"""
202 self.write_status_headers()
203 self.write_content()
204
205 def set_error(self, code, message=""):
206 """Set the response status headers and body to indicate an
207 error"""
208 err = {"code": code,
209 "message": message}
210 data = json.dumps({"error": err})
211 self.status = code
212 self.headers = [("Content-Type", "text/json"),
213 ("Content-Length", len(data))]
214 self.content = data
215 if code == 500:
216 self.logger.error(message)
217
218
219 class MultipartContent(object):
220 def __init__(self, boundary=None, default_content_type=None):
221 self.items = []
222 if boundary is None:
223 boundary = str(uuid.uuid4())
224 self.boundary = boundary
225 self.default_content_type = default_content_type
226
227 def __call__(self):
228 boundary = "--" + self.boundary
229 rv = ["", boundary]
230 for item in self.items:
231 rv.append(str(item))
232 rv.append(boundary)
233 rv[-1] += "--"
234 return "\r\n".join(rv)
235
236 def append_part(self, data, content_type=None, headers=None):
237 if content_type is None:
238 content_type = self.default_content_type
239 self.items.append(MultipartPart(data, content_type, headers))
240
241 def __iter__(self):
242 #This is hackish; when writing the response we need an iterable
243 #or a string. For a multipart/byterange response we want an
244 #iterable that contains a single callable; the MultipartContent
245 #object itself
246 yield self
247
248
249 class MultipartPart(object):
250 def __init__(self, data, content_type=None, headers=None):
251 self.headers = ResponseHeaders()
252
253 if content_type is not None:
254 self.headers.set("Content-Type", content_type)
255
256 if headers is not None:
257 for name, value in headers:
258 if name.lower() == "content-type":
259 func = self.headers.set
260 else:
261 func = self.headers.append
262 func(name, value)
263
264 self.data = data
265
266 def __str__(self):
267 rv = []
268 for item in self.headers:
269 rv.append("%s: %s" % item)
270 rv.append("")
271 rv.append(self.data)
272 return "\r\n".join(rv)
273
274
275 class ResponseHeaders(object):
276 """Dictionary-like object holding the headers for the response"""
277 def __init__(self):
278 self.data = OrderedDict()
279
280 def set(self, key, value):
281 """Set a header to a specific value, overwriting any previous header
282 with the same name
283
284 :param key: Name of the header to set
285 :param value: Value to set the header to
286 """
287 self.data[key.lower()] = (key, [value])
288
289 def append(self, key, value):
290 """Add a new header with a given name, not overwriting any existing
291 headers with the same name
292
293 :param key: Name of the header to add
294 :param value: Value to set for the header
295 """
296 if key.lower() in self.data:
297 self.data[key.lower()][1].append(value)
298 else:
299 self.set(key, value)
300
301 def get(self, key, default=missing):
302 """Get the set values for a particular header."""
303 try:
304 return self[key]
305 except KeyError:
306 if default is missing:
307 return []
308 return default
309
310 def __getitem__(self, key):
311 """Get a list of values for a particular header
312
313 """
314 return self.data[key.lower()][1]
315
316 def __delitem__(self, key):
317 del self.data[key.lower()]
318
319 def __contains__(self, key):
320 return key.lower() in self.data
321
322 def __setitem__(self, key, value):
323 self.set(key, value)
324
325 def __iter__(self):
326 for key, values in self.data.itervalues():
327 for value in values:
328 yield key, value
329
330 def items(self):
331 return list(self)
332
333 def update(self, items_iter):
334 for name, value in items_iter:
335 self.set(name, value)
336
337 def __repr__(self):
338 return repr(self.data)
339
340
341 class ResponseWriter(object):
342 """Object providing an API to write out a HTTP response.
343
344 :param handler: The RequestHandler being used.
345 :param response: The Response associated with this writer.
346
347 After each part of the response is written, the output is
348 flushed unless response.explicit_flush is False, in which case
349 the user must call .flush() explicitly."""
350 def __init__(self, handler, response):
351 self._wfile = handler.wfile
352 self._response = response
353 self._handler = handler
354 self._headers_seen = set()
355 self._headers_complete = False
356 self.content_written = False
357 self.request = response.request
358
359 def write_status(self, code, message=None):
360 """Write out the status line of a response.
361
362 :param code: The integer status code of the response.
363 :param message: The message of the response. Defaults to the message com monly used
364 with the status code."""
365 if message is None:
366 if code in response_codes:
367 message = response_codes[code][0]
368 else:
369 message = ''
370 self.write("%s %d %s\r\n" %
371 (self._response.request.protocol_version, code, message))
372
373 def write_header(self, name, value):
374 """Write out a single header for the response.
375
376 :param name: Name of the header field
377 :param value: Value of the header field
378 """
379 self._headers_seen.add(name.lower())
380 self.write("%s: %s\r\n" % (name, value))
381 if not self._response.explicit_flush:
382 self.flush()
383
384 def write_default_headers(self):
385 for name, f in [("Server", self._handler.version_string),
386 ("Date", self._handler.date_time_string)]:
387 if name.lower() not in self._headers_seen:
388 self.write_header(name, f())
389
390 if (type(self._response.content) in (str, unicode) and
391 "content-length" not in self._headers_seen):
392 #Would be nice to avoid double-encoding here
393 self.write_header("Content-Length", len(self.encode(self._response.c ontent)))
394
395 def end_headers(self):
396 """Finish writing headers and write the separator.
397
398 Unless add_required_headers on the response is False,
399 this will also add HTTP-mandated headers that have not yet been supplied
400 to the response headers"""
401
402 if self._response.add_required_headers:
403 self.write_default_headers()
404
405 self.write("\r\n")
406 if "content-length" not in self._headers_seen:
407 self._response.close_connection = True
408 if not self._response.explicit_flush:
409 self.flush()
410 self._headers_complete = True
411
412 def write_content(self, data):
413 """Write the body of the response."""
414 self.write(self.encode(data))
415 if not self._response.explicit_flush:
416 self.flush()
417
418 def write(self, data):
419 """Write directly to the response, converting unicode to bytes
420 according to response.encoding. Does not flush."""
421 self.content_written = True
422 try:
423 self._wfile.write(self.encode(data))
424 except socket.error:
425 # This can happen if the socket got closed by the remote end
426 pass
427
428 def encode(self, data):
429 """Convert unicode to bytes according to response.encoding."""
430 if isinstance(data, str):
431 return data
432 elif isinstance(data, unicode):
433 return data.encode(self._response.encoding)
434 else:
435 raise ValueError
436
437 def flush(self):
438 """Flush the output."""
439 try:
440 self._wfile.flush()
441 except socket.error:
442 # This can happen if the socket got closed by the remote end
443 pass
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698