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

Side by Side Diff: third_party/cherrypy/_cprequest.py

Issue 9368042: Add CherryPy to third_party. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build/
Patch Set: '' Created 8 years, 10 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 | Annotate | Revision Log
« no previous file with comments | « third_party/cherrypy/_cpreqbody.py ('k') | third_party/cherrypy/_cpserver.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1
2 import os
3 import sys
4 import time
5 import warnings
6
7 import cherrypy
8 from cherrypy._cpcompat import basestring, copykeys, ntob, unicodestr
9 from cherrypy._cpcompat import SimpleCookie, CookieError, py3k
10 from cherrypy import _cpreqbody, _cpconfig
11 from cherrypy._cperror import format_exc, bare_error
12 from cherrypy.lib import httputil, file_generator
13
14
15 class Hook(object):
16 """A callback and its metadata: failsafe, priority, and kwargs."""
17
18 callback = None
19 """
20 The bare callable that this Hook object is wrapping, which will
21 be called when the Hook is called."""
22
23 failsafe = False
24 """
25 If True, the callback is guaranteed to run even if other callbacks
26 from the same call point raise exceptions."""
27
28 priority = 50
29 """
30 Defines the order of execution for a list of Hooks. Priority numbers
31 should be limited to the closed interval [0, 100], but values outside
32 this range are acceptable, as are fractional values."""
33
34 kwargs = {}
35 """
36 A set of keyword arguments that will be passed to the
37 callable on each call."""
38
39 def __init__(self, callback, failsafe=None, priority=None, **kwargs):
40 self.callback = callback
41
42 if failsafe is None:
43 failsafe = getattr(callback, "failsafe", False)
44 self.failsafe = failsafe
45
46 if priority is None:
47 priority = getattr(callback, "priority", 50)
48 self.priority = priority
49
50 self.kwargs = kwargs
51
52 def __lt__(self, other):
53 # Python 3
54 return self.priority < other.priority
55
56 def __cmp__(self, other):
57 # Python 2
58 return cmp(self.priority, other.priority)
59
60 def __call__(self):
61 """Run self.callback(**self.kwargs)."""
62 return self.callback(**self.kwargs)
63
64 def __repr__(self):
65 cls = self.__class__
66 return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
67 % (cls.__module__, cls.__name__, self.callback,
68 self.failsafe, self.priority,
69 ", ".join(['%s=%r' % (k, v)
70 for k, v in self.kwargs.items()])))
71
72
73 class HookMap(dict):
74 """A map of call points to lists of callbacks (Hook objects)."""
75
76 def __new__(cls, points=None):
77 d = dict.__new__(cls)
78 for p in points or []:
79 d[p] = []
80 return d
81
82 def __init__(self, *a, **kw):
83 pass
84
85 def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
86 """Append a new Hook made from the supplied arguments."""
87 self[point].append(Hook(callback, failsafe, priority, **kwargs))
88
89 def run(self, point):
90 """Execute all registered Hooks (callbacks) for the given point."""
91 exc = None
92 hooks = self[point]
93 hooks.sort()
94 for hook in hooks:
95 # Some hooks are guaranteed to run even if others at
96 # the same hookpoint fail. We will still log the failure,
97 # but proceed on to the next hook. The only way
98 # to stop all processing from one of these hooks is
99 # to raise SystemExit and stop the whole server.
100 if exc is None or hook.failsafe:
101 try:
102 hook()
103 except (KeyboardInterrupt, SystemExit):
104 raise
105 except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
106 cherrypy.InternalRedirect):
107 exc = sys.exc_info()[1]
108 except:
109 exc = sys.exc_info()[1]
110 cherrypy.log(traceback=True, severity=40)
111 if exc:
112 raise exc
113
114 def __copy__(self):
115 newmap = self.__class__()
116 # We can't just use 'update' because we want copies of the
117 # mutable values (each is a list) as well.
118 for k, v in self.items():
119 newmap[k] = v[:]
120 return newmap
121 copy = __copy__
122
123 def __repr__(self):
124 cls = self.__class__
125 return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, copykeys(self ))
126
127
128 # Config namespace handlers
129
130 def hooks_namespace(k, v):
131 """Attach bare hooks declared in config."""
132 # Use split again to allow multiple hooks for a single
133 # hookpoint per path (e.g. "hooks.before_handler.1").
134 # Little-known fact you only get from reading source ;)
135 hookpoint = k.split(".", 1)[0]
136 if isinstance(v, basestring):
137 v = cherrypy.lib.attributes(v)
138 if not isinstance(v, Hook):
139 v = Hook(v)
140 cherrypy.serving.request.hooks[hookpoint].append(v)
141
142 def request_namespace(k, v):
143 """Attach request attributes declared in config."""
144 # Provides config entries to set request.body attrs (like attempt_charsets).
145 if k[:5] == 'body.':
146 setattr(cherrypy.serving.request.body, k[5:], v)
147 else:
148 setattr(cherrypy.serving.request, k, v)
149
150 def response_namespace(k, v):
151 """Attach response attributes declared in config."""
152 # Provides config entries to set default response headers
153 # http://cherrypy.org/ticket/889
154 if k[:8] == 'headers.':
155 cherrypy.serving.response.headers[k.split('.', 1)[1]] = v
156 else:
157 setattr(cherrypy.serving.response, k, v)
158
159 def error_page_namespace(k, v):
160 """Attach error pages declared in config."""
161 if k != 'default':
162 k = int(k)
163 cherrypy.serving.request.error_page[k] = v
164
165
166 hookpoints = ['on_start_resource', 'before_request_body',
167 'before_handler', 'before_finalize',
168 'on_end_resource', 'on_end_request',
169 'before_error_response', 'after_error_response']
170
171
172 class Request(object):
173 """An HTTP request.
174
175 This object represents the metadata of an HTTP request message;
176 that is, it contains attributes which describe the environment
177 in which the request URL, headers, and body were sent (if you
178 want tools to interpret the headers and body, those are elsewhere,
179 mostly in Tools). This 'metadata' consists of socket data,
180 transport characteristics, and the Request-Line. This object
181 also contains data regarding the configuration in effect for
182 the given URL, and the execution plan for generating a response.
183 """
184
185 prev = None
186 """
187 The previous Request object (if any). This should be None
188 unless we are processing an InternalRedirect."""
189
190 # Conversation/connection attributes
191 local = httputil.Host("127.0.0.1", 80)
192 "An httputil.Host(ip, port, hostname) object for the server socket."
193
194 remote = httputil.Host("127.0.0.1", 1111)
195 "An httputil.Host(ip, port, hostname) object for the client socket."
196
197 scheme = "http"
198 """
199 The protocol used between client and server. In most cases,
200 this will be either 'http' or 'https'."""
201
202 server_protocol = "HTTP/1.1"
203 """
204 The HTTP version for which the HTTP server is at least
205 conditionally compliant."""
206
207 base = ""
208 """The (scheme://host) portion of the requested URL.
209 In some cases (e.g. when proxying via mod_rewrite), this may contain
210 path segments which cherrypy.url uses when constructing url's, but
211 which otherwise are ignored by CherryPy. Regardless, this value
212 MUST NOT end in a slash."""
213
214 # Request-Line attributes
215 request_line = ""
216 """
217 The complete Request-Line received from the client. This is a
218 single string consisting of the request method, URI, and protocol
219 version (joined by spaces). Any final CRLF is removed."""
220
221 method = "GET"
222 """
223 Indicates the HTTP method to be performed on the resource identified
224 by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
225 DELETE. CherryPy allows any extension method; however, various HTTP
226 servers and gateways may restrict the set of allowable methods.
227 CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
228
229 query_string = ""
230 """
231 The query component of the Request-URI, a string of information to be
232 interpreted by the resource. The query portion of a URI follows the
233 path component, and is separated by a '?'. For example, the URI
234 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
235 'a=3&b=4'."""
236
237 query_string_encoding = 'utf8'
238 """
239 The encoding expected for query string arguments after % HEX HEX decoding).
240 If a query string is provided that cannot be decoded with this encoding,
241 404 is raised (since technically it's a different URI). If you want
242 arbitrary encodings to not error, set this to 'Latin-1'; you can then
243 encode back to bytes and re-decode to whatever encoding you like later.
244 """
245
246 protocol = (1, 1)
247 """The HTTP protocol version corresponding to the set
248 of features which should be allowed in the response. If BOTH
249 the client's request message AND the server's level of HTTP
250 compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
251 If either is 1.0, this attribute will be the tuple (1, 0).
252 Lower HTTP protocol versions are not explicitly supported."""
253
254 params = {}
255 """
256 A dict which combines query string (GET) and request entity (POST)
257 variables. This is populated in two stages: GET params are added
258 before the 'on_start_resource' hook, and POST params are added
259 between the 'before_request_body' and 'before_handler' hooks."""
260
261 # Message attributes
262 header_list = []
263 """
264 A list of the HTTP request headers as (name, value) tuples.
265 In general, you should use request.headers (a dict) instead."""
266
267 headers = httputil.HeaderMap()
268 """
269 A dict-like object containing the request headers. Keys are header
270 names (in Title-Case format); however, you may get and set them in
271 a case-insensitive manner. That is, headers['Content-Type'] and
272 headers['content-type'] refer to the same value. Values are header
273 values (decoded according to :rfc:`2047` if necessary). See also:
274 httputil.HeaderMap, httputil.HeaderElement."""
275
276 cookie = SimpleCookie()
277 """See help(Cookie)."""
278
279 rfile = None
280 """
281 If the request included an entity (body), it will be available
282 as a stream in this attribute. However, the rfile will normally
283 be read for you between the 'before_request_body' hook and the
284 'before_handler' hook, and the resulting string is placed into
285 either request.params or the request.body attribute.
286
287 You may disable the automatic consumption of the rfile by setting
288 request.process_request_body to False, either in config for the desired
289 path, or in an 'on_start_resource' or 'before_request_body' hook.
290
291 WARNING: In almost every case, you should not attempt to read from the
292 rfile stream after CherryPy's automatic mechanism has read it. If you
293 turn off the automatic parsing of rfile, you should read exactly the
294 number of bytes specified in request.headers['Content-Length'].
295 Ignoring either of these warnings may result in a hung request thread
296 or in corruption of the next (pipelined) request.
297 """
298
299 process_request_body = True
300 """
301 If True, the rfile (if any) is automatically read and parsed,
302 and the result placed into request.params or request.body."""
303
304 methods_with_bodies = ("POST", "PUT")
305 """
306 A sequence of HTTP methods for which CherryPy will automatically
307 attempt to read a body from the rfile."""
308
309 body = None
310 """
311 If the request Content-Type is 'application/x-www-form-urlencoded'
312 or multipart, this will be None. Otherwise, this will be an instance
313 of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
314 can .read()); this value is set between the 'before_request_body' and
315 'before_handler' hooks (assuming that process_request_body is True)."""
316
317 # Dispatch attributes
318 dispatch = cherrypy.dispatch.Dispatcher()
319 """
320 The object which looks up the 'page handler' callable and collects
321 config for the current request based on the path_info, other
322 request attributes, and the application architecture. The core
323 calls the dispatcher as early as possible, passing it a 'path_info'
324 argument.
325
326 The default dispatcher discovers the page handler by matching path_info
327 to a hierarchical arrangement of objects, starting at request.app.root.
328 See help(cherrypy.dispatch) for more information."""
329
330 script_name = ""
331 """
332 The 'mount point' of the application which is handling this request.
333
334 This attribute MUST NOT end in a slash. If the script_name refers to
335 the root of the URI, it MUST be an empty string (not "/").
336 """
337
338 path_info = "/"
339 """
340 The 'relative path' portion of the Request-URI. This is relative
341 to the script_name ('mount point') of the application which is
342 handling this request."""
343
344 login = None
345 """
346 When authentication is used during the request processing this is
347 set to 'False' if it failed and to the 'username' value if it succeeded.
348 The default 'None' implies that no authentication happened."""
349
350 # Note that cherrypy.url uses "if request.app:" to determine whether
351 # the call is during a real HTTP request or not. So leave this None.
352 app = None
353 """The cherrypy.Application object which is handling this request."""
354
355 handler = None
356 """
357 The function, method, or other callable which CherryPy will call to
358 produce the response. The discovery of the handler and the arguments
359 it will receive are determined by the request.dispatch object.
360 By default, the handler is discovered by walking a tree of objects
361 starting at request.app.root, and is then passed all HTTP params
362 (from the query string and POST body) as keyword arguments."""
363
364 toolmaps = {}
365 """
366 A nested dict of all Toolboxes and Tools in effect for this request,
367 of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
368
369 config = None
370 """
371 A flat dict of all configuration entries which apply to the
372 current request. These entries are collected from global config,
373 application config (based on request.path_info), and from handler
374 config (exactly how is governed by the request.dispatch object in
375 effect for this request; by default, handler config can be attached
376 anywhere in the tree between request.app.root and the final handler,
377 and inherits downward)."""
378
379 is_index = None
380 """
381 This will be True if the current request is mapped to an 'index'
382 resource handler (also, a 'default' handler if path_info ends with
383 a slash). The value may be used to automatically redirect the
384 user-agent to a 'more canonical' URL which either adds or removes
385 the trailing slash. See cherrypy.tools.trailing_slash."""
386
387 hooks = HookMap(hookpoints)
388 """
389 A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
390 Each key is a str naming the hook point, and each value is a list
391 of hooks which will be called at that hook point during this request.
392 The list of hooks is generally populated as early as possible (mostly
393 from Tools specified in config), but may be extended at any time.
394 See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
395
396 error_response = cherrypy.HTTPError(500).set_response
397 """
398 The no-arg callable which will handle unexpected, untrapped errors
399 during request processing. This is not used for expected exceptions
400 (like NotFound, HTTPError, or HTTPRedirect) which are raised in
401 response to expected conditions (those should be customized either
402 via request.error_page or by overriding HTTPError.set_response).
403 By default, error_response uses HTTPError(500) to return a generic
404 error response to the user-agent."""
405
406 error_page = {}
407 """
408 A dict of {error code: response filename or callable} pairs.
409
410 The error code must be an int representing a given HTTP error code,
411 or the string 'default', which will be used if no matching entry
412 is found for a given numeric code.
413
414 If a filename is provided, the file should contain a Python string-
415 formatting template, and can expect by default to receive format
416 values with the mapping keys %(status)s, %(message)s, %(traceback)s,
417 and %(version)s. The set of format mappings can be extended by
418 overriding HTTPError.set_response.
419
420 If a callable is provided, it will be called by default with keyword
421 arguments 'status', 'message', 'traceback', and 'version', as for a
422 string-formatting template. The callable must return a string or iterable of
423 strings which will be set to response.body. It may also override headers or
424 perform any other processing.
425
426 If no entry is given for an error code, and no 'default' entry exists,
427 a default template will be used.
428 """
429
430 show_tracebacks = True
431 """
432 If True, unexpected errors encountered during request processing will
433 include a traceback in the response body."""
434
435 show_mismatched_params = True
436 """
437 If True, mismatched parameters encountered during PageHandler invocation
438 processing will be included in the response body."""
439
440 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
441 """The sequence of exceptions which Request.run does not trap."""
442
443 throw_errors = False
444 """
445 If True, Request.run will not trap any errors (except HTTPRedirect and
446 HTTPError, which are more properly called 'exceptions', not errors)."""
447
448 closed = False
449 """True once the close method has been called, False otherwise."""
450
451 stage = None
452 """
453 A string containing the stage reached in the request-handling process.
454 This is useful when debugging a live server with hung requests."""
455
456 namespaces = _cpconfig.NamespaceSet(
457 **{"hooks": hooks_namespace,
458 "request": request_namespace,
459 "response": response_namespace,
460 "error_page": error_page_namespace,
461 "tools": cherrypy.tools,
462 })
463
464 def __init__(self, local_host, remote_host, scheme="http",
465 server_protocol="HTTP/1.1"):
466 """Populate a new Request object.
467
468 local_host should be an httputil.Host object with the server info.
469 remote_host should be an httputil.Host object with the client info.
470 scheme should be a string, either "http" or "https".
471 """
472 self.local = local_host
473 self.remote = remote_host
474 self.scheme = scheme
475 self.server_protocol = server_protocol
476
477 self.closed = False
478
479 # Put a *copy* of the class error_page into self.
480 self.error_page = self.error_page.copy()
481
482 # Put a *copy* of the class namespaces into self.
483 self.namespaces = self.namespaces.copy()
484
485 self.stage = None
486
487 def close(self):
488 """Run cleanup code. (Core)"""
489 if not self.closed:
490 self.closed = True
491 self.stage = 'on_end_request'
492 self.hooks.run('on_end_request')
493 self.stage = 'close'
494
495 def run(self, method, path, query_string, req_protocol, headers, rfile):
496 r"""Process the Request. (Core)
497
498 method, path, query_string, and req_protocol should be pulled directly
499 from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
500
501 path
502 This should be %XX-unquoted, but query_string should not be.
503
504 When using Python 2, they both MUST be byte strings,
505 not unicode strings.
506
507 When using Python 3, they both MUST be unicode strings,
508 not byte strings, and preferably not bytes \x00-\xFF
509 disguised as unicode.
510
511 headers
512 A list of (name, value) tuples.
513
514 rfile
515 A file-like object containing the HTTP request entity.
516
517 When run() is done, the returned object should have 3 attributes:
518
519 * status, e.g. "200 OK"
520 * header_list, a list of (name, value) tuples
521 * body, an iterable yielding strings
522
523 Consumer code (HTTP servers) should then access these response
524 attributes to build the outbound stream.
525
526 """
527 response = cherrypy.serving.response
528 self.stage = 'run'
529 try:
530 self.error_response = cherrypy.HTTPError(500).set_response
531
532 self.method = method
533 path = path or "/"
534 self.query_string = query_string or ''
535 self.params = {}
536
537 # Compare request and server HTTP protocol versions, in case our
538 # server does not support the requested protocol. Limit our output
539 # to min(req, server). We want the following output:
540 # request server actual written supported response
541 # protocol protocol response protocol feature set
542 # a 1.0 1.0 1.0 1.0
543 # b 1.0 1.1 1.1 1.0
544 # c 1.1 1.0 1.0 1.0
545 # d 1.1 1.1 1.1 1.1
546 # Notice that, in (b), the response will be "HTTP/1.1" even though
547 # the client only understands 1.0. RFC 2616 10.5.6 says we should
548 # only return 505 if the _major_ version is different.
549 rp = int(req_protocol[5]), int(req_protocol[7])
550 sp = int(self.server_protocol[5]), int(self.server_protocol[7])
551 self.protocol = min(rp, sp)
552 response.headers.protocol = self.protocol
553
554 # Rebuild first line of the request (e.g. "GET /path HTTP/1.0").
555 url = path
556 if query_string:
557 url += '?' + query_string
558 self.request_line = '%s %s %s' % (method, url, req_protocol)
559
560 self.header_list = list(headers)
561 self.headers = httputil.HeaderMap()
562
563 self.rfile = rfile
564 self.body = None
565
566 self.cookie = SimpleCookie()
567 self.handler = None
568
569 # path_info should be the path from the
570 # app root (script_name) to the handler.
571 self.script_name = self.app.script_name
572 self.path_info = pi = path[len(self.script_name):]
573
574 self.stage = 'respond'
575 self.respond(pi)
576
577 except self.throws:
578 raise
579 except:
580 if self.throw_errors:
581 raise
582 else:
583 # Failure in setup, error handler or finalize. Bypass them.
584 # Can't use handle_error because we may not have hooks yet.
585 cherrypy.log(traceback=True, severity=40)
586 if self.show_tracebacks:
587 body = format_exc()
588 else:
589 body = ""
590 r = bare_error(body)
591 response.output_status, response.header_list, response.body = r
592
593 if self.method == "HEAD":
594 # HEAD requests MUST NOT return a message-body in the response.
595 response.body = []
596
597 try:
598 cherrypy.log.access()
599 except:
600 cherrypy.log.error(traceback=True)
601
602 if response.timed_out:
603 raise cherrypy.TimeoutError()
604
605 return response
606
607 # Uncomment for stage debugging
608 # stage = property(lambda self: self._stage, lambda self, v: print(v))
609
610 def respond(self, path_info):
611 """Generate a response for the resource at self.path_info. (Core)"""
612 response = cherrypy.serving.response
613 try:
614 try:
615 try:
616 if self.app is None:
617 raise cherrypy.NotFound()
618
619 # Get the 'Host' header, so we can HTTPRedirect properly.
620 self.stage = 'process_headers'
621 self.process_headers()
622
623 # Make a copy of the class hooks
624 self.hooks = self.__class__.hooks.copy()
625 self.toolmaps = {}
626
627 self.stage = 'get_resource'
628 self.get_resource(path_info)
629
630 self.body = _cpreqbody.RequestBody(
631 self.rfile, self.headers, request_params=self.params)
632
633 self.namespaces(self.config)
634
635 self.stage = 'on_start_resource'
636 self.hooks.run('on_start_resource')
637
638 # Parse the querystring
639 self.stage = 'process_query_string'
640 self.process_query_string()
641
642 # Process the body
643 if self.process_request_body:
644 if self.method not in self.methods_with_bodies:
645 self.process_request_body = False
646 self.stage = 'before_request_body'
647 self.hooks.run('before_request_body')
648 if self.process_request_body:
649 self.body.process()
650
651 # Run the handler
652 self.stage = 'before_handler'
653 self.hooks.run('before_handler')
654 if self.handler:
655 self.stage = 'handler'
656 response.body = self.handler()
657
658 # Finalize
659 self.stage = 'before_finalize'
660 self.hooks.run('before_finalize')
661 response.finalize()
662 except (cherrypy.HTTPRedirect, cherrypy.HTTPError):
663 inst = sys.exc_info()[1]
664 inst.set_response()
665 self.stage = 'before_finalize (HTTPError)'
666 self.hooks.run('before_finalize')
667 response.finalize()
668 finally:
669 self.stage = 'on_end_resource'
670 self.hooks.run('on_end_resource')
671 except self.throws:
672 raise
673 except:
674 if self.throw_errors:
675 raise
676 self.handle_error()
677
678 def process_query_string(self):
679 """Parse the query string into Python structures. (Core)"""
680 try:
681 p = httputil.parse_query_string(
682 self.query_string, encoding=self.query_string_encoding)
683 except UnicodeDecodeError:
684 raise cherrypy.HTTPError(
685 404, "The given query string could not be processed. Query "
686 "strings for this resource must be encoded with %r." %
687 self.query_string_encoding)
688
689 # Python 2 only: keyword arguments must be byte strings (type 'str').
690 if not py3k:
691 for key, value in p.items():
692 if isinstance(key, unicode):
693 del p[key]
694 p[key.encode(self.query_string_encoding)] = value
695 self.params.update(p)
696
697 def process_headers(self):
698 """Parse HTTP header data into Python structures. (Core)"""
699 # Process the headers into self.headers
700 headers = self.headers
701 for name, value in self.header_list:
702 # Call title() now (and use dict.__method__(headers))
703 # so title doesn't have to be called twice.
704 name = name.title()
705 value = value.strip()
706
707 # Warning: if there is more than one header entry for cookies (AFAIK ,
708 # only Konqueror does that), only the last one will remain in header s
709 # (but they will be correctly stored in request.cookie).
710 if "=?" in value:
711 dict.__setitem__(headers, name, httputil.decode_TEXT(value))
712 else:
713 dict.__setitem__(headers, name, value)
714
715 # Handle cookies differently because on Konqueror, multiple
716 # cookies come on different lines with the same key
717 if name == 'Cookie':
718 try:
719 self.cookie.load(value)
720 except CookieError:
721 msg = "Illegal cookie name %s" % value.split('=')[0]
722 raise cherrypy.HTTPError(400, msg)
723
724 if not dict.__contains__(headers, 'Host'):
725 # All Internet-based HTTP/1.1 servers MUST respond with a 400
726 # (Bad Request) status code to any HTTP/1.1 request message
727 # which lacks a Host header field.
728 if self.protocol >= (1, 1):
729 msg = "HTTP/1.1 requires a 'Host' request header."
730 raise cherrypy.HTTPError(400, msg)
731 host = dict.get(headers, 'Host')
732 if not host:
733 host = self.local.name or self.local.ip
734 self.base = "%s://%s" % (self.scheme, host)
735
736 def get_resource(self, path):
737 """Call a dispatcher (which sets self.handler and .config). (Core)"""
738 # First, see if there is a custom dispatch at this URI. Custom
739 # dispatchers can only be specified in app.config, not in _cp_config
740 # (since custom dispatchers may not even have an app.root).
741 dispatch = self.app.find_config(path, "request.dispatch", self.dispatch)
742
743 # dispatch() should set self.handler and self.config
744 dispatch(path)
745
746 def handle_error(self):
747 """Handle the last unanticipated exception. (Core)"""
748 try:
749 self.hooks.run("before_error_response")
750 if self.error_response:
751 self.error_response()
752 self.hooks.run("after_error_response")
753 cherrypy.serving.response.finalize()
754 except cherrypy.HTTPRedirect:
755 inst = sys.exc_info()[1]
756 inst.set_response()
757 cherrypy.serving.response.finalize()
758
759 # ------------------------- Properties ------------------------- #
760
761 def _get_body_params(self):
762 warnings.warn(
763 "body_params is deprecated in CherryPy 3.2, will be removed in "
764 "CherryPy 3.3.",
765 DeprecationWarning
766 )
767 return self.body.params
768 body_params = property(_get_body_params,
769 doc= """
770 If the request Content-Type is 'application/x-www-form-urlencoded' or
771 multipart, this will be a dict of the params pulled from the entity
772 body; that is, it will be the portion of request.params that come
773 from the message body (sometimes called "POST params", although they
774 can be sent with various HTTP method verbs). This value is set between
775 the 'before_request_body' and 'before_handler' hooks (assuming that
776 process_request_body is True).
777
778 Deprecated in 3.2, will be removed for 3.3 in favor of
779 :attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
780
781
782 class ResponseBody(object):
783 """The body of the HTTP response (the response entity)."""
784
785 if py3k:
786 unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
787 "if you wish to return unicode.")
788
789 def __get__(self, obj, objclass=None):
790 if obj is None:
791 # When calling on the class instead of an instance...
792 return self
793 else:
794 return obj._body
795
796 def __set__(self, obj, value):
797 # Convert the given value to an iterable object.
798 if py3k and isinstance(value, str):
799 raise ValueError(self.unicode_err)
800
801 if isinstance(value, basestring):
802 # strings get wrapped in a list because iterating over a single
803 # item list is much faster than iterating over every character
804 # in a long string.
805 if value:
806 value = [value]
807 else:
808 # [''] doesn't evaluate to False, so replace it with [].
809 value = []
810 elif py3k and isinstance(value, list):
811 # every item in a list must be bytes...
812 for i, item in enumerate(value):
813 if isinstance(item, str):
814 raise ValueError(self.unicode_err)
815 # Don't use isinstance here; io.IOBase which has an ABC takes
816 # 1000 times as long as, say, isinstance(value, str)
817 elif hasattr(value, 'read'):
818 value = file_generator(value)
819 elif value is None:
820 value = []
821 obj._body = value
822
823
824 class Response(object):
825 """An HTTP Response, including status, headers, and body."""
826
827 status = ""
828 """The HTTP Status-Code and Reason-Phrase."""
829
830 header_list = []
831 """
832 A list of the HTTP response headers as (name, value) tuples.
833 In general, you should use response.headers (a dict) instead. This
834 attribute is generated from response.headers and is not valid until
835 after the finalize phase."""
836
837 headers = httputil.HeaderMap()
838 """
839 A dict-like object containing the response headers. Keys are header
840 names (in Title-Case format); however, you may get and set them in
841 a case-insensitive manner. That is, headers['Content-Type'] and
842 headers['content-type'] refer to the same value. Values are header
843 values (decoded according to :rfc:`2047` if necessary).
844
845 .. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement`
846 """
847
848 cookie = SimpleCookie()
849 """See help(Cookie)."""
850
851 body = ResponseBody()
852 """The body (entity) of the HTTP response."""
853
854 time = None
855 """The value of time.time() when created. Use in HTTP dates."""
856
857 timeout = 300
858 """Seconds after which the response will be aborted."""
859
860 timed_out = False
861 """
862 Flag to indicate the response should be aborted, because it has
863 exceeded its timeout."""
864
865 stream = False
866 """If False, buffer the response body."""
867
868 def __init__(self):
869 self.status = None
870 self.header_list = None
871 self._body = []
872 self.time = time.time()
873
874 self.headers = httputil.HeaderMap()
875 # Since we know all our keys are titled strings, we can
876 # bypass HeaderMap.update and get a big speed boost.
877 dict.update(self.headers, {
878 "Content-Type": 'text/html',
879 "Server": "CherryPy/" + cherrypy.__version__,
880 "Date": httputil.HTTPDate(self.time),
881 })
882 self.cookie = SimpleCookie()
883
884 def collapse_body(self):
885 """Collapse self.body to a single string; replace it and return it."""
886 if isinstance(self.body, basestring):
887 return self.body
888
889 newbody = []
890 for chunk in self.body:
891 if py3k and not isinstance(chunk, bytes):
892 raise TypeError("Chunk %s is not of type 'bytes'." % repr(chunk) )
893 newbody.append(chunk)
894 newbody = ntob('').join(newbody)
895
896 self.body = newbody
897 return newbody
898
899 def finalize(self):
900 """Transform headers (and cookies) into self.header_list. (Core)"""
901 try:
902 code, reason, _ = httputil.valid_status(self.status)
903 except ValueError:
904 raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])
905
906 headers = self.headers
907
908 self.status = "%s %s" % (code, reason)
909 self.output_status = ntob(str(code), 'ascii') + ntob(" ") + headers.enco de(reason)
910
911 if self.stream:
912 # The upshot: wsgiserver will chunk the response if
913 # you pop Content-Length (or set it explicitly to None).
914 # Note that lib.static sets C-L to the file's st_size.
915 if dict.get(headers, 'Content-Length') is None:
916 dict.pop(headers, 'Content-Length', None)
917 elif code < 200 or code in (204, 205, 304):
918 # "All 1xx (informational), 204 (no content),
919 # and 304 (not modified) responses MUST NOT
920 # include a message-body."
921 dict.pop(headers, 'Content-Length', None)
922 self.body = ntob("")
923 else:
924 # Responses which are not streamed should have a Content-Length,
925 # but allow user code to set Content-Length if desired.
926 if dict.get(headers, 'Content-Length') is None:
927 content = self.collapse_body()
928 dict.__setitem__(headers, 'Content-Length', len(content))
929
930 # Transform our header dict into a list of tuples.
931 self.header_list = h = headers.output()
932
933 cookie = self.cookie.output()
934 if cookie:
935 for line in cookie.split("\n"):
936 if line.endswith("\r"):
937 # Python 2.4 emits cookies joined by LF but 2.5+ by CRLF.
938 line = line[:-1]
939 name, value = line.split(": ", 1)
940 if isinstance(name, unicodestr):
941 name = name.encode("ISO-8859-1")
942 if isinstance(value, unicodestr):
943 value = headers.encode(value)
944 h.append((name, value))
945
946 def check_timeout(self):
947 """If now > self.time + self.timeout, set self.timed_out.
948
949 This purposefully sets a flag, rather than raising an error,
950 so that a monitor thread can interrupt the Response thread.
951 """
952 if time.time() > self.time + self.timeout:
953 self.timed_out = True
954
955
956
OLDNEW
« no previous file with comments | « third_party/cherrypy/_cpreqbody.py ('k') | third_party/cherrypy/_cpserver.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698