1
2 import Cookie
3 import os
4 import sys
5 import time
6 import types
7
8 import cherrypy
9 from cherrypy import _cpcgifs, _cpconfig
10 from cherrypy._cperror import format_exc, bare_error
11 from cherrypy.lib import http, file_generator
12
13
15 """A callback and its metadata: failsafe, priority, and kwargs."""
16
17 __metaclass__ = cherrypy._AttributeDocstrings
18
19 callback = None
20 callback__doc = """
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 failsafe__doc = """
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 priority__doc = """
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 kwargs__doc = """
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
55
57 """Run self.callback(**self.kwargs)."""
58 return self.callback(**self.kwargs)
59
61 cls = self.__class__
62 return ("%s.%s(callback=%r, failsafe=%r, priority=%r, %s)"
63 % (cls.__module__, cls.__name__, self.callback,
64 self.failsafe, self.priority,
65 ", ".join(['%s=%r' % (k, v)
66 for k, v in self.kwargs.iteritems()])))
67
68
70 """A map of call points to lists of callbacks (Hook objects)."""
71
73 d = dict.__new__(cls)
74 for p in points or []:
75 d[p] = []
76 return d
77
80
81 - def attach(self, point, callback, failsafe=None, priority=None, **kwargs):
84
85 - def run(self, point):
86 """Execute all registered Hooks (callbacks) for the given point."""
87 exc = None
88 hooks = self[point]
89 hooks.sort()
90 for hook in hooks:
91
92
93
94
95
96 if exc is None or hook.failsafe:
97 try:
98 hook()
99 except (KeyboardInterrupt, SystemExit):
100 raise
101 except (cherrypy.HTTPError, cherrypy.HTTPRedirect,
102 cherrypy.InternalRedirect):
103 exc = sys.exc_info()[1]
104 except:
105 exc = sys.exc_info()[1]
106 cherrypy.log(traceback=True, severity=40)
107 if exc:
108 raise
109
111 newmap = self.__class__()
112
113
114 for k, v in self.iteritems():
115 newmap[k] = v[:]
116 return newmap
117 copy = __copy__
118
120 cls = self.__class__
121 return "%s.%s(points=%r)" % (cls.__module__, cls.__name__, self.keys())
122
123
124
125
127 """Attach bare hooks declared in config."""
128
129
130
131 hookpoint = k.split(".", 1)[0]
132 if isinstance(v, basestring):
133 v = cherrypy.lib.attributes(v)
134 if not isinstance(v, Hook):
135 v = Hook(v)
136 cherrypy.request.hooks[hookpoint].append(v)
137
139 """Attach request attributes declared in config."""
140 setattr(cherrypy.request, k, v)
141
143 """Attach response attributes declared in config."""
144 setattr(cherrypy.response, k, v)
145
147 """Attach error pages declared in config."""
148 if k != 'default':
149 k = int(k)
150 cherrypy.request.error_page[k] = v
151
152
153 hookpoints = ['on_start_resource', 'before_request_body',
154 'before_handler', 'before_finalize',
155 'on_end_resource', 'on_end_request',
156 'before_error_response', 'after_error_response']
157
158
160 """An HTTP request.
161
162 This object represents the metadata of an HTTP request message;
163 that is, it contains attributes which describe the environment
164 in which the request URL, headers, and body were sent (if you
165 want tools to interpret the headers and body, those are elsewhere,
166 mostly in Tools). This 'metadata' consists of socket data,
167 transport characteristics, and the Request-Line. This object
168 also contains data regarding the configuration in effect for
169 the given URL, and the execution plan for generating a response.
170 """
171
172 __metaclass__ = cherrypy._AttributeDocstrings
173
174 prev = None
175 prev__doc = """
176 The previous Request object (if any). This should be None
177 unless we are processing an InternalRedirect."""
178
179
180 local = http.Host("127.0.0.1", 80)
181 local__doc = \
182 "An http.Host(ip, port, hostname) object for the server socket."
183
184 remote = http.Host("127.0.0.1", 1111)
185 remote__doc = \
186 "An http.Host(ip, port, hostname) object for the client socket."
187
188 scheme = "http"
189 scheme__doc = """
190 The protocol used between client and server. In most cases,
191 this will be either 'http' or 'https'."""
192
193 server_protocol = "HTTP/1.1"
194 server_protocol__doc = """
195 The HTTP version for which the HTTP server is at least
196 conditionally compliant."""
197
198 base = ""
199 base__doc = """The (scheme://host) portion of the requested URL."""
200
201
202 request_line = ""
203 request_line__doc = """
204 The complete Request-Line received from the client. This is a
205 single string consisting of the request method, URI, and protocol
206 version (joined by spaces). Any final CRLF is removed."""
207
208 method = "GET"
209 method__doc = """
210 Indicates the HTTP method to be performed on the resource identified
211 by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
212 DELETE. CherryPy allows any extension method; however, various HTTP
213 servers and gateways may restrict the set of allowable methods.
214 CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
215
216 query_string = ""
217 query_string__doc = """
218 The query component of the Request-URI, a string of information to be
219 interpreted by the resource. The query portion of a URI follows the
220 path component, and is separated by a '?'. For example, the URI
221 'http://www.cherrypy.org/wiki?a=3&b=4' has the query component,
222 'a=3&b=4'."""
223
224 protocol = (1, 1)
225 protocol__doc = """The HTTP protocol version corresponding to the set
226 of features which should be allowed in the response. If BOTH
227 the client's request message AND the server's level of HTTP
228 compliance is HTTP/1.1, this attribute will be the tuple (1, 1).
229 If either is 1.0, this attribute will be the tuple (1, 0).
230 Lower HTTP protocol versions are not explicitly supported."""
231
232 params = {}
233 params__doc = """
234 A dict which combines query string (GET) and request entity (POST)
235 variables. This is populated in two stages: GET params are added
236 before the 'on_start_resource' hook, and POST params are added
237 between the 'before_request_body' and 'before_handler' hooks."""
238
239
240 header_list = []
241 header_list__doc = """
242 A list of the HTTP request headers as (name, value) tuples.
243 In general, you should use request.headers (a dict) instead."""
244
245 headers = http.HeaderMap()
246 headers__doc = """
247 A dict-like object containing the request headers. Keys are header
248 names (in Title-Case format); however, you may get and set them in
249 a case-insensitive manner. That is, headers['Content-Type'] and
250 headers['content-type'] refer to the same value. Values are header
251 values (decoded according to RFC 2047 if necessary). See also:
252 http.HeaderMap, http.HeaderElement."""
253
254 cookie = Cookie.SimpleCookie()
255 cookie__doc = """See help(Cookie)."""
256
257 rfile = None
258 rfile__doc = """
259 If the request included an entity (body), it will be available
260 as a stream in this attribute. However, the rfile will normally
261 be read for you between the 'before_request_body' hook and the
262 'before_handler' hook, and the resulting string is placed into
263 either request.params or the request.body attribute.
264
265 You may disable the automatic consumption of the rfile by setting
266 request.process_request_body to False, either in config for the desired
267 path, or in an 'on_start_resource' or 'before_request_body' hook.
268
269 WARNING: In almost every case, you should not attempt to read from the
270 rfile stream after CherryPy's automatic mechanism has read it. If you
271 turn off the automatic parsing of rfile, you should read exactly the
272 number of bytes specified in request.headers['Content-Length'].
273 Ignoring either of these warnings may result in a hung request thread
274 or in corruption of the next (pipelined) request.
275 """
276
277 process_request_body = True
278 process_request_body__doc = """
279 If True, the rfile (if any) is automatically read and parsed,
280 and the result placed into request.params or request.body."""
281
282 methods_with_bodies = ("POST", "PUT")
283 methods_with_bodies__doc = """
284 A sequence of HTTP methods for which CherryPy will automatically
285 attempt to read a body from the rfile."""
286
287 body = None
288 body__doc = """
289 If the request Content-Type is 'application/x-www-form-urlencoded'
290 or multipart, this will be None. Otherwise, this will contain the
291 request entity body as a string; this value is set between the
292 'before_request_body' and 'before_handler' hooks (assuming that
293 process_request_body is True)."""
294
295 body_params = None
296 body_params__doc = """
297 If the request Content-Type is 'application/x-www-form-urlencoded' or
298 multipart, this will be a dict of the params pulled from the entity
299 body; that is, it will be the portion of request.params that come
300 from the message body (sometimes called "POST params", although they
301 can be sent with various HTTP method verbs). This value is set between
302 the 'before_request_body' and 'before_handler' hooks (assuming that
303 process_request_body is True)."""
304
305
306 dispatch = cherrypy.dispatch.Dispatcher()
307 dispatch__doc = """
308 The object which looks up the 'page handler' callable and collects
309 config for the current request based on the path_info, other
310 request attributes, and the application architecture. The core
311 calls the dispatcher as early as possible, passing it a 'path_info'
312 argument.
313
314 The default dispatcher discovers the page handler by matching path_info
315 to a hierarchical arrangement of objects, starting at request.app.root.
316 See help(cherrypy.dispatch) for more information."""
317
318 script_name = ""
319 script_name__doc = """
320 The 'mount point' of the application which is handling this request.
321
322 This attribute MUST NOT end in a slash. If the script_name refers to
323 the root of the URI, it MUST be an empty string (not "/").
324 """
325
326 path_info = "/"
327 path_info__doc = """
328 The 'relative path' portion of the Request-URI. This is relative
329 to the script_name ('mount point') of the application which is
330 handling this request."""
331
332 login = None
333 login__doc = """
334 When authentication is used during the request processing this is
335 set to 'False' if it failed and to the 'username' value if it succeeded.
336 The default 'None' implies that no authentication happened."""
337
338
339
340 app = None
341 app__doc = \
342 """The cherrypy.Application object which is handling this request."""
343
344 handler = None
345 handler__doc = """
346 The function, method, or other callable which CherryPy will call to
347 produce the response. The discovery of the handler and the arguments
348 it will receive are determined by the request.dispatch object.
349 By default, the handler is discovered by walking a tree of objects
350 starting at request.app.root, and is then passed all HTTP params
351 (from the query string and POST body) as keyword arguments."""
352
353 toolmaps = {}
354 toolmaps__doc = """
355 A nested dict of all Toolboxes and Tools in effect for this request,
356 of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
357
358 config = None
359 config__doc = """
360 A flat dict of all configuration entries which apply to the
361 current request. These entries are collected from global config,
362 application config (based on request.path_info), and from handler
363 config (exactly how is governed by the request.dispatch object in
364 effect for this request; by default, handler config can be attached
365 anywhere in the tree between request.app.root and the final handler,
366 and inherits downward)."""
367
368 is_index = None
369 is_index__doc = """
370 This will be True if the current request is mapped to an 'index'
371 resource handler (also, a 'default' handler if path_info ends with
372 a slash). The value may be used to automatically redirect the
373 user-agent to a 'more canonical' URL which either adds or removes
374 the trailing slash. See cherrypy.tools.trailing_slash."""
375
376 hooks = HookMap(hookpoints)
377 hooks__doc = """
378 A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
379 Each key is a str naming the hook point, and each value is a list
380 of hooks which will be called at that hook point during this request.
381 The list of hooks is generally populated as early as possible (mostly
382 from Tools specified in config), but may be extended at any time.
383 See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
384
385 error_response = cherrypy.HTTPError(500).set_response
386 error_response__doc = """
387 The no-arg callable which will handle unexpected, untrapped errors
388 during request processing. This is not used for expected exceptions
389 (like NotFound, HTTPError, or HTTPRedirect) which are raised in
390 response to expected conditions (those should be customized either
391 via request.error_page or by overriding HTTPError.set_response).
392 By default, error_response uses HTTPError(500) to return a generic
393 error response to the user-agent."""
394
395 error_page = {}
396 error_page__doc = """
397 A dict of {error code: response filename or callable} pairs.
398
399 The error code must be an int representing a given HTTP error code,
400 or the string 'default', which will be used if no matching entry
401 is found for a given numeric code.
402
403 If a filename is provided, the file should contain a Python string-
404 formatting template, and can expect by default to receive format
405 values with the mapping keys %(status)s, %(message)s, %(traceback)s,
406 and %(version)s. The set of format mappings can be extended by
407 overriding HTTPError.set_response.
408
409 If a callable is provided, it will be called by default with keyword
410 arguments 'status', 'message', 'traceback', and 'version', as for a
411 string-formatting template. The callable must return a string which
412 will be set to response.body. It may also override headers or perform
413 any other processing.
414
415 If no entry is given for an error code, and no 'default' entry exists,
416 a default template will be used.
417 """
418
419 show_tracebacks = True
420 show_tracebacks__doc = """
421 If True, unexpected errors encountered during request processing will
422 include a traceback in the response body."""
423
424 throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect)
425 throws__doc = \
426 """The sequence of exceptions which Request.run does not trap."""
427
428 throw_errors = False
429 throw_errors__doc = """
430 If True, Request.run will not trap any errors (except HTTPRedirect and
431 HTTPError, which are more properly called 'exceptions', not errors)."""
432
433 closed = False
434 closed__doc = """
435 True once the close method has been called, False otherwise."""
436
437 stage = None
438 stage__doc = """
439 A string containing the stage reached in the request-handling process.
440 This is useful when debugging a live server with hung requests."""
441
442 namespaces = _cpconfig.NamespaceSet(
443 **{"hooks": hooks_namespace,
444 "request": request_namespace,
445 "response": response_namespace,
446 "error_page": error_page_namespace,
447 "tools": cherrypy.tools,
448 })
449
450 - def __init__(self, local_host, remote_host, scheme="http",
451 server_protocol="HTTP/1.1"):
452 """Populate a new Request object.
453
454 local_host should be an http.Host object with the server info.
455 remote_host should be an http.Host object with the client info.
456 scheme should be a string, either "http" or "https".
457 """
458 self.local = local_host
459 self.remote = remote_host
460 self.scheme = scheme
461 self.server_protocol = server_protocol
462
463 self.closed = False
464
465
466 self.error_page = self.error_page.copy()
467
468
469 self.namespaces = self.namespaces.copy()
470
471 self.stage = None
472
474 """Run cleanup code. (Core)"""
475 if not self.closed:
476 self.closed = True
477 self.stage = 'on_end_request'
478 self.hooks.run('on_end_request')
479 self.stage = 'close'
480
481 - def run(self, method, path, query_string, req_protocol, headers, rfile):
482 """Process the Request. (Core)
483
484 method, path, query_string, and req_protocol should be pulled directly
485 from the Request-Line (e.g. "GET /path?key=val HTTP/1.0").
486 path should be %XX-unquoted, but query_string should not be.
487 headers should be a list of (name, value) tuples.
488 rfile should be a file-like object containing the HTTP request entity.
489
490 When run() is done, the returned object should have 3 attributes:
491 status, e.g. "200 OK"
492 header_list, a list of (name, value) tuples
493 body, an iterable yielding strings
494
495 Consumer code (HTTP servers) should then access these response
496 attributes to build the outbound stream.
497
498 """
499 self.stage = 'run'
500 try:
501 self.error_response = cherrypy.HTTPError(500).set_response
502
503 self.method = method
504 path = path or "/"
505 self.query_string = query_string or ''
506
507
508
509
510
511
512
513
514
515
516
517
518
519 rp = int(req_protocol[5]), int(req_protocol[7])
520 sp = int(self.server_protocol[5]), int(self.server_protocol[7])
521 self.protocol = min(rp, sp)
522
523
524 url = path
525 if query_string:
526 url += '?' + query_string
527 self.request_line = '%s %s %s' % (method, url, req_protocol)
528
529 self.header_list = list(headers)
530 self.rfile = rfile
531 self.headers = http.HeaderMap()
532 self.cookie = Cookie.SimpleCookie()
533 self.handler = None
534
535
536
537 self.script_name = self.app.script_name
538 self.path_info = pi = path[len(self.script_name):]
539
540 self.stage = 'respond'
541 self.respond(pi)
542
543 except self.throws:
544 raise
545 except:
546 if self.throw_errors:
547 raise
548 else:
549
550
551 cherrypy.log(traceback=True, severity=40)
552 if self.show_tracebacks:
553 body = format_exc()
554 else:
555 body = ""
556 r = bare_error(body)
557 response = cherrypy.response
558 response.status, response.header_list, response.body = r
559
560 if self.method == "HEAD":
561
562 cherrypy.response.body = []
563
564 cherrypy.log.access()
565
566 if cherrypy.response.timed_out:
567 raise cherrypy.TimeoutError()
568
569 return cherrypy.response
570
625
627 """Parse HTTP header data into Python structures. (Core)"""
628 self.params = http.parse_query_string(self.query_string)
629
630
631 headers = self.headers
632 for name, value in self.header_list:
633
634
635 name = name.title()
636 value = value.strip()
637
638
639
640
641 if "=?" in value:
642 dict.__setitem__(headers, name, http.decode_TEXT(value))
643 else:
644 dict.__setitem__(headers, name, value)
645
646
647
648 if name == 'Cookie':
649 try:
650 self.cookie.load(value)
651 except Cookie.CookieError:
652 msg = "Illegal cookie name %s" % value.split('=')[0]
653 raise cherrypy.HTTPError(400, msg)
654
655 if not dict.__contains__(headers, 'Host'):
656
657
658
659 if self.protocol >= (1, 1):
660 msg = "HTTP/1.1 requires a 'Host' request header."
661 raise cherrypy.HTTPError(400, msg)
662 host = dict.get(headers, 'Host')
663 if not host:
664 host = self.local.name or self.local.ip
665 self.base = "%s://%s" % (self.scheme, host)
666
668 """Call a dispatcher (which sets self.handler and .config). (Core)"""
669 dispatch = self.dispatch
670
671
672
673 trail = path or "/"
674 while trail:
675 nodeconf = self.app.config.get(trail, {})
676
677 d = nodeconf.get("request.dispatch")
678 if d:
679 dispatch = d
680 break
681
682 lastslash = trail.rfind("/")
683 if lastslash == -1:
684 break
685 elif lastslash == 0 and trail != "/":
686 trail = "/"
687 else:
688 trail = trail[:lastslash]
689
690
691 dispatch(path)
692
693 - def process_body(self):
694 """Convert request.rfile into request.params (or request.body). (Core)"""
695 if not self.headers.get("Content-Length", ""):
696
697
698
699
700
701
702
703
704
705 raise cherrypy.HTTPError(411)
706
707
708
709
710
711 if 'Content-Type' not in self.headers:
712 h = http.HeaderMap(self.headers.items())
713 h['Content-Type'] = ''
714 else:
715 h = self.headers
716
717 try:
718 forms = _cpcgifs.FieldStorage(fp=self.rfile,
719 headers=h,
720
721 environ={'REQUEST_METHOD': "POST"},
722 keep_blank_values=1)
723 except Exception, e:
724 if e.__class__.__name__ == 'MaxSizeExceeded':
725
726 raise cherrypy.HTTPError(413)
727 else:
728 raise
729
730
731
732
733
734 if forms.file:
735
736 self.body = forms.file
737 else:
738 self.body_params = p = http.params_from_CGI_form(forms)
739 self.params.update(p)
740
752
753
755 """The body of the HTTP response (the response entity)."""
756
757 - def __get__(self, obj, objclass=None):
758 if obj is None:
759
760 return self
761 else:
762 return obj._body
763
764 - def __set__(self, obj, value):
765
766 if isinstance(value, basestring):
767
768
769
770 if value:
771 value = [value]
772 else:
773
774 value = []
775 elif isinstance(value, types.FileType):
776 value = file_generator(value)
777 elif value is None:
778 value = []
779 obj._body = value
780
781
783 """An HTTP Response, including status, headers, and body.
784
785 Application developers should use Response.headers (a dict) to
786 set or modify HTTP response headers. When the response is finalized,
787 Response.headers is transformed into Response.header_list as
788 (key, value) tuples.
789 """
790
791 __metaclass__ = cherrypy._AttributeDocstrings
792
793
794 status = ""
795 status__doc = """The HTTP Status-Code and Reason-Phrase."""
796
797 header_list = []
798 header_list__doc = """
799 A list of the HTTP response headers as (name, value) tuples.
800 In general, you should use response.headers (a dict) instead."""
801
802 headers = http.HeaderMap()
803 headers__doc = """
804 A dict-like object containing the response headers. Keys are header
805 names (in Title-Case format); however, you may get and set them in
806 a case-insensitive manner. That is, headers['Content-Type'] and
807 headers['content-type'] refer to the same value. Values are header
808 values (decoded according to RFC 2047 if necessary). See also:
809 http.HeaderMap, http.HeaderElement."""
810
811 cookie = Cookie.SimpleCookie()
812 cookie__doc = """See help(Cookie)."""
813
814 body = Body()
815 body__doc = """The body (entity) of the HTTP response."""
816
817 time = None
818 time__doc = """The value of time.time() when created. Use in HTTP dates."""
819
820 timeout = 300
821 timeout__doc = """Seconds after which the response will be aborted."""
822
823 timed_out = False
824 timed_out__doc = """
825 Flag to indicate the response should be aborted, because it has
826 exceeded its timeout."""
827
828 stream = False
829 stream__doc = """If False, buffer the response body."""
830
846
847 - def collapse_body(self):
848 """Collapse self.body to a single string; replace it and return it."""
849 newbody = ''.join([chunk for chunk in self.body])
850 self.body = newbody
851 return newbody
852
854 """Transform headers (and cookies) into self.header_list. (Core)"""
855 try:
856 code, reason, _ = http.valid_status(self.status)
857 except ValueError, x:
858 raise cherrypy.HTTPError(500, x.args[0])
859
860 self.status = "%s %s" % (code, reason)
861
862 headers = self.headers
863 if self.stream:
864 if dict.get(headers, 'Content-Length') is None:
865 dict.pop(headers, 'Content-Length', None)
866 elif code < 200 or code in (204, 205, 304):
867
868
869
870 dict.pop(headers, 'Content-Length', None)
871 self.body = ""
872 else:
873
874
875 if dict.get(headers, 'Content-Length') is None:
876 content = self.collapse_body()
877 dict.__setitem__(headers, 'Content-Length', len(content))
878
879
880 self.header_list = h = headers.output(cherrypy.request.protocol)
881
882 cookie = self.cookie.output()
883 if cookie:
884 for line in cookie.split("\n"):
885 if line.endswith("\r"):
886
887 line = line[:-1]
888 name, value = line.split(": ", 1)
889 h.append((name, value))
890
892 """If now > self.time + self.timeout, set self.timed_out.
893
894 This purposefully sets a flag, rather than raising an error,
895 so that a monitor thread can interrupt the Response thread.
896 """
897 if time.time() > self.time + self.timeout:
898 self.timed_out = True
899