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
17 """A callback and its metadata: failsafe, priority, and kwargs."""
18
19 callback = None
20 """
21 The bare callable that this Hook object is wrapping, which will
22 be called when the Hook is called."""
23
24 failsafe = False
25 """
26 If True, the callback is guaranteed to run even if other callbacks
27 from the same call point raise exceptions."""
28
29 priority = 50
30 """
31 Defines the order of execution for a list of Hooks. Priority numbers
32 should be limited to the closed interval [0, 100], but values outside
33 this range are acceptable, as are fractional values."""
34
35 kwargs = {}
36 """
37 A set of keyword arguments that will be passed to the
38 callable on each call."""
39
40 - def __init__(self, callback, failsafe=None, priority=None, **kwargs):
52
56
60
62 """Run self.callback(**self.kwargs)."""
63 return self.callback(**self.kwargs)
64
66 cls = self.__class__
67 return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
68 % (cls.__module__, cls.__name__, self.callback,
69 self.failsafe, self.priority,
70 ", ".join(['%s=%r' % (k, v)
71 for k, v in self.kwargs.items()])))
72
73
75
76 """A map of call points to lists of callbacks (Hook objects)."""
77
79 d = dict.__new__(cls)
80 for p in points or []:
81 d[p] = []
82 return d
83
86
87 - def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
90
91 - def run(self, point):
92 """Execute all registered Hooks (callbacks) for the given point."""
93 exc = None
94 hooks = self[point]
95 hooks.sort()
96 for hook in hooks:
97
98
99
100
101
102 if exc is None or hook.failsafe:
103 try:
104 hook()
105 except (KeyboardInterrupt, SystemExit):
106 raise
107 except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
108 cherrypy.InternalRedirect):
109 exc = sys.exc_info()[1]
110 except:
111 exc = sys.exc_info()[1]
112 cherrypy.log(traceback=True, severity=40)
113 if exc:
114 raise exc
115
117 newmap = self.__class__()
118
119
120 for k, v in self.items():
121 newmap[k] = v[:]
122 return newmap
123 copy = __copy__
124
126 cls = self.__class__
127 return "%s.%s(points=%r)" % (
128 cls.__module__,
129 cls.__name__,
130 copykeys(self)
131 )
132
133
134
135
147
148
157
158
167
168
170 """Attach error pages declared in config."""
171 if k != 'default':
172 k = int(k)
173 cherrypy.serving.request.error_page[k] = v
174
175
176 hookpoints = ['on_start_resource', 'before_request_body',
177 'before_handler', 'before_finalize',
178 'on_end_resource', 'on_end_request',
179 'before_error_response', 'after_error_response']
180
181
183
184 """An HTTP request.
185
186 This object represents the metadata of an HTTP request message;
187 that is, it contains attributes which describe the environment
188 in which the request URL, headers, and body were sent (if you
189 want tools to interpret the headers and body, those are elsewhere,
190 mostly in Tools). This 'metadata' consists of socket data,
191 transport characteristics, and the Request-Line. This object
192 also contains data regarding the configuration in effect for
193 the given URL, and the execution plan for generating a response.
194 """
195
196 prev = None
197 """
198 The previous Request object (if any). This should be None
199 unless we are processing an InternalRedirect."""
200
201
202 local = httputil.Host("127.0.0.1", 80)
203 "An httputil.Host(ip, port, hostname) object for the server socket."
204
205 remote = httputil.Host("127.0.0.1", 1111)
206 "An httputil.Host(ip, port, hostname) object for the client socket."
207
208 scheme = "http"
209 """
210 The protocol used between client and server. In most cases,
211 this will be either 'http' or 'https'."""
212
213 server_protocol = "HTTP/1.1"
214 """
215 The HTTP version for which the HTTP server is at least
216 conditionally compliant."""
217
218 base = ""
219 """The (scheme://host) portion of the requested URL.
220 In some cases (e.g. when proxying via mod_rewrite), this may contain
221 path segments which cherrypy.url uses when constructing url's, but
222 which otherwise are ignored by CherryPy. Regardless, this value
223 MUST NOT end in a slash."""
224
225
226 request_line = ""
227 """
228 The complete Request-Line received from the client. This is a
229 single string consisting of the request method, URI, and protocol
230 version (joined by spaces). Any final CRLF is removed."""
231
232 method = "GET"
233 """
234 Indicates the HTTP method to be performed on the resource identified
235 by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
236 DELETE. CherryPy allows any extension method; however, various HTTP
237 servers and gateways may restrict the set of allowable methods.
238 CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
239
240 query_string = ""
241 """
242 The query component of the Request-URI, a string of information to be
243 interpreted by the resource. The query portion of a URI follows the
244 path component, and is separated by a '?'. For example, the URI
245 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
246 'a=3&b=4'."""
247
248 query_string_encoding = 'utf8'
249 """
250 The encoding expected for query string arguments after % HEX HEX decoding).
251 If a query string is provided that cannot be decoded with this encoding,
252 404 is raised (since technically it's a different URI). If you want
253 arbitrary encodings to not error, set this to 'Latin-1'; you can then
254 encode back to bytes and re-decode to whatever encoding you like later.
255 """
256
257 protocol = (1, 1)
258 """The HTTP protocol version corresponding to the set
259 of features which should be allowed in the response. If BOTH
260 the client's request message AND the server's level of HTTP
261 compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
262 If either is 1.0, this attribute will be the tuple (1, 0).
263 Lower HTTP protocol versions are not explicitly supported."""
264
265 params = {}
266 """
267 A dict which combines query string (GET) and request entity (POST)
268 variables. This is populated in two stages: GET params are added
269 before the 'on_start_resource' hook, and POST params are added
270 between the 'before_request_body' and 'before_handler' hooks."""
271
272
273 header_list = []
274 """
275 A list of the HTTP request headers as (name, value) tuples.
276 In general, you should use request.headers (a dict) instead."""
277
278 headers = httputil.HeaderMap()
279 """
280 A dict-like object containing the request headers. Keys are header
281 names (in Title-Case format); however, you may get and set them in
282 a case-insensitive manner. That is, headers['Content-Type'] and
283 headers['content-type'] refer to the same value. Values are header
284 values (decoded according to :rfc:`2047` if necessary). See also:
285 httputil.HeaderMap, httputil.HeaderElement."""
286
287 cookie = SimpleCookie()
288 """See help(Cookie)."""
289
290 rfile = None
291 """
292 If the request included an entity (body), it will be available
293 as a stream in this attribute. However, the rfile will normally
294 be read for you between the 'before_request_body' hook and the
295 'before_handler' hook, and the resulting string is placed into
296 either request.params or the request.body attribute.
297
298 You may disable the automatic consumption of the rfile by setting
299 request.process_request_body to False, either in config for the desired
300 path, or in an 'on_start_resource' or 'before_request_body' hook.
301
302 WARNING: In almost every case, you should not attempt to read from the
303 rfile stream after CherryPy's automatic mechanism has read it. If you
304 turn off the automatic parsing of rfile, you should read exactly the
305 number of bytes specified in request.headers['Content-Length'].
306 Ignoring either of these warnings may result in a hung request thread
307 or in corruption of the next (pipelined) request.
308 """
309
310 process_request_body = True
311 """
312 If True, the rfile (if any) is automatically read and parsed,
313 and the result placed into request.params or request.body."""
314
315 methods_with_bodies = ("POST", "PUT")
316 """
317 A sequence of HTTP methods for which CherryPy will automatically
318 attempt to read a body from the rfile. If you are going to change
319 this property, modify it on the configuration (recommended)
320 or on the "hook point" `on_start_resource`.
321 """
322
323 body = None
324 """
325 If the request Content-Type is 'application/x-www-form-urlencoded'
326 or multipart, this will be None. Otherwise, this will be an instance
327 of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
328 can .read()); this value is set between the 'before_request_body' and
329 'before_handler' hooks (assuming that process_request_body is True)."""
330
331
332 dispatch = cherrypy.dispatch.Dispatcher()
333 """
334 The object which looks up the 'page handler' callable and collects
335 config for the current request based on the path_info, other
336 request attributes, and the application architecture. The core
337 calls the dispatcher as early as possible, passing it a 'path_info'
338 argument.
339
340 The default dispatcher discovers the page handler by matching path_info
341 to a hierarchical arrangement of objects, starting at request.app.root.
342 See help(cherrypy.dispatch) for more information."""
343
344 script_name = ""
345 """
346 The 'mount point' of the application which is handling this request.
347
348 This attribute MUST NOT end in a slash. If the script_name refers to
349 the root of the URI, it MUST be an empty string (not "/").
350 """
351
352 path_info = "/"
353 """
354 The 'relative path' portion of the Request-URI. This is relative
355 to the script_name ('mount point') of the application which is
356 handling this request."""
357
358 login = None
359 """
360 When authentication is used during the request processing this is
361 set to 'False' if it failed and to the 'username' value if it succeeded.
362 The default 'None' implies that no authentication happened."""
363
364
365
366 app = None
367 """The cherrypy.Application object which is handling this request."""
368
369 handler = None
370 """
371 The function, method, or other callable which CherryPy will call to
372 produce the response. The discovery of the handler and the arguments
373 it will receive are determined by the request.dispatch object.
374 By default, the handler is discovered by walking a tree of objects
375 starting at request.app.root, and is then passed all HTTP params
376 (from the query string and POST body) as keyword arguments."""
377
378 toolmaps = {}
379 """
380 A nested dict of all Toolboxes and Tools in effect for this request,
381 of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
382
383 config = None
384 """
385 A flat dict of all configuration entries which apply to the
386 current request. These entries are collected from global config,
387 application config (based on request.path_info), and from handler
388 config (exactly how is governed by the request.dispatch object in
389 effect for this request; by default, handler config can be attached
390 anywhere in the tree between request.app.root and the final handler,
391 and inherits downward)."""
392
393 is_index = None
394 """
395 This will be True if the current request is mapped to an 'index'
396 resource handler (also, a 'default' handler if path_info ends with
397 a slash). The value may be used to automatically redirect the
398 user-agent to a 'more canonical' URL which either adds or removes
399 the trailing slash. See cherrypy.tools.trailing_slash."""
400
401 hooks = HookMap(hookpoints)
402 """
403 A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
404 Each key is a str naming the hook point, and each value is a list
405 of hooks which will be called at that hook point during this request.
406 The list of hooks is generally populated as early as possible (mostly
407 from Tools specified in config), but may be extended at any time.
408 See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
409
410 error_response = cherrypy.HTTPError(500).set_response
411 """
412 The no-arg callable which will handle unexpected, untrapped errors
413 during request processing. This is not used for expected exceptions
414 (like NotFound, HTTPError, or HTTPRedirect) which are raised in
415 response to expected conditions (those should be customized either
416 via request.error_page or by overriding HTTPError.set_response).
417 By default, error_response uses HTTPError(500) to return a generic
418 error response to the user-agent."""
419
420 error_page = {}
421 """
422 A dict of {error code: response filename or callable} pairs.
423
424 The error code must be an int representing a given HTTP error code,
425 or the string 'default', which will be used if no matching entry
426 is found for a given numeric code.
427
428 If a filename is provided, the file should contain a Python string-
429 formatting template, and can expect by default to receive format
430 values with the mapping keys %(status)s, %(message)s, %(traceback)s,
431 and %(version)s. The set of format mappings can be extended by
432 overriding HTTPError.set_response.
433
434 If a callable is provided, it will be called by default with keyword
435 arguments 'status', 'message', 'traceback', and 'version', as for a
436 string-formatting template. The callable must return a string or
437 iterable of strings which will be set to response.body. It may also
438 override headers or perform any other processing.
439
440 If no entry is given for an error code, and no 'default' entry exists,
441 a default template will be used.
442 """
443
444 show_tracebacks = True
445 """
446 If True, unexpected errors encountered during request processing will
447 include a traceback in the response body."""
448
449 show_mismatched_params = True
450 """
451 If True, mismatched parameters encountered during PageHandler invocation
452 processing will be included in the response body."""
453
454 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
455 """The sequence of exceptions which Request.run does not trap."""
456
457 throw_errors = False
458 """
459 If True, Request.run will not trap any errors (except HTTPRedirect and
460 HTTPError, which are more properly called 'exceptions', not errors)."""
461
462 closed = False
463 """True once the close method has been called, False otherwise."""
464
465 stage = None
466 """
467 A string containing the stage reached in the request-handling process.
468 This is useful when debugging a live server with hung requests."""
469
470 namespaces = _cpconfig.NamespaceSet(
471 **{"hooks": hooks_namespace,
472 "request": request_namespace,
473 "response": response_namespace,
474 "error_page": error_page_namespace,
475 "tools": cherrypy.tools,
476 })
477
478 - def __init__(self, local_host, remote_host, scheme="http",
479 server_protocol="HTTP/1.1"):
480 """Populate a new Request object.
481
482 local_host should be an httputil.Host object with the server info.
483 remote_host should be an httputil.Host object with the client info.
484 scheme should be a string, either "http" or "https".
485 """
486 self.local = local_host
487 self.remote = remote_host
488 self.scheme = scheme
489 self.server_protocol = server_protocol
490
491 self.closed = False
492
493
494 self.error_page = self.error_page.copy()
495
496
497 self.namespaces = self.namespaces.copy()
498
499 self.stage = None
500
502 """Run cleanup code. (Core)"""
503 if not self.closed:
504 self.closed = True
505 self.stage = 'on_end_request'
506 self.hooks.run('on_end_request')
507 self.stage = 'close'
508
509 - def run(self, method, path, query_string, req_protocol, headers, rfile):
510 r"""Process the Request. (Core)
511
512 method, path, query_string, and req_protocol should be pulled directly
513 from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
514
515 path
516 This should be %XX-unquoted, but query_string should not be.
517
518 When using Python 2, they both MUST be byte strings,
519 not unicode strings.
520
521 When using Python 3, they both MUST be unicode strings,
522 not byte strings, and preferably not bytes \x00-\xFF
523 disguised as unicode.
524
525 headers
526 A list of (name, value) tuples.
527
528 rfile
529 A file-like object containing the HTTP request entity.
530
531 When run() is done, the returned object should have 3 attributes:
532
533 * status, e.g. "200 OK"
534 * header_list, a list of (name, value) tuples
535 * body, an iterable yielding strings
536
537 Consumer code (HTTP servers) should then access these response
538 attributes to build the outbound stream.
539
540 """
541 response = cherrypy.serving.response
542 self.stage = 'run'
543 try:
544 self.error_response = cherrypy.HTTPError(500).set_response
545
546 self.method = method
547 path = path or "/"
548 self.query_string = query_string or ''
549 self.params = {}
550
551
552
553
554
555
556
557
558
559
560
561
562
563 rp = int(req_protocol[5]), int(req_protocol[7])
564 sp = int(self.server_protocol[5]), int(self.server_protocol[7])
565 self.protocol = min(rp, sp)
566 response.headers.protocol = self.protocol
567
568
569 url = path
570 if query_string:
571 url += '?' + query_string
572 self.request_line = '%s %s %s' % (method, url, req_protocol)
573
574 self.header_list = list(headers)
575 self.headers = httputil.HeaderMap()
576
577 self.rfile = rfile
578 self.body = None
579
580 self.cookie = SimpleCookie()
581 self.handler = None
582
583
584
585 self.script_name = self.app.script_name
586 self.path_info = pi = path[len(self.script_name):]
587
588 self.stage = 'respond'
589 self.respond(pi)
590
591 except self.throws:
592 raise
593 except:
594 if self.throw_errors:
595 raise
596 else:
597
598
599 cherrypy.log(traceback=True, severity=40)
600 if self.show_tracebacks:
601 body = format_exc()
602 else:
603 body = ""
604 r = bare_error(body)
605 response.output_status, response.header_list, response.body = r
606
607 if self.method == "HEAD":
608
609 response.body = []
610
611 try:
612 cherrypy.log.access()
613 except:
614 cherrypy.log.error(traceback=True)
615
616 if response.timed_out:
617 raise cherrypy.TimeoutError()
618
619 return response
620
621
622
623
691
710
712 """Parse HTTP header data into Python structures. (Core)"""
713
714 headers = self.headers
715 for name, value in self.header_list:
716
717
718 name = name.title()
719 value = value.strip()
720
721
722
723
724
725 if "=?" in value:
726 dict.__setitem__(headers, name, httputil.decode_TEXT(value))
727 else:
728 dict.__setitem__(headers, name, value)
729
730
731
732 if name == 'Cookie':
733 try:
734 self.cookie.load(value)
735 except CookieError:
736 msg = "Illegal cookie name %s" % value.split('=')[0]
737 raise cherrypy.HTTPError(400, msg)
738
739 if not dict.__contains__(headers, 'Host'):
740
741
742
743 if self.protocol >= (1, 1):
744 msg = "HTTP/1.1 requires a 'Host' request header."
745 raise cherrypy.HTTPError(400, msg)
746 host = dict.get(headers, 'Host')
747 if not host:
748 host = self.local.name or self.local.ip
749 self.base = "%s://%s" % (self.scheme, host)
750
752 """Call a dispatcher (which sets self.handler and .config). (Core)"""
753
754
755
756 dispatch = self.app.find_config(
757 path, "request.dispatch", self.dispatch)
758
759
760 dispatch(path)
761
774
775
776
778 warnings.warn(
779 "body_params is deprecated in CherryPy 3.2, will be removed in "
780 "CherryPy 3.3.",
781 DeprecationWarning
782 )
783 return self.body.params
784 body_params = property(_get_body_params,
785 doc="""
786 If the request Content-Type is 'application/x-www-form-urlencoded' or
787 multipart, this will be a dict of the params pulled from the entity
788 body; that is, it will be the portion of request.params that come
789 from the message body (sometimes called "POST params", although they
790 can be sent with various HTTP method verbs). This value is set between
791 the 'before_request_body' and 'before_handler' hooks (assuming that
792 process_request_body is True).
793
794 Deprecated in 3.2, will be removed for 3.3 in favor of
795 :attr:`request.body.params<cherrypy._cprequest.RequestBody.params>`.""")
796
797
798 -class ResponseBody(object):
799
800 """The body of the HTTP response (the response entity)."""
801
802 if py3k:
803 unicode_err = ("Page handlers MUST return bytes. Use tools.encode "
804 "if you wish to return unicode.")
805
806 - def __get__(self, obj, objclass=None):
807 if obj is None:
808
809 return self
810 else:
811 return obj._body
812
813 - def __set__(self, obj, value):
814
815 if py3k and isinstance(value, str):
816 raise ValueError(self.unicode_err)
817
818 if isinstance(value, basestring):
819
820
821
822 if value:
823 value = [value]
824 else:
825
826 value = []
827 elif py3k and isinstance(value, list):
828
829 for i, item in enumerate(value):
830 if isinstance(item, str):
831 raise ValueError(self.unicode_err)
832
833
834 elif hasattr(value, 'read'):
835 value = file_generator(value)
836 elif value is None:
837 value = []
838 obj._body = value
839
840
842
843 """An HTTP Response, including status, headers, and body."""
844
845 status = ""
846 """The HTTP Status-Code and Reason-Phrase."""
847
848 header_list = []
849 """
850 A list of the HTTP response headers as (name, value) tuples.
851 In general, you should use response.headers (a dict) instead. This
852 attribute is generated from response.headers and is not valid until
853 after the finalize phase."""
854
855 headers = httputil.HeaderMap()
856 """
857 A dict-like object containing the response headers. Keys are header
858 names (in Title-Case format); however, you may get and set them in
859 a case-insensitive manner. That is, headers['Content-Type'] and
860 headers['content-type'] refer to the same value. Values are header
861 values (decoded according to :rfc:`2047` if necessary).
862
863 .. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement`
864 """
865
866 cookie = SimpleCookie()
867 """See help(Cookie)."""
868
869 body = ResponseBody()
870 """The body (entity) of the HTTP response."""
871
872 time = None
873 """The value of time.time() when created. Use in HTTP dates."""
874
875 timeout = 300
876 """Seconds after which the response will be aborted."""
877
878 timed_out = False
879 """
880 Flag to indicate the response should be aborted, because it has
881 exceeded its timeout."""
882
883 stream = False
884 """If False, buffer the response body."""
885
901
902 - def collapse_body(self):
903 """Collapse self.body to a single string; replace it and return it."""
904 if isinstance(self.body, basestring):
905 return self.body
906
907 newbody = []
908 for chunk in self.body:
909 if py3k and not isinstance(chunk, bytes):
910 raise TypeError("Chunk %s is not of type 'bytes'." %
911 repr(chunk))
912 newbody.append(chunk)
913 newbody = ntob('').join(newbody)
914
915 self.body = newbody
916 return newbody
917
919 """Transform headers (and cookies) into self.header_list. (Core)"""
920 try:
921 code, reason, _ = httputil.valid_status(self.status)
922 except ValueError:
923 raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0])
924
925 headers = self.headers
926
927 self.status = "%s %s" % (code, reason)
928 self.output_status = ntob(str(code), 'ascii') + \
929 ntob(" ") + headers.encode(reason)
930
931 if self.stream:
932
933
934
935 if dict.get(headers, 'Content-Length') is None:
936 dict.pop(headers, 'Content-Length', None)
937 elif code < 200 or code in (204, 205, 304):
938
939
940
941 dict.pop(headers, 'Content-Length', None)
942 self.body = ntob("")
943 else:
944
945
946 if dict.get(headers, 'Content-Length') is None:
947 content = self.collapse_body()
948 dict.__setitem__(headers, 'Content-Length', len(content))
949
950
951 self.header_list = h = headers.output()
952
953 cookie = self.cookie.output()
954 if cookie:
955 for line in cookie.split("\n"):
956 if line.endswith("\r"):
957
958 line = line[:-1]
959 name, value = line.split(": ", 1)
960 if isinstance(name, unicodestr):
961 name = name.encode("ISO-8859-1")
962 if isinstance(value, unicodestr):
963 value = headers.encode(value)
964 h.append((name, value))
965
967 """If now > self.time + self.timeout, set self.timed_out.
968
969 This purposefully sets a flag, rather than raising an error,
970 so that a monitor thread can interrupt the Response thread.
971 """
972 if time.time() > self.time + self.timeout:
973 self.timed_out = True
974