~j5-dev/+junk/cherrypy3-3.2.0rc1

« back to all changes in this revision

Viewing changes to cherrypy/_cprequest.py

  • Committer: steveh at sjsoft
  • Date: 2010-07-01 13:07:15 UTC
  • Revision ID: steveh@sjsoft.com-20100701130715-w56oim8346qzqlka
New upstream release

Show diffs side-by-side

added added

removed removed

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