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
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):
51
55
59
61 """Run self.callback(**self.kwargs)."""
62 return self.callback(**self.kwargs)
63
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
74 """A map of call points to lists of callbacks (Hook objects)."""
75
77 d = dict.__new__(cls)
78 for p in points or []:
79 d[p] = []
80 return d
81
84
85 - def attach(self, point, callback, failsafe=None, priority=None, **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
96
97
98
99
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
115 newmap = self.__class__()
116
117
118 for k, v in self.items():
119 newmap[k] = v[:]
120 return newmap
121 copy = __copy__
122
124 cls = self.__class__
125 return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, copykeys(self))
126
127
128
129
141
149
158
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
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
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
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
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
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
351
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
480 self.error_page = self.error_page.copy()
481
482
483 self.namespaces = self.namespaces.copy()
484
485 self.stage = None
486
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
538
539
540
541
542
543
544
545
546
547
548
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
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
570
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
584
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
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
608
609
677
696
698 """Parse HTTP header data into Python structures. (Core)"""
699
700 headers = self.headers
701 for name, value in self.header_list:
702
703
704 name = name.title()
705 value = value.strip()
706
707
708
709
710 if "=?" in value:
711 dict.__setitem__(headers, name, httputil.decode_TEXT(value))
712 else:
713 dict.__setitem__(headers, name, value)
714
715
716
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
726
727
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
745
758
759
760
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
792 return self
793 else:
794 return obj._body
795
796 - def __set__(self, obj, value):
797
798 if py3k and isinstance(value, str):
799 raise ValueError(self.unicode_err)
800
801 if isinstance(value, basestring):
802
803
804
805 if value:
806 value = [value]
807 else:
808
809 value = []
810 elif py3k and isinstance(value, list):
811
812 for i, item in enumerate(value):
813 if isinstance(item, str):
814 raise ValueError(self.unicode_err)
815
816
817 elif hasattr(value, 'read'):
818 value = file_generator(value)
819 elif value is None:
820 value = []
821 obj._body = value
822
823
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
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
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.encode(reason)
910
911 if self.stream:
912
913
914
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
919
920
921 dict.pop(headers, 'Content-Length', None)
922 self.body = ntob("")
923 else:
924
925
926 if dict.get(headers, 'Content-Length') is None:
927 content = self.collapse_body()
928 dict.__setitem__(headers, 'Content-Length', len(content))
929
930
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
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
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