mirror of
https://github.com/rembo10/headphones.git
synced 2026-07-19 23:44:01 +01:00
cherrypy: 18.8.0 -> 6387a2b
This commit is contained in:
+23
-17
@@ -57,9 +57,11 @@ These API's are described in the `CherryPy specification
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pkg_resources
|
import importlib.metadata as importlib_metadata
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
# fall back for python <= 3.7
|
||||||
|
# This try/except can be removed with py <= 3.7 support
|
||||||
|
import importlib_metadata
|
||||||
|
|
||||||
from threading import local as _local
|
from threading import local as _local
|
||||||
|
|
||||||
@@ -109,7 +111,7 @@ tree = _cptree.Tree()
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__version__ = pkg_resources.require('cherrypy')[0].version
|
__version__ = importlib_metadata.version('cherrypy')
|
||||||
except Exception:
|
except Exception:
|
||||||
__version__ = 'unknown'
|
__version__ = 'unknown'
|
||||||
|
|
||||||
@@ -181,24 +183,28 @@ def quickstart(root=None, script_name='', config=None):
|
|||||||
class _Serving(_local):
|
class _Serving(_local):
|
||||||
"""An interface for registering request and response objects.
|
"""An interface for registering request and response objects.
|
||||||
|
|
||||||
Rather than have a separate "thread local" object for the request and
|
Rather than have a separate "thread local" object for the request
|
||||||
the response, this class works as a single threadlocal container for
|
and the response, this class works as a single threadlocal container
|
||||||
both objects (and any others which developers wish to define). In this
|
for both objects (and any others which developers wish to define).
|
||||||
way, we can easily dump those objects when we stop/start a new HTTP
|
In this way, we can easily dump those objects when we stop/start a
|
||||||
conversation, yet still refer to them as module-level globals in a
|
new HTTP conversation, yet still refer to them as module-level
|
||||||
thread-safe way.
|
globals in a thread-safe way.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = _cprequest.Request(_httputil.Host('127.0.0.1', 80),
|
request = _cprequest.Request(_httputil.Host('127.0.0.1', 80),
|
||||||
_httputil.Host('127.0.0.1', 1111))
|
_httputil.Host('127.0.0.1', 1111))
|
||||||
|
"""The request object for the current thread.
|
||||||
|
|
||||||
|
In the main thread, and any threads which are not receiving HTTP
|
||||||
|
requests, this is None.
|
||||||
"""
|
"""
|
||||||
The request object for the current thread. In the main thread,
|
|
||||||
and any threads which are not receiving HTTP requests, this is None."""
|
|
||||||
|
|
||||||
response = _cprequest.Response()
|
response = _cprequest.Response()
|
||||||
|
"""The response object for the current thread.
|
||||||
|
|
||||||
|
In the main thread, and any threads which are not receiving HTTP
|
||||||
|
requests, this is None.
|
||||||
"""
|
"""
|
||||||
The response object for the current thread. In the main thread,
|
|
||||||
and any threads which are not receiving HTTP requests, this is None."""
|
|
||||||
|
|
||||||
def load(self, request, response):
|
def load(self, request, response):
|
||||||
self.request = request
|
self.request = request
|
||||||
@@ -316,8 +322,8 @@ class _GlobalLogManager(_cplogging.LogManager):
|
|||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
"""Log the given message to the app.log or global log.
|
"""Log the given message to the app.log or global log.
|
||||||
|
|
||||||
Log the given message to the app.log or global
|
Log the given message to the app.log or global log as
|
||||||
log as appropriate.
|
appropriate.
|
||||||
"""
|
"""
|
||||||
# Do NOT use try/except here. See
|
# Do NOT use try/except here. See
|
||||||
# https://github.com/cherrypy/cherrypy/issues/945
|
# https://github.com/cherrypy/cherrypy/issues/945
|
||||||
@@ -330,8 +336,8 @@ class _GlobalLogManager(_cplogging.LogManager):
|
|||||||
def access(self):
|
def access(self):
|
||||||
"""Log an access message to the app.log or global log.
|
"""Log an access message to the app.log or global log.
|
||||||
|
|
||||||
Log the given message to the app.log or global
|
Log the given message to the app.log or global log as
|
||||||
log as appropriate.
|
appropriate.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return request.app.log.access()
|
return request.app.log.access()
|
||||||
|
|||||||
@@ -313,7 +313,10 @@ class Checker(object):
|
|||||||
|
|
||||||
# -------------------- Specific config warnings -------------------- #
|
# -------------------- Specific config warnings -------------------- #
|
||||||
def check_localhost(self):
|
def check_localhost(self):
|
||||||
"""Warn if any socket_host is 'localhost'. See #711."""
|
"""Warn if any socket_host is 'localhost'.
|
||||||
|
|
||||||
|
See #711.
|
||||||
|
"""
|
||||||
for k, v in cherrypy.config.items():
|
for k, v in cherrypy.config.items():
|
||||||
if k == 'server.socket_host' and v == 'localhost':
|
if k == 'server.socket_host' and v == 'localhost':
|
||||||
warnings.warn("The use of 'localhost' as a socket host can "
|
warnings.warn("The use of 'localhost' as a socket host can "
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""
|
"""Configuration system for CherryPy.
|
||||||
Configuration system for CherryPy.
|
|
||||||
|
|
||||||
Configuration in CherryPy is implemented via dictionaries. Keys are strings
|
Configuration in CherryPy is implemented via dictionaries. Keys are strings
|
||||||
which name the mapped value, which may be of any type.
|
which name the mapped value, which may be of any type.
|
||||||
@@ -132,8 +131,8 @@ def _if_filename_register_autoreload(ob):
|
|||||||
def merge(base, other):
|
def merge(base, other):
|
||||||
"""Merge one app config (from a dict, file, or filename) into another.
|
"""Merge one app config (from a dict, file, or filename) into another.
|
||||||
|
|
||||||
If the given config is a filename, it will be appended to
|
If the given config is a filename, it will be appended to the list
|
||||||
the list of files to monitor for "autoreload" changes.
|
of files to monitor for "autoreload" changes.
|
||||||
"""
|
"""
|
||||||
_if_filename_register_autoreload(other)
|
_if_filename_register_autoreload(other)
|
||||||
|
|
||||||
|
|||||||
+24
-30
@@ -1,9 +1,10 @@
|
|||||||
"""CherryPy dispatchers.
|
"""CherryPy dispatchers.
|
||||||
|
|
||||||
A 'dispatcher' is the object which looks up the 'page handler' callable
|
A 'dispatcher' is the object which looks up the 'page handler' callable
|
||||||
and collects config for the current request based on the path_info, other
|
and collects config for the current request based on the path_info,
|
||||||
request attributes, and the application architecture. The core calls the
|
other request attributes, and the application architecture. The core
|
||||||
dispatcher as early as possible, passing it a 'path_info' argument.
|
calls the dispatcher as early as possible, passing it a 'path_info'
|
||||||
|
argument.
|
||||||
|
|
||||||
The default dispatcher discovers the page handler by matching path_info
|
The default dispatcher discovers the page handler by matching path_info
|
||||||
to a hierarchical arrangement of objects, starting at request.app.root.
|
to a hierarchical arrangement of objects, starting at request.app.root.
|
||||||
@@ -21,7 +22,6 @@ import cherrypy
|
|||||||
|
|
||||||
|
|
||||||
class PageHandler(object):
|
class PageHandler(object):
|
||||||
|
|
||||||
"""Callable which sets response.body."""
|
"""Callable which sets response.body."""
|
||||||
|
|
||||||
def __init__(self, callable, *args, **kwargs):
|
def __init__(self, callable, *args, **kwargs):
|
||||||
@@ -64,8 +64,7 @@ class PageHandler(object):
|
|||||||
|
|
||||||
|
|
||||||
def test_callable_spec(callable, callable_args, callable_kwargs):
|
def test_callable_spec(callable, callable_args, callable_kwargs):
|
||||||
"""
|
"""Inspect callable and test to see if the given args are suitable for it.
|
||||||
Inspect callable and test to see if the given args are suitable for it.
|
|
||||||
|
|
||||||
When an error occurs during the handler's invoking stage there are 2
|
When an error occurs during the handler's invoking stage there are 2
|
||||||
erroneous cases:
|
erroneous cases:
|
||||||
@@ -252,16 +251,16 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
|
|
||||||
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
|
"""CherryPy Dispatcher which walks a tree of objects to find a handler.
|
||||||
|
|
||||||
The tree is rooted at cherrypy.request.app.root, and each hierarchical
|
The tree is rooted at cherrypy.request.app.root, and each
|
||||||
component in the path_info argument is matched to a corresponding nested
|
hierarchical component in the path_info argument is matched to a
|
||||||
attribute of the root object. Matching handlers must have an 'exposed'
|
corresponding nested attribute of the root object. Matching handlers
|
||||||
attribute which evaluates to True. The special method name "index"
|
must have an 'exposed' attribute which evaluates to True. The
|
||||||
matches a URI which ends in a slash ("/"). The special method name
|
special method name "index" matches a URI which ends in a slash
|
||||||
"default" may match a portion of the path_info (but only when no longer
|
("/"). The special method name "default" may match a portion of the
|
||||||
substring of the path_info matches some other object).
|
path_info (but only when no longer substring of the path_info
|
||||||
|
matches some other object).
|
||||||
|
|
||||||
This is the default, built-in dispatcher for CherryPy.
|
This is the default, built-in dispatcher for CherryPy.
|
||||||
"""
|
"""
|
||||||
@@ -306,9 +305,9 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
The second object returned will be a list of names which are
|
The second object returned will be a list of names which are
|
||||||
'virtual path' components: parts of the URL which are dynamic,
|
'virtual path' components: parts of the URL which are dynamic,
|
||||||
and were not used when looking up the handler.
|
and were not used when looking up the handler. These virtual
|
||||||
These virtual path components are passed to the handler as
|
path components are passed to the handler as positional
|
||||||
positional arguments.
|
arguments.
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
app = request.app
|
app = request.app
|
||||||
@@ -448,13 +447,11 @@ class Dispatcher(object):
|
|||||||
|
|
||||||
|
|
||||||
class MethodDispatcher(Dispatcher):
|
class MethodDispatcher(Dispatcher):
|
||||||
|
|
||||||
"""Additional dispatch based on cherrypy.request.method.upper().
|
"""Additional dispatch based on cherrypy.request.method.upper().
|
||||||
|
|
||||||
Methods named GET, POST, etc will be called on an exposed class.
|
Methods named GET, POST, etc will be called on an exposed class. The
|
||||||
The method names must be all caps; the appropriate Allow header
|
method names must be all caps; the appropriate Allow header will be
|
||||||
will be output showing all capitalized method names as allowable
|
output showing all capitalized method names as allowable HTTP verbs.
|
||||||
HTTP verbs.
|
|
||||||
|
|
||||||
Note that the containing class must be exposed, not the methods.
|
Note that the containing class must be exposed, not the methods.
|
||||||
"""
|
"""
|
||||||
@@ -492,16 +489,14 @@ class MethodDispatcher(Dispatcher):
|
|||||||
|
|
||||||
|
|
||||||
class RoutesDispatcher(object):
|
class RoutesDispatcher(object):
|
||||||
|
|
||||||
"""A Routes based dispatcher for CherryPy."""
|
"""A Routes based dispatcher for CherryPy."""
|
||||||
|
|
||||||
def __init__(self, full_result=False, **mapper_options):
|
def __init__(self, full_result=False, **mapper_options):
|
||||||
"""
|
"""Routes dispatcher.
|
||||||
Routes dispatcher
|
|
||||||
|
|
||||||
Set full_result to True if you wish the controller
|
Set full_result to True if you wish the controller and the
|
||||||
and the action to be passed on to the page handler
|
action to be passed on to the page handler parameters. By
|
||||||
parameters. By default they won't be.
|
default they won't be.
|
||||||
"""
|
"""
|
||||||
import routes
|
import routes
|
||||||
self.full_result = full_result
|
self.full_result = full_result
|
||||||
@@ -617,8 +612,7 @@ def XMLRPCDispatcher(next_dispatcher=Dispatcher()):
|
|||||||
|
|
||||||
def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
|
def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True,
|
||||||
**domains):
|
**domains):
|
||||||
"""
|
"""Select a different handler based on the Host header.
|
||||||
Select a different handler based on the Host header.
|
|
||||||
|
|
||||||
This can be useful when running multiple sites within one CP server.
|
This can be useful when running multiple sites within one CP server.
|
||||||
It allows several domains to point to different parts of a single
|
It allows several domains to point to different parts of a single
|
||||||
|
|||||||
+21
-23
@@ -136,19 +136,17 @@ from cherrypy.lib import httputil as _httputil
|
|||||||
|
|
||||||
|
|
||||||
class CherryPyException(Exception):
|
class CherryPyException(Exception):
|
||||||
|
|
||||||
"""A base class for CherryPy exceptions."""
|
"""A base class for CherryPy exceptions."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InternalRedirect(CherryPyException):
|
class InternalRedirect(CherryPyException):
|
||||||
|
|
||||||
"""Exception raised to switch to the handler for a different URL.
|
"""Exception raised to switch to the handler for a different URL.
|
||||||
|
|
||||||
This exception will redirect processing to another path within the site
|
This exception will redirect processing to another path within the
|
||||||
(without informing the client). Provide the new path as an argument when
|
site (without informing the client). Provide the new path as an
|
||||||
raising the exception. Provide any params in the querystring for the new
|
argument when raising the exception. Provide any params in the
|
||||||
URL.
|
querystring for the new URL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path, query_string=''):
|
def __init__(self, path, query_string=''):
|
||||||
@@ -173,7 +171,6 @@ class InternalRedirect(CherryPyException):
|
|||||||
|
|
||||||
|
|
||||||
class HTTPRedirect(CherryPyException):
|
class HTTPRedirect(CherryPyException):
|
||||||
|
|
||||||
"""Exception raised when the request should be redirected.
|
"""Exception raised when the request should be redirected.
|
||||||
|
|
||||||
This exception will force a HTTP redirect to the URL or URL's you give it.
|
This exception will force a HTTP redirect to the URL or URL's you give it.
|
||||||
@@ -202,7 +199,7 @@ class HTTPRedirect(CherryPyException):
|
|||||||
"""The list of URL's to emit."""
|
"""The list of URL's to emit."""
|
||||||
|
|
||||||
encoding = 'utf-8'
|
encoding = 'utf-8'
|
||||||
"""The encoding when passed urls are not native strings"""
|
"""The encoding when passed urls are not native strings."""
|
||||||
|
|
||||||
def __init__(self, urls, status=None, encoding=None):
|
def __init__(self, urls, status=None, encoding=None):
|
||||||
self.urls = abs_urls = [
|
self.urls = abs_urls = [
|
||||||
@@ -230,8 +227,7 @@ class HTTPRedirect(CherryPyException):
|
|||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def default_status(cls):
|
def default_status(cls):
|
||||||
"""
|
"""The default redirect status for the request.
|
||||||
The default redirect status for the request.
|
|
||||||
|
|
||||||
RFC 2616 indicates a 301 response code fits our goal; however,
|
RFC 2616 indicates a 301 response code fits our goal; however,
|
||||||
browser support for 301 is quite messy. Use 302/303 instead. See
|
browser support for 301 is quite messy. Use 302/303 instead. See
|
||||||
@@ -249,8 +245,9 @@ class HTTPRedirect(CherryPyException):
|
|||||||
"""Modify cherrypy.response status, headers, and body to represent
|
"""Modify cherrypy.response status, headers, and body to represent
|
||||||
self.
|
self.
|
||||||
|
|
||||||
CherryPy uses this internally, but you can also use it to create an
|
CherryPy uses this internally, but you can also use it to create
|
||||||
HTTPRedirect object and set its output without *raising* the exception.
|
an HTTPRedirect object and set its output without *raising* the
|
||||||
|
exception.
|
||||||
"""
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
response.status = status = self.status
|
response.status = status = self.status
|
||||||
@@ -339,7 +336,6 @@ def clean_headers(status):
|
|||||||
|
|
||||||
|
|
||||||
class HTTPError(CherryPyException):
|
class HTTPError(CherryPyException):
|
||||||
|
|
||||||
"""Exception used to return an HTTP error code (4xx-5xx) to the client.
|
"""Exception used to return an HTTP error code (4xx-5xx) to the client.
|
||||||
|
|
||||||
This exception can be used to automatically send a response using a
|
This exception can be used to automatically send a response using a
|
||||||
@@ -358,7 +354,9 @@ class HTTPError(CherryPyException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
status = None
|
status = None
|
||||||
"""The HTTP status code. May be of type int or str (with a Reason-Phrase).
|
"""The HTTP status code.
|
||||||
|
|
||||||
|
May be of type int or str (with a Reason-Phrase).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
code = None
|
code = None
|
||||||
@@ -386,8 +384,9 @@ class HTTPError(CherryPyException):
|
|||||||
"""Modify cherrypy.response status, headers, and body to represent
|
"""Modify cherrypy.response status, headers, and body to represent
|
||||||
self.
|
self.
|
||||||
|
|
||||||
CherryPy uses this internally, but you can also use it to create an
|
CherryPy uses this internally, but you can also use it to create
|
||||||
HTTPError object and set its output without *raising* the exception.
|
an HTTPError object and set its output without *raising* the
|
||||||
|
exception.
|
||||||
"""
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
@@ -426,11 +425,10 @@ class HTTPError(CherryPyException):
|
|||||||
|
|
||||||
|
|
||||||
class NotFound(HTTPError):
|
class NotFound(HTTPError):
|
||||||
|
|
||||||
"""Exception raised when a URL could not be mapped to any handler (404).
|
"""Exception raised when a URL could not be mapped to any handler (404).
|
||||||
|
|
||||||
This is equivalent to raising
|
This is equivalent to raising :class:`HTTPError("404 Not Found")
|
||||||
:class:`HTTPError("404 Not Found") <cherrypy._cperror.HTTPError>`.
|
<cherrypy._cperror.HTTPError>`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path=None):
|
def __init__(self, path=None):
|
||||||
@@ -477,8 +475,8 @@ _HTTPErrorTemplate = '''<!DOCTYPE html PUBLIC
|
|||||||
def get_error_page(status, **kwargs):
|
def get_error_page(status, **kwargs):
|
||||||
"""Return an HTML page, containing a pretty error response.
|
"""Return an HTML page, containing a pretty error response.
|
||||||
|
|
||||||
status should be an int or a str.
|
status should be an int or a str. kwargs will be interpolated into
|
||||||
kwargs will be interpolated into the page template.
|
the page template.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
code, reason, message = _httputil.valid_status(status)
|
code, reason, message = _httputil.valid_status(status)
|
||||||
@@ -595,8 +593,8 @@ def bare_error(extrabody=None):
|
|||||||
"""Produce status, headers, body for a critical error.
|
"""Produce status, headers, body for a critical error.
|
||||||
|
|
||||||
Returns a triple without calling any other questionable functions,
|
Returns a triple without calling any other questionable functions,
|
||||||
so it should be as error-free as possible. Call it from an HTTP server
|
so it should be as error-free as possible. Call it from an HTTP
|
||||||
if you get errors outside of the request.
|
server if you get errors outside of the request.
|
||||||
|
|
||||||
If extrabody is None, a friendly but rather unhelpful error message
|
If extrabody is None, a friendly but rather unhelpful error message
|
||||||
is set in the body. If extrabody is a string, it will be appended
|
is set in the body. If extrabody is a string, it will be appended
|
||||||
|
|||||||
+11
-10
@@ -123,7 +123,6 @@ logfmt = logging.Formatter('%(message)s')
|
|||||||
|
|
||||||
|
|
||||||
class NullHandler(logging.Handler):
|
class NullHandler(logging.Handler):
|
||||||
|
|
||||||
"""A no-op logging handler to silence the logging.lastResort handler."""
|
"""A no-op logging handler to silence the logging.lastResort handler."""
|
||||||
|
|
||||||
def handle(self, record):
|
def handle(self, record):
|
||||||
@@ -137,15 +136,16 @@ class NullHandler(logging.Handler):
|
|||||||
|
|
||||||
|
|
||||||
class LogManager(object):
|
class LogManager(object):
|
||||||
|
|
||||||
"""An object to assist both simple and advanced logging.
|
"""An object to assist both simple and advanced logging.
|
||||||
|
|
||||||
``cherrypy.log`` is an instance of this class.
|
``cherrypy.log`` is an instance of this class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
appid = None
|
appid = None
|
||||||
"""The id() of the Application object which owns this log manager. If this
|
"""The id() of the Application object which owns this log manager.
|
||||||
is a global log manager, appid is None."""
|
|
||||||
|
If this is a global log manager, appid is None.
|
||||||
|
"""
|
||||||
|
|
||||||
error_log = None
|
error_log = None
|
||||||
"""The actual :class:`logging.Logger` instance for error messages."""
|
"""The actual :class:`logging.Logger` instance for error messages."""
|
||||||
@@ -317,8 +317,8 @@ class LogManager(object):
|
|||||||
def screen(self):
|
def screen(self):
|
||||||
"""Turn stderr/stdout logging on or off.
|
"""Turn stderr/stdout logging on or off.
|
||||||
|
|
||||||
If you set this to True, it'll add the appropriate StreamHandler for
|
If you set this to True, it'll add the appropriate StreamHandler
|
||||||
you. If you set it to False, it will remove the handler.
|
for you. If you set it to False, it will remove the handler.
|
||||||
"""
|
"""
|
||||||
h = self._get_builtin_handler
|
h = self._get_builtin_handler
|
||||||
has_h = h(self.error_log, 'screen') or h(self.access_log, 'screen')
|
has_h = h(self.error_log, 'screen') or h(self.access_log, 'screen')
|
||||||
@@ -414,7 +414,6 @@ class LogManager(object):
|
|||||||
|
|
||||||
|
|
||||||
class WSGIErrorHandler(logging.Handler):
|
class WSGIErrorHandler(logging.Handler):
|
||||||
|
|
||||||
"A handler class which writes logging records to environ['wsgi.errors']."
|
"A handler class which writes logging records to environ['wsgi.errors']."
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
@@ -452,6 +451,8 @@ class WSGIErrorHandler(logging.Handler):
|
|||||||
|
|
||||||
class LazyRfc3339UtcTime(object):
|
class LazyRfc3339UtcTime(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return now() in RFC3339 UTC Format."""
|
"""Return datetime in RFC3339 UTC Format."""
|
||||||
now = datetime.datetime.now()
|
iso_formatted_now = datetime.datetime.now(
|
||||||
return now.isoformat('T') + 'Z'
|
datetime.timezone.utc,
|
||||||
|
).isoformat('T')
|
||||||
|
return f'{iso_formatted_now!s}Z'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Native adapter for serving CherryPy via mod_python
|
"""Native adapter for serving CherryPy via mod_python.
|
||||||
|
|
||||||
Basic usage:
|
Basic usage:
|
||||||
|
|
||||||
|
|||||||
@@ -120,10 +120,10 @@ class NativeGateway(cheroot.server.Gateway):
|
|||||||
class CPHTTPServer(cheroot.server.HTTPServer):
|
class CPHTTPServer(cheroot.server.HTTPServer):
|
||||||
"""Wrapper for cheroot.server.HTTPServer.
|
"""Wrapper for cheroot.server.HTTPServer.
|
||||||
|
|
||||||
cheroot has been designed to not reference CherryPy in any way,
|
cheroot has been designed to not reference CherryPy in any way, so
|
||||||
so that it can be used in other frameworks and applications.
|
that it can be used in other frameworks and applications. Therefore,
|
||||||
Therefore, we wrap it here, so we can apply some attributes
|
we wrap it here, so we can apply some attributes from config ->
|
||||||
from config -> cherrypy.server -> HTTPServer.
|
cherrypy.server -> HTTPServer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server_adapter=cherrypy.server):
|
def __init__(self, server_adapter=cherrypy.server):
|
||||||
|
|||||||
+24
-21
@@ -248,7 +248,10 @@ def process_multipart_form_data(entity):
|
|||||||
|
|
||||||
|
|
||||||
def _old_process_multipart(entity):
|
def _old_process_multipart(entity):
|
||||||
"""The behavior of 3.2 and lower. Deprecated and will be changed in 3.3."""
|
"""The behavior of 3.2 and lower.
|
||||||
|
|
||||||
|
Deprecated and will be changed in 3.3.
|
||||||
|
"""
|
||||||
process_multipart(entity)
|
process_multipart(entity)
|
||||||
|
|
||||||
params = entity.params
|
params = entity.params
|
||||||
@@ -277,7 +280,6 @@ def _old_process_multipart(entity):
|
|||||||
|
|
||||||
# -------------------------------- Entities --------------------------------- #
|
# -------------------------------- Entities --------------------------------- #
|
||||||
class Entity(object):
|
class Entity(object):
|
||||||
|
|
||||||
"""An HTTP request body, or MIME multipart body.
|
"""An HTTP request body, or MIME multipart body.
|
||||||
|
|
||||||
This class collects information about the HTTP request entity. When a
|
This class collects information about the HTTP request entity. When a
|
||||||
@@ -346,13 +348,15 @@ class Entity(object):
|
|||||||
content_type = None
|
content_type = None
|
||||||
"""The value of the Content-Type request header.
|
"""The value of the Content-Type request header.
|
||||||
|
|
||||||
If the Entity is part of a multipart payload, this will be the Content-Type
|
If the Entity is part of a multipart payload, this will be the
|
||||||
given in the MIME headers for this part.
|
Content-Type given in the MIME headers for this part.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_content_type = 'application/x-www-form-urlencoded'
|
default_content_type = 'application/x-www-form-urlencoded'
|
||||||
"""This defines a default ``Content-Type`` to use if no Content-Type header
|
"""This defines a default ``Content-Type`` to use if no Content-Type header
|
||||||
is given. The empty string is used for RequestBody, which results in the
|
is given.
|
||||||
|
|
||||||
|
The empty string is used for RequestBody, which results in the
|
||||||
request body not being read or parsed at all. This is by design; a missing
|
request body not being read or parsed at all. This is by design; a missing
|
||||||
``Content-Type`` header in the HTTP request entity is an error at best,
|
``Content-Type`` header in the HTTP request entity is an error at best,
|
||||||
and a security hole at worst. For multipart parts, however, the MIME spec
|
and a security hole at worst. For multipart parts, however, the MIME spec
|
||||||
@@ -402,8 +406,8 @@ class Entity(object):
|
|||||||
part_class = None
|
part_class = None
|
||||||
"""The class used for multipart parts.
|
"""The class used for multipart parts.
|
||||||
|
|
||||||
You can replace this with custom subclasses to alter the processing of
|
You can replace this with custom subclasses to alter the processing
|
||||||
multipart parts.
|
of multipart parts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fp, headers, params=None, parts=None):
|
def __init__(self, fp, headers, params=None, parts=None):
|
||||||
@@ -509,7 +513,8 @@ class Entity(object):
|
|||||||
"""Return a file-like object into which the request body will be read.
|
"""Return a file-like object into which the request body will be read.
|
||||||
|
|
||||||
By default, this will return a TemporaryFile. Override as needed.
|
By default, this will return a TemporaryFile. Override as needed.
|
||||||
See also :attr:`cherrypy._cpreqbody.Part.maxrambytes`."""
|
See also :attr:`cherrypy._cpreqbody.Part.maxrambytes`.
|
||||||
|
"""
|
||||||
return tempfile.TemporaryFile()
|
return tempfile.TemporaryFile()
|
||||||
|
|
||||||
def fullvalue(self):
|
def fullvalue(self):
|
||||||
@@ -525,7 +530,7 @@ class Entity(object):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def decode_entity(self, value):
|
def decode_entity(self, value):
|
||||||
"""Return a given byte encoded value as a string"""
|
"""Return a given byte encoded value as a string."""
|
||||||
for charset in self.attempt_charsets:
|
for charset in self.attempt_charsets:
|
||||||
try:
|
try:
|
||||||
value = value.decode(charset)
|
value = value.decode(charset)
|
||||||
@@ -569,7 +574,6 @@ class Entity(object):
|
|||||||
|
|
||||||
|
|
||||||
class Part(Entity):
|
class Part(Entity):
|
||||||
|
|
||||||
"""A MIME part entity, part of a multipart entity."""
|
"""A MIME part entity, part of a multipart entity."""
|
||||||
|
|
||||||
# "The default character set, which must be assumed in the absence of a
|
# "The default character set, which must be assumed in the absence of a
|
||||||
@@ -653,8 +657,8 @@ class Part(Entity):
|
|||||||
def read_lines_to_boundary(self, fp_out=None):
|
def read_lines_to_boundary(self, fp_out=None):
|
||||||
"""Read bytes from self.fp and return or write them to a file.
|
"""Read bytes from self.fp and return or write them to a file.
|
||||||
|
|
||||||
If the 'fp_out' argument is None (the default), all bytes read are
|
If the 'fp_out' argument is None (the default), all bytes read
|
||||||
returned in a single byte string.
|
are returned in a single byte string.
|
||||||
|
|
||||||
If the 'fp_out' argument is not None, it must be a file-like
|
If the 'fp_out' argument is not None, it must be a file-like
|
||||||
object that supports the 'write' method; all bytes read will be
|
object that supports the 'write' method; all bytes read will be
|
||||||
@@ -755,15 +759,15 @@ class SizedReader:
|
|||||||
def read(self, size=None, fp_out=None):
|
def read(self, size=None, fp_out=None):
|
||||||
"""Read bytes from the request body and return or write them to a file.
|
"""Read bytes from the request body and return or write them to a file.
|
||||||
|
|
||||||
A number of bytes less than or equal to the 'size' argument are read
|
A number of bytes less than or equal to the 'size' argument are
|
||||||
off the socket. The actual number of bytes read are tracked in
|
read off the socket. The actual number of bytes read are tracked
|
||||||
self.bytes_read. The number may be smaller than 'size' when 1) the
|
in self.bytes_read. The number may be smaller than 'size' when
|
||||||
client sends fewer bytes, 2) the 'Content-Length' request header
|
1) the client sends fewer bytes, 2) the 'Content-Length' request
|
||||||
specifies fewer bytes than requested, or 3) the number of bytes read
|
header specifies fewer bytes than requested, or 3) the number of
|
||||||
exceeds self.maxbytes (in which case, 413 is raised).
|
bytes read exceeds self.maxbytes (in which case, 413 is raised).
|
||||||
|
|
||||||
If the 'fp_out' argument is None (the default), all bytes read are
|
If the 'fp_out' argument is None (the default), all bytes read
|
||||||
returned in a single byte string.
|
are returned in a single byte string.
|
||||||
|
|
||||||
If the 'fp_out' argument is not None, it must be a file-like
|
If the 'fp_out' argument is not None, it must be a file-like
|
||||||
object that supports the 'write' method; all bytes read will be
|
object that supports the 'write' method; all bytes read will be
|
||||||
@@ -918,7 +922,6 @@ class SizedReader:
|
|||||||
|
|
||||||
|
|
||||||
class RequestBody(Entity):
|
class RequestBody(Entity):
|
||||||
|
|
||||||
"""The entity of the HTTP request."""
|
"""The entity of the HTTP request."""
|
||||||
|
|
||||||
bufsize = 8 * 1024
|
bufsize = 8 * 1024
|
||||||
|
|||||||
+118
-83
@@ -16,7 +16,6 @@ from cherrypy.lib import httputil, reprconf, encoding
|
|||||||
|
|
||||||
|
|
||||||
class Hook(object):
|
class Hook(object):
|
||||||
|
|
||||||
"""A callback and its metadata: failsafe, priority, and kwargs."""
|
"""A callback and its metadata: failsafe, priority, and kwargs."""
|
||||||
|
|
||||||
callback = None
|
callback = None
|
||||||
@@ -30,10 +29,12 @@ class Hook(object):
|
|||||||
from the same call point raise exceptions."""
|
from the same call point raise exceptions."""
|
||||||
|
|
||||||
priority = 50
|
priority = 50
|
||||||
|
"""Defines the order of execution for a list of Hooks.
|
||||||
|
|
||||||
|
Priority numbers should be limited to the closed interval [0, 100],
|
||||||
|
but values outside this range are acceptable, as are fractional
|
||||||
|
values.
|
||||||
"""
|
"""
|
||||||
Defines the order of execution for a list of Hooks. Priority numbers
|
|
||||||
should be limited to the closed interval [0, 100], but values outside
|
|
||||||
this range are acceptable, as are fractional values."""
|
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
"""
|
"""
|
||||||
@@ -74,7 +75,6 @@ class Hook(object):
|
|||||||
|
|
||||||
|
|
||||||
class HookMap(dict):
|
class HookMap(dict):
|
||||||
|
|
||||||
"""A map of call points to lists of callbacks (Hook objects)."""
|
"""A map of call points to lists of callbacks (Hook objects)."""
|
||||||
|
|
||||||
def __new__(cls, points=None):
|
def __new__(cls, points=None):
|
||||||
@@ -190,23 +190,23 @@ hookpoints = ['on_start_resource', 'before_request_body',
|
|||||||
|
|
||||||
|
|
||||||
class Request(object):
|
class Request(object):
|
||||||
|
|
||||||
"""An HTTP request.
|
"""An HTTP request.
|
||||||
|
|
||||||
This object represents the metadata of an HTTP request message;
|
This object represents the metadata of an HTTP request message; that
|
||||||
that is, it contains attributes which describe the environment
|
is, it contains attributes which describe the environment in which
|
||||||
in which the request URL, headers, and body were sent (if you
|
the request URL, headers, and body were sent (if you want tools to
|
||||||
want tools to interpret the headers and body, those are elsewhere,
|
interpret the headers and body, those are elsewhere, mostly in
|
||||||
mostly in Tools). This 'metadata' consists of socket data,
|
Tools). This 'metadata' consists of socket data, transport
|
||||||
transport characteristics, and the Request-Line. This object
|
characteristics, and the Request-Line. This object also contains
|
||||||
also contains data regarding the configuration in effect for
|
data regarding the configuration in effect for the given URL, and
|
||||||
the given URL, and the execution plan for generating a response.
|
the execution plan for generating a response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prev = None
|
prev = None
|
||||||
|
"""The previous Request object (if any).
|
||||||
|
|
||||||
|
This should be None unless we are processing an InternalRedirect.
|
||||||
"""
|
"""
|
||||||
The previous Request object (if any). This should be None
|
|
||||||
unless we are processing an InternalRedirect."""
|
|
||||||
|
|
||||||
# Conversation/connection attributes
|
# Conversation/connection attributes
|
||||||
local = httputil.Host('127.0.0.1', 80)
|
local = httputil.Host('127.0.0.1', 80)
|
||||||
@@ -216,9 +216,10 @@ class Request(object):
|
|||||||
'An httputil.Host(ip, port, hostname) object for the client socket.'
|
'An httputil.Host(ip, port, hostname) object for the client socket.'
|
||||||
|
|
||||||
scheme = 'http'
|
scheme = 'http'
|
||||||
|
"""The protocol used between client and server.
|
||||||
|
|
||||||
|
In most cases, this will be either 'http' or 'https'.
|
||||||
"""
|
"""
|
||||||
The protocol used between client and server. In most cases,
|
|
||||||
this will be either 'http' or 'https'."""
|
|
||||||
|
|
||||||
server_protocol = 'HTTP/1.1'
|
server_protocol = 'HTTP/1.1'
|
||||||
"""
|
"""
|
||||||
@@ -227,25 +228,30 @@ class Request(object):
|
|||||||
|
|
||||||
base = ''
|
base = ''
|
||||||
"""The (scheme://host) portion of the requested URL.
|
"""The (scheme://host) portion of the requested URL.
|
||||||
|
|
||||||
In some cases (e.g. when proxying via mod_rewrite), this may contain
|
In some cases (e.g. when proxying via mod_rewrite), this may contain
|
||||||
path segments which cherrypy.url uses when constructing url's, but
|
path segments which cherrypy.url uses when constructing url's, but
|
||||||
which otherwise are ignored by CherryPy. Regardless, this value
|
which otherwise are ignored by CherryPy. Regardless, this value MUST
|
||||||
MUST NOT end in a slash."""
|
NOT end in a slash.
|
||||||
|
"""
|
||||||
|
|
||||||
# Request-Line attributes
|
# Request-Line attributes
|
||||||
request_line = ''
|
request_line = ''
|
||||||
|
"""The complete Request-Line received from the client.
|
||||||
|
|
||||||
|
This is a single string consisting of the request method, URI, and
|
||||||
|
protocol version (joined by spaces). Any final CRLF is removed.
|
||||||
"""
|
"""
|
||||||
The complete Request-Line received from the client. This is a
|
|
||||||
single string consisting of the request method, URI, and protocol
|
|
||||||
version (joined by spaces). Any final CRLF is removed."""
|
|
||||||
|
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
"""Indicates the HTTP method to be performed on the resource identified by
|
||||||
|
the Request-URI.
|
||||||
|
|
||||||
|
Common methods include GET, HEAD, POST, PUT, and DELETE. CherryPy
|
||||||
|
allows any extension method; however, various HTTP servers and
|
||||||
|
gateways may restrict the set of allowable methods. CherryPy
|
||||||
|
applications SHOULD restrict the set (on a per-URI basis).
|
||||||
"""
|
"""
|
||||||
Indicates the HTTP method to be performed on the resource identified
|
|
||||||
by the Request-URI. Common methods include GET, HEAD, POST, PUT, and
|
|
||||||
DELETE. CherryPy allows any extension method; however, various HTTP
|
|
||||||
servers and gateways may restrict the set of allowable methods.
|
|
||||||
CherryPy applications SHOULD restrict the set (on a per-URI basis)."""
|
|
||||||
|
|
||||||
query_string = ''
|
query_string = ''
|
||||||
"""
|
"""
|
||||||
@@ -277,22 +283,26 @@ class Request(object):
|
|||||||
A dict which combines query string (GET) and request entity (POST)
|
A dict which combines query string (GET) and request entity (POST)
|
||||||
variables. This is populated in two stages: GET params are added
|
variables. This is populated in two stages: GET params are added
|
||||||
before the 'on_start_resource' hook, and POST params are added
|
before the 'on_start_resource' hook, and POST params are added
|
||||||
between the 'before_request_body' and 'before_handler' hooks."""
|
between the 'before_request_body' and 'before_handler' hooks.
|
||||||
|
"""
|
||||||
|
|
||||||
# Message attributes
|
# Message attributes
|
||||||
header_list = []
|
header_list = []
|
||||||
|
"""A list of the HTTP request headers as (name, value) tuples.
|
||||||
|
|
||||||
|
In general, you should use request.headers (a dict) instead.
|
||||||
"""
|
"""
|
||||||
A list of the HTTP request headers as (name, value) tuples.
|
|
||||||
In general, you should use request.headers (a dict) instead."""
|
|
||||||
|
|
||||||
headers = httputil.HeaderMap()
|
headers = httputil.HeaderMap()
|
||||||
"""
|
"""A dict-like object containing the request headers.
|
||||||
A dict-like object containing the request headers. Keys are header
|
|
||||||
|
Keys are header
|
||||||
names (in Title-Case format); however, you may get and set them in
|
names (in Title-Case format); however, you may get and set them in
|
||||||
a case-insensitive manner. That is, headers['Content-Type'] and
|
a case-insensitive manner. That is, headers['Content-Type'] and
|
||||||
headers['content-type'] refer to the same value. Values are header
|
headers['content-type'] refer to the same value. Values are header
|
||||||
values (decoded according to :rfc:`2047` if necessary). See also:
|
values (decoded according to :rfc:`2047` if necessary). See also:
|
||||||
httputil.HeaderMap, httputil.HeaderElement."""
|
httputil.HeaderMap, httputil.HeaderElement.
|
||||||
|
"""
|
||||||
|
|
||||||
cookie = SimpleCookie()
|
cookie = SimpleCookie()
|
||||||
"""See help(Cookie)."""
|
"""See help(Cookie)."""
|
||||||
@@ -336,7 +346,8 @@ class Request(object):
|
|||||||
or multipart, this will be None. Otherwise, this will be an instance
|
or multipart, this will be None. Otherwise, this will be an instance
|
||||||
of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
|
of :class:`RequestBody<cherrypy._cpreqbody.RequestBody>` (which you
|
||||||
can .read()); this value is set between the 'before_request_body' and
|
can .read()); this value is set between the 'before_request_body' and
|
||||||
'before_handler' hooks (assuming that process_request_body is True)."""
|
'before_handler' hooks (assuming that process_request_body is True).
|
||||||
|
"""
|
||||||
|
|
||||||
# Dispatch attributes
|
# Dispatch attributes
|
||||||
dispatch = cherrypy.dispatch.Dispatcher()
|
dispatch = cherrypy.dispatch.Dispatcher()
|
||||||
@@ -347,23 +358,24 @@ class Request(object):
|
|||||||
calls the dispatcher as early as possible, passing it a 'path_info'
|
calls the dispatcher as early as possible, passing it a 'path_info'
|
||||||
argument.
|
argument.
|
||||||
|
|
||||||
The default dispatcher discovers the page handler by matching path_info
|
The default dispatcher discovers the page handler by matching
|
||||||
to a hierarchical arrangement of objects, starting at request.app.root.
|
path_info to a hierarchical arrangement of objects, starting at
|
||||||
See help(cherrypy.dispatch) for more information."""
|
request.app.root. See help(cherrypy.dispatch) for more information.
|
||||||
|
"""
|
||||||
|
|
||||||
script_name = ''
|
script_name = ''
|
||||||
"""
|
"""The 'mount point' of the application which is handling this request.
|
||||||
The 'mount point' of the application which is handling this request.
|
|
||||||
|
|
||||||
This attribute MUST NOT end in a slash. If the script_name refers to
|
This attribute MUST NOT end in a slash. If the script_name refers to
|
||||||
the root of the URI, it MUST be an empty string (not "/").
|
the root of the URI, it MUST be an empty string (not "/").
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path_info = '/'
|
path_info = '/'
|
||||||
|
"""The 'relative path' portion of the Request-URI.
|
||||||
|
|
||||||
|
This is relative to the script_name ('mount point') of the
|
||||||
|
application which is handling this request.
|
||||||
"""
|
"""
|
||||||
The 'relative path' portion of the Request-URI. This is relative
|
|
||||||
to the script_name ('mount point') of the application which is
|
|
||||||
handling this request."""
|
|
||||||
|
|
||||||
login = None
|
login = None
|
||||||
"""
|
"""
|
||||||
@@ -391,14 +403,16 @@ class Request(object):
|
|||||||
of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
|
of the form: {Toolbox.namespace: {Tool.name: config dict}}."""
|
||||||
|
|
||||||
config = None
|
config = None
|
||||||
|
"""A flat dict of all configuration entries which apply to the current
|
||||||
|
request.
|
||||||
|
|
||||||
|
These entries are collected from global config, application config
|
||||||
|
(based on request.path_info), and from handler config (exactly how
|
||||||
|
is governed by the request.dispatch object in effect for this
|
||||||
|
request; by default, handler config can be attached anywhere in the
|
||||||
|
tree between request.app.root and the final handler, and inherits
|
||||||
|
downward).
|
||||||
"""
|
"""
|
||||||
A flat dict of all configuration entries which apply to the
|
|
||||||
current request. These entries are collected from global config,
|
|
||||||
application config (based on request.path_info), and from handler
|
|
||||||
config (exactly how is governed by the request.dispatch object in
|
|
||||||
effect for this request; by default, handler config can be attached
|
|
||||||
anywhere in the tree between request.app.root and the final handler,
|
|
||||||
and inherits downward)."""
|
|
||||||
|
|
||||||
is_index = None
|
is_index = None
|
||||||
"""
|
"""
|
||||||
@@ -409,13 +423,14 @@ class Request(object):
|
|||||||
the trailing slash. See cherrypy.tools.trailing_slash."""
|
the trailing slash. See cherrypy.tools.trailing_slash."""
|
||||||
|
|
||||||
hooks = HookMap(hookpoints)
|
hooks = HookMap(hookpoints)
|
||||||
"""
|
"""A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
|
||||||
A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}.
|
|
||||||
Each key is a str naming the hook point, and each value is a list
|
Each key is a str naming the hook point, and each value is a list
|
||||||
of hooks which will be called at that hook point during this request.
|
of hooks which will be called at that hook point during this request.
|
||||||
The list of hooks is generally populated as early as possible (mostly
|
The list of hooks is generally populated as early as possible (mostly
|
||||||
from Tools specified in config), but may be extended at any time.
|
from Tools specified in config), but may be extended at any time.
|
||||||
See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools."""
|
See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools.
|
||||||
|
"""
|
||||||
|
|
||||||
error_response = cherrypy.HTTPError(500).set_response
|
error_response = cherrypy.HTTPError(500).set_response
|
||||||
"""
|
"""
|
||||||
@@ -428,12 +443,11 @@ class Request(object):
|
|||||||
error response to the user-agent."""
|
error response to the user-agent."""
|
||||||
|
|
||||||
error_page = {}
|
error_page = {}
|
||||||
"""
|
"""A dict of {error code: response filename or callable} pairs.
|
||||||
A dict of {error code: response filename or callable} pairs.
|
|
||||||
|
|
||||||
The error code must be an int representing a given HTTP error code,
|
The error code must be an int representing a given HTTP error code,
|
||||||
or the string 'default', which will be used if no matching entry
|
or the string 'default', which will be used if no matching entry is
|
||||||
is found for a given numeric code.
|
found for a given numeric code.
|
||||||
|
|
||||||
If a filename is provided, the file should contain a Python string-
|
If a filename is provided, the file should contain a Python string-
|
||||||
formatting template, and can expect by default to receive format
|
formatting template, and can expect by default to receive format
|
||||||
@@ -447,8 +461,8 @@ class Request(object):
|
|||||||
iterable of strings which will be set to response.body. It may also
|
iterable of strings which will be set to response.body. It may also
|
||||||
override headers or perform any other processing.
|
override headers or perform any other processing.
|
||||||
|
|
||||||
If no entry is given for an error code, and no 'default' entry exists,
|
If no entry is given for an error code, and no 'default' entry
|
||||||
a default template will be used.
|
exists, a default template will be used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
show_tracebacks = True
|
show_tracebacks = True
|
||||||
@@ -473,9 +487,10 @@ class Request(object):
|
|||||||
"""True once the close method has been called, False otherwise."""
|
"""True once the close method has been called, False otherwise."""
|
||||||
|
|
||||||
stage = None
|
stage = None
|
||||||
|
"""A string containing the stage reached in the request-handling process.
|
||||||
|
|
||||||
|
This is useful when debugging a live server with hung requests.
|
||||||
"""
|
"""
|
||||||
A string containing the stage reached in the request-handling process.
|
|
||||||
This is useful when debugging a live server with hung requests."""
|
|
||||||
|
|
||||||
unique_id = None
|
unique_id = None
|
||||||
"""A lazy object generating and memorizing UUID4 on ``str()`` render."""
|
"""A lazy object generating and memorizing UUID4 on ``str()`` render."""
|
||||||
@@ -492,9 +507,10 @@ class Request(object):
|
|||||||
server_protocol='HTTP/1.1'):
|
server_protocol='HTTP/1.1'):
|
||||||
"""Populate a new Request object.
|
"""Populate a new Request object.
|
||||||
|
|
||||||
local_host should be an httputil.Host object with the server info.
|
local_host should be an httputil.Host object with the server
|
||||||
remote_host should be an httputil.Host object with the client info.
|
info. remote_host should be an httputil.Host object with the
|
||||||
scheme should be a string, either "http" or "https".
|
client info. scheme should be a string, either "http" or
|
||||||
|
"https".
|
||||||
"""
|
"""
|
||||||
self.local = local_host
|
self.local = local_host
|
||||||
self.remote = remote_host
|
self.remote = remote_host
|
||||||
@@ -514,7 +530,10 @@ class Request(object):
|
|||||||
self.unique_id = LazyUUID4()
|
self.unique_id = LazyUUID4()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Run cleanup code. (Core)"""
|
"""Run cleanup code.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
if not self.closed:
|
if not self.closed:
|
||||||
self.closed = True
|
self.closed = True
|
||||||
self.stage = 'on_end_request'
|
self.stage = 'on_end_request'
|
||||||
@@ -551,7 +570,6 @@ class Request(object):
|
|||||||
|
|
||||||
Consumer code (HTTP servers) should then access these response
|
Consumer code (HTTP servers) should then access these response
|
||||||
attributes to build the outbound stream.
|
attributes to build the outbound stream.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
self.stage = 'run'
|
self.stage = 'run'
|
||||||
@@ -631,7 +649,10 @@ class Request(object):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
def respond(self, path_info):
|
def respond(self, path_info):
|
||||||
"""Generate a response for the resource at self.path_info. (Core)"""
|
"""Generate a response for the resource at self.path_info.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
@@ -702,7 +723,10 @@ class Request(object):
|
|||||||
response.finalize()
|
response.finalize()
|
||||||
|
|
||||||
def process_query_string(self):
|
def process_query_string(self):
|
||||||
"""Parse the query string into Python structures. (Core)"""
|
"""Parse the query string into Python structures.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
p = httputil.parse_query_string(
|
p = httputil.parse_query_string(
|
||||||
self.query_string, encoding=self.query_string_encoding)
|
self.query_string, encoding=self.query_string_encoding)
|
||||||
@@ -715,7 +739,10 @@ class Request(object):
|
|||||||
self.params.update(p)
|
self.params.update(p)
|
||||||
|
|
||||||
def process_headers(self):
|
def process_headers(self):
|
||||||
"""Parse HTTP header data into Python structures. (Core)"""
|
"""Parse HTTP header data into Python structures.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
# Process the headers into self.headers
|
# Process the headers into self.headers
|
||||||
headers = self.headers
|
headers = self.headers
|
||||||
for name, value in self.header_list:
|
for name, value in self.header_list:
|
||||||
@@ -751,7 +778,10 @@ class Request(object):
|
|||||||
self.base = '%s://%s' % (self.scheme, host)
|
self.base = '%s://%s' % (self.scheme, host)
|
||||||
|
|
||||||
def get_resource(self, path):
|
def get_resource(self, path):
|
||||||
"""Call a dispatcher (which sets self.handler and .config). (Core)"""
|
"""Call a dispatcher (which sets self.handler and .config).
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
# First, see if there is a custom dispatch at this URI. Custom
|
# First, see if there is a custom dispatch at this URI. Custom
|
||||||
# dispatchers can only be specified in app.config, not in _cp_config
|
# dispatchers can only be specified in app.config, not in _cp_config
|
||||||
# (since custom dispatchers may not even have an app.root).
|
# (since custom dispatchers may not even have an app.root).
|
||||||
@@ -762,7 +792,10 @@ class Request(object):
|
|||||||
dispatch(path)
|
dispatch(path)
|
||||||
|
|
||||||
def handle_error(self):
|
def handle_error(self):
|
||||||
"""Handle the last unanticipated exception. (Core)"""
|
"""Handle the last unanticipated exception.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.hooks.run('before_error_response')
|
self.hooks.run('before_error_response')
|
||||||
if self.error_response:
|
if self.error_response:
|
||||||
@@ -776,7 +809,6 @@ class Request(object):
|
|||||||
|
|
||||||
|
|
||||||
class ResponseBody(object):
|
class ResponseBody(object):
|
||||||
|
|
||||||
"""The body of the HTTP response (the response entity)."""
|
"""The body of the HTTP response (the response entity)."""
|
||||||
|
|
||||||
unicode_err = ('Page handlers MUST return bytes. Use tools.encode '
|
unicode_err = ('Page handlers MUST return bytes. Use tools.encode '
|
||||||
@@ -802,18 +834,18 @@ class ResponseBody(object):
|
|||||||
|
|
||||||
|
|
||||||
class Response(object):
|
class Response(object):
|
||||||
|
|
||||||
"""An HTTP Response, including status, headers, and body."""
|
"""An HTTP Response, including status, headers, and body."""
|
||||||
|
|
||||||
status = ''
|
status = ''
|
||||||
"""The HTTP Status-Code and Reason-Phrase."""
|
"""The HTTP Status-Code and Reason-Phrase."""
|
||||||
|
|
||||||
header_list = []
|
header_list = []
|
||||||
"""
|
"""A list of the HTTP response headers as (name, value) tuples.
|
||||||
A list of the HTTP response headers as (name, value) tuples.
|
|
||||||
In general, you should use response.headers (a dict) instead. This
|
In general, you should use response.headers (a dict) instead. This
|
||||||
attribute is generated from response.headers and is not valid until
|
attribute is generated from response.headers and is not valid until
|
||||||
after the finalize phase."""
|
after the finalize phase.
|
||||||
|
"""
|
||||||
|
|
||||||
headers = httputil.HeaderMap()
|
headers = httputil.HeaderMap()
|
||||||
"""
|
"""
|
||||||
@@ -833,7 +865,10 @@ class Response(object):
|
|||||||
"""The body (entity) of the HTTP response."""
|
"""The body (entity) of the HTTP response."""
|
||||||
|
|
||||||
time = None
|
time = None
|
||||||
"""The value of time.time() when created. Use in HTTP dates."""
|
"""The value of time.time() when created.
|
||||||
|
|
||||||
|
Use in HTTP dates.
|
||||||
|
"""
|
||||||
|
|
||||||
stream = False
|
stream = False
|
||||||
"""If False, buffer the response body."""
|
"""If False, buffer the response body."""
|
||||||
@@ -861,15 +896,15 @@ class Response(object):
|
|||||||
return new_body
|
return new_body
|
||||||
|
|
||||||
def _flush_body(self):
|
def _flush_body(self):
|
||||||
"""
|
"""Discard self.body but consume any generator such that any
|
||||||
Discard self.body but consume any generator such that
|
finalization can occur, such as is required by caching.tee_output()."""
|
||||||
any finalization can occur, such as is required by
|
|
||||||
caching.tee_output().
|
|
||||||
"""
|
|
||||||
consume(iter(self.body))
|
consume(iter(self.body))
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
"""Transform headers (and cookies) into self.header_list. (Core)"""
|
"""Transform headers (and cookies) into self.header_list.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
code, reason, _ = httputil.valid_status(self.status)
|
code, reason, _ = httputil.valid_status(self.status)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|||||||
+24
-12
@@ -50,7 +50,8 @@ class Server(ServerAdapter):
|
|||||||
"""If given, the name of the UNIX socket to use instead of TCP/IP.
|
"""If given, the name of the UNIX socket to use instead of TCP/IP.
|
||||||
|
|
||||||
When this option is not None, the `socket_host` and `socket_port` options
|
When this option is not None, the `socket_host` and `socket_port` options
|
||||||
are ignored."""
|
are ignored.
|
||||||
|
"""
|
||||||
|
|
||||||
socket_queue_size = 5
|
socket_queue_size = 5
|
||||||
"""The 'backlog' argument to socket.listen(); specifies the maximum number
|
"""The 'backlog' argument to socket.listen(); specifies the maximum number
|
||||||
@@ -79,17 +80,24 @@ class Server(ServerAdapter):
|
|||||||
"""The number of worker threads to start up in the pool."""
|
"""The number of worker threads to start up in the pool."""
|
||||||
|
|
||||||
thread_pool_max = -1
|
thread_pool_max = -1
|
||||||
"""The maximum size of the worker-thread pool. Use -1 to indicate no limit.
|
"""The maximum size of the worker-thread pool.
|
||||||
|
|
||||||
|
Use -1 to indicate no limit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
max_request_header_size = 500 * 1024
|
max_request_header_size = 500 * 1024
|
||||||
"""The maximum number of bytes allowable in the request headers.
|
"""The maximum number of bytes allowable in the request headers.
|
||||||
If exceeded, the HTTP server should return "413 Request Entity Too Large".
|
|
||||||
|
If exceeded, the HTTP server should return "413 Request Entity Too
|
||||||
|
Large".
|
||||||
"""
|
"""
|
||||||
|
|
||||||
max_request_body_size = 100 * 1024 * 1024
|
max_request_body_size = 100 * 1024 * 1024
|
||||||
"""The maximum number of bytes allowable in the request body. If exceeded,
|
"""The maximum number of bytes allowable in the request body.
|
||||||
the HTTP server should return "413 Request Entity Too Large"."""
|
|
||||||
|
If exceeded, the HTTP server should return "413 Request Entity Too
|
||||||
|
Large".
|
||||||
|
"""
|
||||||
|
|
||||||
instance = None
|
instance = None
|
||||||
"""If not None, this should be an HTTP server instance (such as
|
"""If not None, this should be an HTTP server instance (such as
|
||||||
@@ -119,7 +127,8 @@ class Server(ServerAdapter):
|
|||||||
the builtin WSGI server. Builtin options are: 'builtin' (to
|
the builtin WSGI server. Builtin options are: 'builtin' (to
|
||||||
use the SSL library built into recent versions of Python).
|
use the SSL library built into recent versions of Python).
|
||||||
You may also register your own classes in the
|
You may also register your own classes in the
|
||||||
cheroot.server.ssl_adapters dict."""
|
cheroot.server.ssl_adapters dict.
|
||||||
|
"""
|
||||||
|
|
||||||
statistics = False
|
statistics = False
|
||||||
"""Turns statistics-gathering on or off for aware HTTP servers."""
|
"""Turns statistics-gathering on or off for aware HTTP servers."""
|
||||||
@@ -129,11 +138,13 @@ class Server(ServerAdapter):
|
|||||||
|
|
||||||
wsgi_version = (1, 0)
|
wsgi_version = (1, 0)
|
||||||
"""The WSGI version tuple to use with the builtin WSGI server.
|
"""The WSGI version tuple to use with the builtin WSGI server.
|
||||||
The provided options are (1, 0) [which includes support for PEP 3333,
|
|
||||||
which declares it covers WSGI version 1.0.1 but still mandates the
|
The provided options are (1, 0) [which includes support for PEP
|
||||||
wsgi.version (1, 0)] and ('u', 0), an experimental unicode version.
|
3333, which declares it covers WSGI version 1.0.1 but still mandates
|
||||||
You may create and register your own experimental versions of the WSGI
|
the wsgi.version (1, 0)] and ('u', 0), an experimental unicode
|
||||||
protocol by adding custom classes to the cheroot.server.wsgi_gateways dict.
|
version. You may create and register your own experimental versions
|
||||||
|
of the WSGI protocol by adding custom classes to the
|
||||||
|
cheroot.server.wsgi_gateways dict.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
peercreds = False
|
peercreds = False
|
||||||
@@ -184,7 +195,8 @@ class Server(ServerAdapter):
|
|||||||
def bind_addr(self):
|
def bind_addr(self):
|
||||||
"""Return bind address.
|
"""Return bind address.
|
||||||
|
|
||||||
A (host, port) tuple for TCP sockets or a str for Unix domain sockts.
|
A (host, port) tuple for TCP sockets or a str for Unix domain
|
||||||
|
sockets.
|
||||||
"""
|
"""
|
||||||
if self.socket_file:
|
if self.socket_file:
|
||||||
return self.socket_file
|
return self.socket_file
|
||||||
|
|||||||
+21
-26
@@ -1,7 +1,7 @@
|
|||||||
"""CherryPy tools. A "tool" is any helper, adapted to CP.
|
"""CherryPy tools. A "tool" is any helper, adapted to CP.
|
||||||
|
|
||||||
Tools are usually designed to be used in a variety of ways (although some
|
Tools are usually designed to be used in a variety of ways (although
|
||||||
may only offer one if they choose):
|
some may only offer one if they choose):
|
||||||
|
|
||||||
Library calls
|
Library calls
|
||||||
All tools are callables that can be used wherever needed.
|
All tools are callables that can be used wherever needed.
|
||||||
@@ -48,10 +48,10 @@ _attr_error = (
|
|||||||
|
|
||||||
|
|
||||||
class Tool(object):
|
class Tool(object):
|
||||||
|
|
||||||
"""A registered function for use with CherryPy request-processing hooks.
|
"""A registered function for use with CherryPy request-processing hooks.
|
||||||
|
|
||||||
help(tool.callable) should give you more information about this Tool.
|
help(tool.callable) should give you more information about this
|
||||||
|
Tool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
namespace = 'tools'
|
namespace = 'tools'
|
||||||
@@ -135,8 +135,8 @@ class Tool(object):
|
|||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""Hook this tool into cherrypy.request.
|
"""Hook this tool into cherrypy.request.
|
||||||
|
|
||||||
The standard CherryPy request object will automatically call this
|
The standard CherryPy request object will automatically call
|
||||||
method when the tool is "turned on" in config.
|
this method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
p = conf.pop('priority', None)
|
p = conf.pop('priority', None)
|
||||||
@@ -147,15 +147,15 @@ class Tool(object):
|
|||||||
|
|
||||||
|
|
||||||
class HandlerTool(Tool):
|
class HandlerTool(Tool):
|
||||||
|
|
||||||
"""Tool which is called 'before main', that may skip normal handlers.
|
"""Tool which is called 'before main', that may skip normal handlers.
|
||||||
|
|
||||||
If the tool successfully handles the request (by setting response.body),
|
If the tool successfully handles the request (by setting
|
||||||
if should return True. This will cause CherryPy to skip any 'normal' page
|
response.body), if should return True. This will cause CherryPy to
|
||||||
handler. If the tool did not handle the request, it should return False
|
skip any 'normal' page handler. If the tool did not handle the
|
||||||
to tell CherryPy to continue on and call the normal page handler. If the
|
request, it should return False to tell CherryPy to continue on and
|
||||||
tool is declared AS a page handler (see the 'handler' method), returning
|
call the normal page handler. If the tool is declared AS a page
|
||||||
False will raise NotFound.
|
handler (see the 'handler' method), returning False will raise
|
||||||
|
NotFound.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, callable, name=None):
|
def __init__(self, callable, name=None):
|
||||||
@@ -185,8 +185,8 @@ class HandlerTool(Tool):
|
|||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""Hook this tool into cherrypy.request.
|
"""Hook this tool into cherrypy.request.
|
||||||
|
|
||||||
The standard CherryPy request object will automatically call this
|
The standard CherryPy request object will automatically call
|
||||||
method when the tool is "turned on" in config.
|
this method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
conf = self._merged_args()
|
conf = self._merged_args()
|
||||||
p = conf.pop('priority', None)
|
p = conf.pop('priority', None)
|
||||||
@@ -197,7 +197,6 @@ class HandlerTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class HandlerWrapperTool(Tool):
|
class HandlerWrapperTool(Tool):
|
||||||
|
|
||||||
"""Tool which wraps request.handler in a provided wrapper function.
|
"""Tool which wraps request.handler in a provided wrapper function.
|
||||||
|
|
||||||
The 'newhandler' arg must be a handler wrapper function that takes a
|
The 'newhandler' arg must be a handler wrapper function that takes a
|
||||||
@@ -232,7 +231,6 @@ class HandlerWrapperTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class ErrorTool(Tool):
|
class ErrorTool(Tool):
|
||||||
|
|
||||||
"""Tool which is used to replace the default request.error_response."""
|
"""Tool which is used to replace the default request.error_response."""
|
||||||
|
|
||||||
def __init__(self, callable, name=None):
|
def __init__(self, callable, name=None):
|
||||||
@@ -244,8 +242,8 @@ class ErrorTool(Tool):
|
|||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""Hook this tool into cherrypy.request.
|
"""Hook this tool into cherrypy.request.
|
||||||
|
|
||||||
The standard CherryPy request object will automatically call this
|
The standard CherryPy request object will automatically call
|
||||||
method when the tool is "turned on" in config.
|
this method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
cherrypy.serving.request.error_response = self._wrapper
|
cherrypy.serving.request.error_response = self._wrapper
|
||||||
|
|
||||||
@@ -254,7 +252,6 @@ class ErrorTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class SessionTool(Tool):
|
class SessionTool(Tool):
|
||||||
|
|
||||||
"""Session Tool for CherryPy.
|
"""Session Tool for CherryPy.
|
||||||
|
|
||||||
sessions.locking
|
sessions.locking
|
||||||
@@ -282,8 +279,8 @@ class SessionTool(Tool):
|
|||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""Hook this tool into cherrypy.request.
|
"""Hook this tool into cherrypy.request.
|
||||||
|
|
||||||
The standard CherryPy request object will automatically call this
|
The standard CherryPy request object will automatically call
|
||||||
method when the tool is "turned on" in config.
|
this method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
hooks = cherrypy.serving.request.hooks
|
hooks = cherrypy.serving.request.hooks
|
||||||
|
|
||||||
@@ -325,7 +322,6 @@ class SessionTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class XMLRPCController(object):
|
class XMLRPCController(object):
|
||||||
|
|
||||||
"""A Controller (page handler collection) for XML-RPC.
|
"""A Controller (page handler collection) for XML-RPC.
|
||||||
|
|
||||||
To use it, have your controllers subclass this base class (it will
|
To use it, have your controllers subclass this base class (it will
|
||||||
@@ -392,7 +388,6 @@ class SessionAuthTool(HandlerTool):
|
|||||||
|
|
||||||
|
|
||||||
class CachingTool(Tool):
|
class CachingTool(Tool):
|
||||||
|
|
||||||
"""Caching Tool for CherryPy."""
|
"""Caching Tool for CherryPy."""
|
||||||
|
|
||||||
def _wrapper(self, **kwargs):
|
def _wrapper(self, **kwargs):
|
||||||
@@ -416,11 +411,11 @@ class CachingTool(Tool):
|
|||||||
|
|
||||||
|
|
||||||
class Toolbox(object):
|
class Toolbox(object):
|
||||||
|
|
||||||
"""A collection of Tools.
|
"""A collection of Tools.
|
||||||
|
|
||||||
This object also functions as a config namespace handler for itself.
|
This object also functions as a config namespace handler for itself.
|
||||||
Custom toolboxes should be added to each Application's toolboxes dict.
|
Custom toolboxes should be added to each Application's toolboxes
|
||||||
|
dict.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, namespace):
|
def __init__(self, namespace):
|
||||||
|
|||||||
+31
-19
@@ -10,19 +10,22 @@ from cherrypy.lib import httputil, reprconf
|
|||||||
class Application(object):
|
class Application(object):
|
||||||
"""A CherryPy Application.
|
"""A CherryPy Application.
|
||||||
|
|
||||||
Servers and gateways should not instantiate Request objects directly.
|
Servers and gateways should not instantiate Request objects
|
||||||
Instead, they should ask an Application object for a request object.
|
directly. Instead, they should ask an Application object for a
|
||||||
|
request object.
|
||||||
|
|
||||||
An instance of this class may also be used as a WSGI callable
|
An instance of this class may also be used as a WSGI callable (WSGI
|
||||||
(WSGI application object) for itself.
|
application object) for itself.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
root = None
|
root = None
|
||||||
"""The top-most container of page handlers for this app. Handlers should
|
"""The top-most container of page handlers for this app.
|
||||||
be arranged in a hierarchy of attributes, matching the expected URI
|
|
||||||
hierarchy; the default dispatcher then searches this hierarchy for a
|
Handlers should be arranged in a hierarchy of attributes, matching
|
||||||
matching handler. When using a dispatcher other than the default,
|
the expected URI hierarchy; the default dispatcher then searches
|
||||||
this value may be None."""
|
this hierarchy for a matching handler. When using a dispatcher other
|
||||||
|
than the default, this value may be None.
|
||||||
|
"""
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
"""A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
|
"""A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict
|
||||||
@@ -32,10 +35,16 @@ class Application(object):
|
|||||||
toolboxes = {'tools': cherrypy.tools}
|
toolboxes = {'tools': cherrypy.tools}
|
||||||
|
|
||||||
log = None
|
log = None
|
||||||
"""A LogManager instance. See _cplogging."""
|
"""A LogManager instance.
|
||||||
|
|
||||||
|
See _cplogging.
|
||||||
|
"""
|
||||||
|
|
||||||
wsgiapp = None
|
wsgiapp = None
|
||||||
"""A CPWSGIApp instance. See _cpwsgi."""
|
"""A CPWSGIApp instance.
|
||||||
|
|
||||||
|
See _cpwsgi.
|
||||||
|
"""
|
||||||
|
|
||||||
request_class = _cprequest.Request
|
request_class = _cprequest.Request
|
||||||
response_class = _cprequest.Response
|
response_class = _cprequest.Response
|
||||||
@@ -82,12 +91,15 @@ class Application(object):
|
|||||||
def script_name(self): # noqa: D401; irrelevant for properties
|
def script_name(self): # noqa: D401; irrelevant for properties
|
||||||
"""The URI "mount point" for this app.
|
"""The URI "mount point" for this app.
|
||||||
|
|
||||||
A mount point is that portion of the URI which is constant for all URIs
|
A mount point is that portion of the URI which is constant for
|
||||||
that are serviced by this application; it does not include scheme,
|
all URIs that are serviced by this application; it does not
|
||||||
host, or proxy ("virtual host") portions of the URI.
|
include scheme, host, or proxy ("virtual host") portions of the
|
||||||
|
URI.
|
||||||
|
|
||||||
For example, if script_name is "/my/cool/app", then the URL
|
For example, if script_name is "/my/cool/app", then the URL "
|
||||||
"http://www.example.com/my/cool/app/page1" might be handled by a
|
|
||||||
|
http://www.example.com/my/cool/app/page1"
|
||||||
|
might be handled by a
|
||||||
"page1" method on the root object.
|
"page1" method on the root object.
|
||||||
|
|
||||||
The value of script_name MUST NOT end in a slash. If the script_name
|
The value of script_name MUST NOT end in a slash. If the script_name
|
||||||
@@ -171,9 +183,9 @@ class Application(object):
|
|||||||
class Tree(object):
|
class Tree(object):
|
||||||
"""A registry of CherryPy applications, mounted at diverse points.
|
"""A registry of CherryPy applications, mounted at diverse points.
|
||||||
|
|
||||||
An instance of this class may also be used as a WSGI callable
|
An instance of this class may also be used as a WSGI callable (WSGI
|
||||||
(WSGI application object), in which case it dispatches to all
|
application object), in which case it dispatches to all mounted
|
||||||
mounted apps.
|
apps.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
apps = {}
|
apps = {}
|
||||||
|
|||||||
+34
-26
@@ -1,10 +1,10 @@
|
|||||||
"""WSGI interface (see PEP 333 and 3333).
|
"""WSGI interface (see PEP 333 and 3333).
|
||||||
|
|
||||||
Note that WSGI environ keys and values are 'native strings'; that is,
|
Note that WSGI environ keys and values are 'native strings'; that is,
|
||||||
whatever the type of "" is. For Python 2, that's a byte string; for Python 3,
|
whatever the type of "" is. For Python 2, that's a byte string; for
|
||||||
it's a unicode string. But PEP 3333 says: "even if Python's str type is
|
Python 3, it's a unicode string. But PEP 3333 says: "even if Python's
|
||||||
actually Unicode "under the hood", the content of native strings must
|
str type is actually Unicode "under the hood", the content of native
|
||||||
still be translatable to bytes via the Latin-1 encoding!"
|
strings must still be translatable to bytes via the Latin-1 encoding!"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys as _sys
|
import sys as _sys
|
||||||
@@ -34,7 +34,6 @@ def downgrade_wsgi_ux_to_1x(environ):
|
|||||||
|
|
||||||
|
|
||||||
class VirtualHost(object):
|
class VirtualHost(object):
|
||||||
|
|
||||||
"""Select a different WSGI application based on the Host header.
|
"""Select a different WSGI application based on the Host header.
|
||||||
|
|
||||||
This can be useful when running multiple sites within one CP server.
|
This can be useful when running multiple sites within one CP server.
|
||||||
@@ -56,7 +55,10 @@ class VirtualHost(object):
|
|||||||
cherrypy.tree.graft(vhost)
|
cherrypy.tree.graft(vhost)
|
||||||
"""
|
"""
|
||||||
default = None
|
default = None
|
||||||
"""Required. The default WSGI application."""
|
"""Required.
|
||||||
|
|
||||||
|
The default WSGI application.
|
||||||
|
"""
|
||||||
|
|
||||||
use_x_forwarded_host = True
|
use_x_forwarded_host = True
|
||||||
"""If True (the default), any "X-Forwarded-Host"
|
"""If True (the default), any "X-Forwarded-Host"
|
||||||
@@ -65,11 +67,12 @@ class VirtualHost(object):
|
|||||||
|
|
||||||
domains = {}
|
domains = {}
|
||||||
"""A dict of {host header value: application} pairs.
|
"""A dict of {host header value: application} pairs.
|
||||||
The incoming "Host" request header is looked up in this dict,
|
|
||||||
and, if a match is found, the corresponding WSGI application
|
The incoming "Host" request header is looked up in this dict, and,
|
||||||
will be called instead of the default. Note that you often need
|
if a match is found, the corresponding WSGI application will be
|
||||||
separate entries for "example.com" and "www.example.com".
|
called instead of the default. Note that you often need separate
|
||||||
In addition, "Host" headers may contain the port number.
|
entries for "example.com" and "www.example.com". In addition, "Host"
|
||||||
|
headers may contain the port number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
def __init__(self, default, domains=None, use_x_forwarded_host=True):
|
||||||
@@ -89,7 +92,6 @@ class VirtualHost(object):
|
|||||||
|
|
||||||
|
|
||||||
class InternalRedirector(object):
|
class InternalRedirector(object):
|
||||||
|
|
||||||
"""WSGI middleware that handles raised cherrypy.InternalRedirect."""
|
"""WSGI middleware that handles raised cherrypy.InternalRedirect."""
|
||||||
|
|
||||||
def __init__(self, nextapp, recursive=False):
|
def __init__(self, nextapp, recursive=False):
|
||||||
@@ -137,7 +139,6 @@ class InternalRedirector(object):
|
|||||||
|
|
||||||
|
|
||||||
class ExceptionTrapper(object):
|
class ExceptionTrapper(object):
|
||||||
|
|
||||||
"""WSGI middleware that traps exceptions."""
|
"""WSGI middleware that traps exceptions."""
|
||||||
|
|
||||||
def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
|
def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)):
|
||||||
@@ -226,7 +227,6 @@ class _TrappedResponse(object):
|
|||||||
|
|
||||||
|
|
||||||
class AppResponse(object):
|
class AppResponse(object):
|
||||||
|
|
||||||
"""WSGI response iterable for CherryPy applications."""
|
"""WSGI response iterable for CherryPy applications."""
|
||||||
|
|
||||||
def __init__(self, environ, start_response, cpapp):
|
def __init__(self, environ, start_response, cpapp):
|
||||||
@@ -277,7 +277,10 @@ class AppResponse(object):
|
|||||||
return next(self.iter_response)
|
return next(self.iter_response)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Close and de-reference the current request and response. (Core)"""
|
"""Close and de-reference the current request and response.
|
||||||
|
|
||||||
|
(Core)
|
||||||
|
"""
|
||||||
streaming = _cherrypy.serving.response.stream
|
streaming = _cherrypy.serving.response.stream
|
||||||
self.cpapp.release_serving()
|
self.cpapp.release_serving()
|
||||||
|
|
||||||
@@ -380,18 +383,20 @@ class AppResponse(object):
|
|||||||
|
|
||||||
|
|
||||||
class CPWSGIApp(object):
|
class CPWSGIApp(object):
|
||||||
|
|
||||||
"""A WSGI application object for a CherryPy Application."""
|
"""A WSGI application object for a CherryPy Application."""
|
||||||
|
|
||||||
pipeline = [
|
pipeline = [
|
||||||
('ExceptionTrapper', ExceptionTrapper),
|
('ExceptionTrapper', ExceptionTrapper),
|
||||||
('InternalRedirector', InternalRedirector),
|
('InternalRedirector', InternalRedirector),
|
||||||
]
|
]
|
||||||
"""A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
|
"""A list of (name, wsgiapp) pairs.
|
||||||
constructor that takes an initial, positional 'nextapp' argument,
|
|
||||||
plus optional keyword arguments, and returns a WSGI application
|
Each 'wsgiapp' MUST be a constructor that takes an initial,
|
||||||
(that takes environ and start_response arguments). The 'name' can
|
positional 'nextapp' argument, plus optional keyword arguments, and
|
||||||
be any you choose, and will correspond to keys in self.config."""
|
returns a WSGI application (that takes environ and start_response
|
||||||
|
arguments). The 'name' can be any you choose, and will correspond to
|
||||||
|
keys in self.config.
|
||||||
|
"""
|
||||||
|
|
||||||
head = None
|
head = None
|
||||||
"""Rather than nest all apps in the pipeline on each call, it's only
|
"""Rather than nest all apps in the pipeline on each call, it's only
|
||||||
@@ -399,9 +404,12 @@ class CPWSGIApp(object):
|
|||||||
this to None again if you change self.pipeline after calling self."""
|
this to None again if you change self.pipeline after calling self."""
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
"""A dict whose keys match names listed in the pipeline. Each
|
"""A dict whose keys match names listed in the pipeline.
|
||||||
value is a further dict which will be passed to the corresponding
|
|
||||||
named WSGI callable (from the pipeline) as keyword arguments."""
|
Each value is a further dict which will be passed to the
|
||||||
|
corresponding named WSGI callable (from the pipeline) as keyword
|
||||||
|
arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
response_class = AppResponse
|
response_class = AppResponse
|
||||||
"""The class to instantiate and return as the next app in the WSGI chain.
|
"""The class to instantiate and return as the next app in the WSGI chain.
|
||||||
@@ -417,8 +425,8 @@ class CPWSGIApp(object):
|
|||||||
def tail(self, environ, start_response):
|
def tail(self, environ, start_response):
|
||||||
"""WSGI application callable for the actual CherryPy application.
|
"""WSGI application callable for the actual CherryPy application.
|
||||||
|
|
||||||
You probably shouldn't call this; call self.__call__ instead,
|
You probably shouldn't call this; call self.__call__ instead, so
|
||||||
so that any WSGI middleware in self.pipeline can run first.
|
that any WSGI middleware in self.pipeline can run first.
|
||||||
"""
|
"""
|
||||||
return self.response_class(environ, start_response, self.cpapp)
|
return self.response_class(environ, start_response, self.cpapp)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""
|
"""WSGI server interface (see PEP 333).
|
||||||
WSGI server interface (see PEP 333).
|
|
||||||
|
|
||||||
This adds some CP-specific bits to the framework-agnostic cheroot package.
|
This adds some CP-specific bits to the framework-agnostic cheroot
|
||||||
|
package.
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -35,10 +35,11 @@ class CPWSGIHTTPRequest(cheroot.server.HTTPRequest):
|
|||||||
class CPWSGIServer(cheroot.wsgi.Server):
|
class CPWSGIServer(cheroot.wsgi.Server):
|
||||||
"""Wrapper for cheroot.wsgi.Server.
|
"""Wrapper for cheroot.wsgi.Server.
|
||||||
|
|
||||||
cheroot has been designed to not reference CherryPy in any way,
|
cheroot has been designed to not reference CherryPy in any way, so
|
||||||
so that it can be used in other frameworks and applications. Therefore,
|
that it can be used in other frameworks and applications. Therefore,
|
||||||
we wrap it here, so we can set our own mount points from cherrypy.tree
|
we wrap it here, so we can set our own mount points from
|
||||||
and apply some attributes from config -> cherrypy.server -> wsgi.Server.
|
cherrypy.tree and apply some attributes from config ->
|
||||||
|
cherrypy.server -> wsgi.Server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fmt = 'CherryPy/{cherrypy.__version__} {cheroot.wsgi.Server.version}'
|
fmt = 'CherryPy/{cherrypy.__version__} {cheroot.wsgi.Server.version}'
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ def popargs(*args, **kwargs):
|
|||||||
class Root:
|
class Root:
|
||||||
def index(self):
|
def index(self):
|
||||||
#...
|
#...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Since keyword arg comes after *args, we have to process it ourselves
|
# Since keyword arg comes after *args, we have to process it ourselves
|
||||||
# for lower versions of python.
|
# for lower versions of python.
|
||||||
@@ -201,16 +200,17 @@ def url(path='', qs='', script_name=None, base=None, relative=None):
|
|||||||
If it does not start with a slash, this returns
|
If it does not start with a slash, this returns
|
||||||
(base + script_name [+ request.path_info] + path + qs).
|
(base + script_name [+ request.path_info] + path + qs).
|
||||||
|
|
||||||
If script_name is None, cherrypy.request will be used
|
If script_name is None, cherrypy.request will be used to find a
|
||||||
to find a script_name, if available.
|
script_name, if available.
|
||||||
|
|
||||||
If base is None, cherrypy.request.base will be used (if available).
|
If base is None, cherrypy.request.base will be used (if available).
|
||||||
Note that you can use cherrypy.tools.proxy to change this.
|
Note that you can use cherrypy.tools.proxy to change this.
|
||||||
|
|
||||||
Finally, note that this function can be used to obtain an absolute URL
|
Finally, note that this function can be used to obtain an absolute
|
||||||
for the current request path (minus the querystring) by passing no args.
|
URL for the current request path (minus the querystring) by passing
|
||||||
If you call url(qs=cherrypy.request.query_string), you should get the
|
no args. If you call url(qs=cherrypy.request.query_string), you
|
||||||
original browser URL (assuming no internal redirections).
|
should get the original browser URL (assuming no internal
|
||||||
|
redirections).
|
||||||
|
|
||||||
If relative is None or not provided, request.app.relative_urls will
|
If relative is None or not provided, request.app.relative_urls will
|
||||||
be used (if available, else False). If False, the output will be an
|
be used (if available, else False). If False, the output will be an
|
||||||
@@ -320,8 +320,8 @@ def normalize_path(path):
|
|||||||
class _ClassPropertyDescriptor(object):
|
class _ClassPropertyDescriptor(object):
|
||||||
"""Descript for read-only class-based property.
|
"""Descript for read-only class-based property.
|
||||||
|
|
||||||
Turns a classmethod-decorated func into a read-only property of that class
|
Turns a classmethod-decorated func into a read-only property of that
|
||||||
type (means the value cannot be set).
|
class type (means the value cannot be set).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fget, fset=None):
|
def __init__(self, fget, fset=None):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""
|
"""JSON support.
|
||||||
JSON support.
|
|
||||||
|
|
||||||
Expose preferred json module as json and provide encode/decode
|
Expose preferred json module as json and provide encode/decode
|
||||||
convenience functions.
|
convenience functions.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ def is_iterator(obj):
|
|||||||
|
|
||||||
(i.e. like a generator).
|
(i.e. like a generator).
|
||||||
|
|
||||||
This will return False for objects which are iterable,
|
This will return False for objects which are iterable, but not
|
||||||
but not iterators themselves.
|
iterators themselves.
|
||||||
"""
|
"""
|
||||||
from types import GeneratorType
|
from types import GeneratorType
|
||||||
if isinstance(obj, GeneratorType):
|
if isinstance(obj, GeneratorType):
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ as the credentials store::
|
|||||||
'tools.auth_basic.accept_charset': 'UTF-8',
|
'tools.auth_basic.accept_charset': 'UTF-8',
|
||||||
}
|
}
|
||||||
app_config = { '/' : basic_auth }
|
app_config = { '/' : basic_auth }
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ def TRACE(msg):
|
|||||||
|
|
||||||
|
|
||||||
def get_ha1_dict_plain(user_password_dict):
|
def get_ha1_dict_plain(user_password_dict):
|
||||||
"""Returns a get_ha1 function which obtains a plaintext password from a
|
"""Return a get_ha1 function which obtains a plaintext password from a
|
||||||
dictionary of the form: {username : password}.
|
dictionary of the form: {username : password}.
|
||||||
|
|
||||||
If you want a simple dictionary-based authentication scheme, with plaintext
|
If you want a simple dictionary-based authentication scheme, with plaintext
|
||||||
@@ -72,7 +72,7 @@ def get_ha1_dict_plain(user_password_dict):
|
|||||||
|
|
||||||
|
|
||||||
def get_ha1_dict(user_ha1_dict):
|
def get_ha1_dict(user_ha1_dict):
|
||||||
"""Returns a get_ha1 function which obtains a HA1 password hash from a
|
"""Return a get_ha1 function which obtains a HA1 password hash from a
|
||||||
dictionary of the form: {username : HA1}.
|
dictionary of the form: {username : HA1}.
|
||||||
|
|
||||||
If you want a dictionary-based authentication scheme, but with
|
If you want a dictionary-based authentication scheme, but with
|
||||||
@@ -87,7 +87,7 @@ def get_ha1_dict(user_ha1_dict):
|
|||||||
|
|
||||||
|
|
||||||
def get_ha1_file_htdigest(filename):
|
def get_ha1_file_htdigest(filename):
|
||||||
"""Returns a get_ha1 function which obtains a HA1 password hash from a
|
"""Return a get_ha1 function which obtains a HA1 password hash from a
|
||||||
flat file with lines of the same format as that produced by the Apache
|
flat file with lines of the same format as that produced by the Apache
|
||||||
htdigest utility. For example, for realm 'wonderland', username 'alice',
|
htdigest utility. For example, for realm 'wonderland', username 'alice',
|
||||||
and password '4x5istwelve', the htdigest line would be::
|
and password '4x5istwelve', the htdigest line would be::
|
||||||
@@ -135,7 +135,7 @@ def synthesize_nonce(s, key, timestamp=None):
|
|||||||
|
|
||||||
|
|
||||||
def H(s):
|
def H(s):
|
||||||
"""The hash function H"""
|
"""The hash function H."""
|
||||||
return md5_hex(s)
|
return md5_hex(s)
|
||||||
|
|
||||||
|
|
||||||
@@ -259,10 +259,11 @@ class HttpDigestAuthorization(object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def is_nonce_stale(self, max_age_seconds=600):
|
def is_nonce_stale(self, max_age_seconds=600):
|
||||||
"""Returns True if a validated nonce is stale. The nonce contains a
|
"""Return True if a validated nonce is stale.
|
||||||
timestamp in plaintext and also a secure hash of the timestamp.
|
|
||||||
You should first validate the nonce to ensure the plaintext
|
The nonce contains a timestamp in plaintext and also a secure
|
||||||
timestamp is not spoofed.
|
hash of the timestamp. You should first validate the nonce to
|
||||||
|
ensure the plaintext timestamp is not spoofed.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
timestamp, hashpart = self.nonce.split(':', 1)
|
timestamp, hashpart = self.nonce.split(':', 1)
|
||||||
@@ -275,7 +276,10 @@ class HttpDigestAuthorization(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def HA2(self, entity_body=''):
|
def HA2(self, entity_body=''):
|
||||||
"""Returns the H(A2) string. See :rfc:`2617` section 3.2.2.3."""
|
"""Return the H(A2) string.
|
||||||
|
|
||||||
|
See :rfc:`2617` section 3.2.2.3.
|
||||||
|
"""
|
||||||
# RFC 2617 3.2.2.3
|
# RFC 2617 3.2.2.3
|
||||||
# If the "qop" directive's value is "auth" or is unspecified,
|
# If the "qop" directive's value is "auth" or is unspecified,
|
||||||
# then A2 is:
|
# then A2 is:
|
||||||
@@ -306,7 +310,6 @@ class HttpDigestAuthorization(object):
|
|||||||
4.3. This refers to the entity the user agent sent in the
|
4.3. This refers to the entity the user agent sent in the
|
||||||
request which has the Authorization header. Typically GET
|
request which has the Authorization header. Typically GET
|
||||||
requests don't have an entity, and POST requests do.
|
requests don't have an entity, and POST requests do.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ha2 = self.HA2(entity_body)
|
ha2 = self.HA2(entity_body)
|
||||||
# Request-Digest -- RFC 2617 3.2.2.1
|
# Request-Digest -- RFC 2617 3.2.2.1
|
||||||
@@ -395,7 +398,6 @@ def digest_auth(realm, get_ha1, key, debug=False, accept_charset='utf-8'):
|
|||||||
key
|
key
|
||||||
A secret string known only to the server, used in the synthesis
|
A secret string known only to the server, used in the synthesis
|
||||||
of nonces.
|
of nonces.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
|
|
||||||
@@ -447,9 +449,7 @@ def digest_auth(realm, get_ha1, key, debug=False, accept_charset='utf-8'):
|
|||||||
|
|
||||||
|
|
||||||
def _respond_401(realm, key, accept_charset, debug, **kwargs):
|
def _respond_401(realm, key, accept_charset, debug, **kwargs):
|
||||||
"""
|
"""Respond with 401 status and a WWW-Authenticate header."""
|
||||||
Respond with 401 status and a WWW-Authenticate header
|
|
||||||
"""
|
|
||||||
header = www_authenticate(
|
header = www_authenticate(
|
||||||
realm, key,
|
realm, key,
|
||||||
accept_charset=accept_charset,
|
accept_charset=accept_charset,
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ from cherrypy.lib import cptools, httputil
|
|||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
|
|
||||||
"""Base class for Cache implementations."""
|
"""Base class for Cache implementations."""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
@@ -64,17 +63,16 @@ class Cache(object):
|
|||||||
|
|
||||||
# ------------------------------ Memory Cache ------------------------------- #
|
# ------------------------------ Memory Cache ------------------------------- #
|
||||||
class AntiStampedeCache(dict):
|
class AntiStampedeCache(dict):
|
||||||
|
|
||||||
"""A storage system for cached items which reduces stampede collisions."""
|
"""A storage system for cached items which reduces stampede collisions."""
|
||||||
|
|
||||||
def wait(self, key, timeout=5, debug=False):
|
def wait(self, key, timeout=5, debug=False):
|
||||||
"""Return the cached value for the given key, or None.
|
"""Return the cached value for the given key, or None.
|
||||||
|
|
||||||
If timeout is not None, and the value is already
|
If timeout is not None, and the value is already being
|
||||||
being calculated by another thread, wait until the given timeout has
|
calculated by another thread, wait until the given timeout has
|
||||||
elapsed. If the value is available before the timeout expires, it is
|
elapsed. If the value is available before the timeout expires,
|
||||||
returned. If not, None is returned, and a sentinel placed in the cache
|
it is returned. If not, None is returned, and a sentinel placed
|
||||||
to signal other threads to wait.
|
in the cache to signal other threads to wait.
|
||||||
|
|
||||||
If timeout is None, no waiting is performed nor sentinels used.
|
If timeout is None, no waiting is performed nor sentinels used.
|
||||||
"""
|
"""
|
||||||
@@ -127,7 +125,6 @@ class AntiStampedeCache(dict):
|
|||||||
|
|
||||||
|
|
||||||
class MemoryCache(Cache):
|
class MemoryCache(Cache):
|
||||||
|
|
||||||
"""An in-memory cache for varying response content.
|
"""An in-memory cache for varying response content.
|
||||||
|
|
||||||
Each key in self.store is a URI, and each value is an AntiStampedeCache.
|
Each key in self.store is a URI, and each value is an AntiStampedeCache.
|
||||||
@@ -381,7 +378,10 @@ def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def tee_output():
|
def tee_output():
|
||||||
"""Tee response output to cache storage. Internal."""
|
"""Tee response output to cache storage.
|
||||||
|
|
||||||
|
Internal.
|
||||||
|
"""
|
||||||
# Used by CachingTool by attaching to request.hooks
|
# Used by CachingTool by attaching to request.hooks
|
||||||
|
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
@@ -441,7 +441,6 @@ def expires(secs=0, force=False, debug=False):
|
|||||||
* Expires
|
* Expires
|
||||||
|
|
||||||
If any are already present, none of the above response headers are set.
|
If any are already present, none of the above response headers are set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ To report statistics::
|
|||||||
To format statistics reports::
|
To format statistics reports::
|
||||||
|
|
||||||
See 'Reporting', above.
|
See 'Reporting', above.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -254,7 +253,6 @@ def proc_time(s):
|
|||||||
|
|
||||||
|
|
||||||
class ByteCountWrapper(object):
|
class ByteCountWrapper(object):
|
||||||
|
|
||||||
"""Wraps a file-like object, counting the number of bytes read."""
|
"""Wraps a file-like object, counting the number of bytes read."""
|
||||||
|
|
||||||
def __init__(self, rfile):
|
def __init__(self, rfile):
|
||||||
@@ -307,7 +305,6 @@ def _get_threading_ident():
|
|||||||
|
|
||||||
|
|
||||||
class StatsTool(cherrypy.Tool):
|
class StatsTool(cherrypy.Tool):
|
||||||
|
|
||||||
"""Record various information about the current request."""
|
"""Record various information about the current request."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -316,8 +313,8 @@ class StatsTool(cherrypy.Tool):
|
|||||||
def _setup(self):
|
def _setup(self):
|
||||||
"""Hook this tool into cherrypy.request.
|
"""Hook this tool into cherrypy.request.
|
||||||
|
|
||||||
The standard CherryPy request object will automatically call this
|
The standard CherryPy request object will automatically call
|
||||||
method when the tool is "turned on" in config.
|
this method when the tool is "turned on" in config.
|
||||||
"""
|
"""
|
||||||
if appstats.get('Enabled', False):
|
if appstats.get('Enabled', False):
|
||||||
cherrypy.Tool._setup(self)
|
cherrypy.Tool._setup(self)
|
||||||
|
|||||||
+41
-32
@@ -94,8 +94,8 @@ def validate_etags(autotags=False, debug=False):
|
|||||||
def validate_since():
|
def validate_since():
|
||||||
"""Validate the current Last-Modified against If-Modified-Since headers.
|
"""Validate the current Last-Modified against If-Modified-Since headers.
|
||||||
|
|
||||||
If no code has set the Last-Modified response header, then no validation
|
If no code has set the Last-Modified response header, then no
|
||||||
will be performed.
|
validation will be performed.
|
||||||
"""
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
lastmod = response.headers.get('Last-Modified')
|
lastmod = response.headers.get('Last-Modified')
|
||||||
@@ -123,9 +123,9 @@ def validate_since():
|
|||||||
def allow(methods=None, debug=False):
|
def allow(methods=None, debug=False):
|
||||||
"""Raise 405 if request.method not in methods (default ['GET', 'HEAD']).
|
"""Raise 405 if request.method not in methods (default ['GET', 'HEAD']).
|
||||||
|
|
||||||
The given methods are case-insensitive, and may be in any order.
|
The given methods are case-insensitive, and may be in any order. If
|
||||||
If only one method is allowed, you may supply a single string;
|
only one method is allowed, you may supply a single string; if more
|
||||||
if more than one, supply a list of strings.
|
than one, supply a list of strings.
|
||||||
|
|
||||||
Regardless of whether the current method is allowed or not, this
|
Regardless of whether the current method is allowed or not, this
|
||||||
also emits an 'Allow' response header, containing the given methods.
|
also emits an 'Allow' response header, containing the given methods.
|
||||||
@@ -154,22 +154,23 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
|||||||
scheme='X-Forwarded-Proto', debug=False):
|
scheme='X-Forwarded-Proto', debug=False):
|
||||||
"""Change the base URL (scheme://host[:port][/path]).
|
"""Change the base URL (scheme://host[:port][/path]).
|
||||||
|
|
||||||
For running a CP server behind Apache, lighttpd, or other HTTP server.
|
For running a CP server behind Apache, lighttpd, or other HTTP
|
||||||
|
server.
|
||||||
|
|
||||||
For Apache and lighttpd, you should leave the 'local' argument at the
|
For Apache and lighttpd, you should leave the 'local' argument at
|
||||||
default value of 'X-Forwarded-Host'. For Squid, you probably want to set
|
the default value of 'X-Forwarded-Host'. For Squid, you probably
|
||||||
tools.proxy.local = 'Origin'.
|
want to set tools.proxy.local = 'Origin'.
|
||||||
|
|
||||||
If you want the new request.base to include path info (not just the host),
|
If you want the new request.base to include path info (not just the
|
||||||
you must explicitly set base to the full base path, and ALSO set 'local'
|
host), you must explicitly set base to the full base path, and ALSO
|
||||||
to '', so that the X-Forwarded-Host request header (which never includes
|
set 'local' to '', so that the X-Forwarded-Host request header
|
||||||
path info) does not override it. Regardless, the value for 'base' MUST
|
(which never includes path info) does not override it. Regardless,
|
||||||
NOT end in a slash.
|
the value for 'base' MUST NOT end in a slash.
|
||||||
|
|
||||||
cherrypy.request.remote.ip (the IP address of the client) will be
|
cherrypy.request.remote.ip (the IP address of the client) will be
|
||||||
rewritten if the header specified by the 'remote' arg is valid.
|
rewritten if the header specified by the 'remote' arg is valid. By
|
||||||
By default, 'remote' is set to 'X-Forwarded-For'. If you do not
|
default, 'remote' is set to 'X-Forwarded-For'. If you do not want to
|
||||||
want to rewrite remote.ip, set the 'remote' arg to an empty string.
|
rewrite remote.ip, set the 'remote' arg to an empty string.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
@@ -217,8 +218,8 @@ def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For',
|
|||||||
def ignore_headers(headers=('Range',), debug=False):
|
def ignore_headers(headers=('Range',), debug=False):
|
||||||
"""Delete request headers whose field names are included in 'headers'.
|
"""Delete request headers whose field names are included in 'headers'.
|
||||||
|
|
||||||
This is a useful tool for working behind certain HTTP servers;
|
This is a useful tool for working behind certain HTTP servers; for
|
||||||
for example, Apache duplicates the work that CP does for 'Range'
|
example, Apache duplicates the work that CP does for 'Range'
|
||||||
headers, and will doubly-truncate the response.
|
headers, and will doubly-truncate the response.
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
@@ -281,7 +282,6 @@ def referer(pattern, accept=True, accept_missing=False, error=403,
|
|||||||
|
|
||||||
|
|
||||||
class SessionAuth(object):
|
class SessionAuth(object):
|
||||||
|
|
||||||
"""Assert that the user is logged in."""
|
"""Assert that the user is logged in."""
|
||||||
|
|
||||||
session_key = 'username'
|
session_key = 'username'
|
||||||
@@ -319,7 +319,10 @@ Message: %(error_msg)s
|
|||||||
</body></html>""") % vars()).encode('utf-8')
|
</body></html>""") % vars()).encode('utf-8')
|
||||||
|
|
||||||
def do_login(self, username, password, from_page='..', **kwargs):
|
def do_login(self, username, password, from_page='..', **kwargs):
|
||||||
"""Login. May raise redirect, or return True if request handled."""
|
"""Login.
|
||||||
|
|
||||||
|
May raise redirect, or return True if request handled.
|
||||||
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
error_msg = self.check_username_and_password(username, password)
|
error_msg = self.check_username_and_password(username, password)
|
||||||
if error_msg:
|
if error_msg:
|
||||||
@@ -336,7 +339,10 @@ Message: %(error_msg)s
|
|||||||
raise cherrypy.HTTPRedirect(from_page or '/')
|
raise cherrypy.HTTPRedirect(from_page or '/')
|
||||||
|
|
||||||
def do_logout(self, from_page='..', **kwargs):
|
def do_logout(self, from_page='..', **kwargs):
|
||||||
"""Logout. May raise redirect, or return True if request handled."""
|
"""Logout.
|
||||||
|
|
||||||
|
May raise redirect, or return True if request handled.
|
||||||
|
"""
|
||||||
sess = cherrypy.session
|
sess = cherrypy.session
|
||||||
username = sess.get(self.session_key)
|
username = sess.get(self.session_key)
|
||||||
sess[self.session_key] = None
|
sess[self.session_key] = None
|
||||||
@@ -346,7 +352,9 @@ Message: %(error_msg)s
|
|||||||
raise cherrypy.HTTPRedirect(from_page)
|
raise cherrypy.HTTPRedirect(from_page)
|
||||||
|
|
||||||
def do_check(self):
|
def do_check(self):
|
||||||
"""Assert username. Raise redirect, or return True if request handled.
|
"""Assert username.
|
||||||
|
|
||||||
|
Raise redirect, or return True if request handled.
|
||||||
"""
|
"""
|
||||||
sess = cherrypy.session
|
sess = cherrypy.session
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
@@ -408,8 +416,7 @@ def session_auth(**kwargs):
|
|||||||
|
|
||||||
Any attribute of the SessionAuth class may be overridden
|
Any attribute of the SessionAuth class may be overridden
|
||||||
via a keyword arg to this function:
|
via a keyword arg to this function:
|
||||||
|
""" + '\n' + '\n '.join(
|
||||||
""" + '\n '.join(
|
|
||||||
'{!s}: {!s}'.format(k, type(getattr(SessionAuth, k)).__name__)
|
'{!s}: {!s}'.format(k, type(getattr(SessionAuth, k)).__name__)
|
||||||
for k in dir(SessionAuth)
|
for k in dir(SessionAuth)
|
||||||
if not k.startswith('__')
|
if not k.startswith('__')
|
||||||
@@ -490,8 +497,8 @@ def trailing_slash(missing=True, extra=False, status=None, debug=False):
|
|||||||
def flatten(debug=False):
|
def flatten(debug=False):
|
||||||
"""Wrap response.body in a generator that recursively iterates over body.
|
"""Wrap response.body in a generator that recursively iterates over body.
|
||||||
|
|
||||||
This allows cherrypy.response.body to consist of 'nested generators';
|
This allows cherrypy.response.body to consist of 'nested
|
||||||
that is, a set of generators that yield generators.
|
generators'; that is, a set of generators that yield generators.
|
||||||
"""
|
"""
|
||||||
def flattener(input):
|
def flattener(input):
|
||||||
numchunks = 0
|
numchunks = 0
|
||||||
@@ -622,13 +629,15 @@ def autovary(ignore=None, debug=False):
|
|||||||
|
|
||||||
|
|
||||||
def convert_params(exception=ValueError, error=400):
|
def convert_params(exception=ValueError, error=400):
|
||||||
"""Convert request params based on function annotations, with error handling.
|
"""Convert request params based on function annotations.
|
||||||
|
|
||||||
exception
|
This function also processes errors that are subclasses of ``exception``.
|
||||||
Exception class to catch.
|
|
||||||
|
|
||||||
status
|
:param BaseException exception: Exception class to catch.
|
||||||
The HTTP error code to return to the client on failure.
|
:type exception: BaseException
|
||||||
|
|
||||||
|
:param error: The HTTP status code to return to the client on failure.
|
||||||
|
:type error: int
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
types = request.handler.callable.__annotations__
|
types = request.handler.callable.__annotations__
|
||||||
|
|||||||
@@ -261,9 +261,7 @@ class ResponseEncoder:
|
|||||||
|
|
||||||
|
|
||||||
def prepare_iter(value):
|
def prepare_iter(value):
|
||||||
"""
|
"""Ensure response body is iterable and resolves to False when empty."""
|
||||||
Ensure response body is iterable and resolves to False when empty.
|
|
||||||
"""
|
|
||||||
if isinstance(value, text_or_bytes):
|
if isinstance(value, text_or_bytes):
|
||||||
# strings get wrapped in a list because iterating over a single
|
# strings get wrapped in a list because iterating over a single
|
||||||
# item list is much faster than iterating over every character
|
# item list is much faster than iterating over every character
|
||||||
@@ -360,7 +358,6 @@ def gzip(compress_level=5, mime_types=['text/html', 'text/plain'],
|
|||||||
* No 'gzip' or 'x-gzip' is present in the Accept-Encoding header
|
* No 'gzip' or 'x-gzip' is present in the Accept-Encoding header
|
||||||
* No 'gzip' or 'x-gzip' with a qvalue > 0 is present
|
* No 'gzip' or 'x-gzip' with a qvalue > 0 is present
|
||||||
* The 'identity' value is given with a qvalue > 0.
|
* The 'identity' value is given with a qvalue > 0.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
request = cherrypy.serving.request
|
request = cherrypy.serving.request
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from cherrypy.process.plugins import SimplePlugin
|
|||||||
|
|
||||||
|
|
||||||
class ReferrerTree(object):
|
class ReferrerTree(object):
|
||||||
|
|
||||||
"""An object which gathers all referrers of an object to a given depth."""
|
"""An object which gathers all referrers of an object to a given depth."""
|
||||||
|
|
||||||
peek_length = 40
|
peek_length = 40
|
||||||
@@ -132,7 +131,6 @@ def get_context(obj):
|
|||||||
|
|
||||||
|
|
||||||
class GCRoot(object):
|
class GCRoot(object):
|
||||||
|
|
||||||
"""A CherryPy page handler for testing reference leaks."""
|
"""A CherryPy page handler for testing reference leaks."""
|
||||||
|
|
||||||
classes = [
|
classes = [
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ def protocol_from_http(protocol_str):
|
|||||||
def get_ranges(headervalue, content_length):
|
def get_ranges(headervalue, content_length):
|
||||||
"""Return a list of (start, stop) indices from a Range header, or None.
|
"""Return a list of (start, stop) indices from a Range header, or None.
|
||||||
|
|
||||||
Each (start, stop) tuple will be composed of two ints, which are suitable
|
Each (start, stop) tuple will be composed of two ints, which are
|
||||||
for use in a slicing operation. That is, the header "Range: bytes=3-6",
|
suitable for use in a slicing operation. That is, the header "Range:
|
||||||
if applied against a Python string, is requesting resource[3:7]. This
|
bytes=3-6", if applied against a Python string, is requesting
|
||||||
function will return the list [(3, 7)].
|
resource[3:7]. This function will return the list [(3, 7)].
|
||||||
|
|
||||||
If this function returns an empty list, you should return HTTP 416.
|
If this function returns an empty list, you should return HTTP 416.
|
||||||
"""
|
"""
|
||||||
@@ -127,7 +127,6 @@ def get_ranges(headervalue, content_length):
|
|||||||
|
|
||||||
|
|
||||||
class HeaderElement(object):
|
class HeaderElement(object):
|
||||||
|
|
||||||
"""An element (with parameters) from an HTTP header's element list."""
|
"""An element (with parameters) from an HTTP header's element list."""
|
||||||
|
|
||||||
def __init__(self, value, params=None):
|
def __init__(self, value, params=None):
|
||||||
@@ -169,14 +168,14 @@ q_separator = re.compile(r'; *q *=')
|
|||||||
|
|
||||||
|
|
||||||
class AcceptElement(HeaderElement):
|
class AcceptElement(HeaderElement):
|
||||||
|
|
||||||
"""An element (with parameters) from an Accept* header's element list.
|
"""An element (with parameters) from an Accept* header's element list.
|
||||||
|
|
||||||
AcceptElement objects are comparable; the more-preferred object will be
|
AcceptElement objects are comparable; the more-preferred object will
|
||||||
"less than" the less-preferred object. They are also therefore sortable;
|
be "less than" the less-preferred object. They are also therefore
|
||||||
if you sort a list of AcceptElement objects, they will be listed in
|
sortable; if you sort a list of AcceptElement objects, they will be
|
||||||
priority order; the most preferred value will be first. Yes, it should
|
listed in priority order; the most preferred value will be first.
|
||||||
have been the other way around, but it's too late to fix now.
|
Yes, it should have been the other way around, but it's too late to
|
||||||
|
fix now.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -249,8 +248,7 @@ def header_elements(fieldname, fieldvalue):
|
|||||||
|
|
||||||
|
|
||||||
def decode_TEXT(value):
|
def decode_TEXT(value):
|
||||||
r"""
|
r"""Decode :rfc:`2047` TEXT.
|
||||||
Decode :rfc:`2047` TEXT
|
|
||||||
|
|
||||||
>>> decode_TEXT("=?utf-8?q?f=C3=BCr?=") == b'f\xfcr'.decode('latin-1')
|
>>> decode_TEXT("=?utf-8?q?f=C3=BCr?=") == b'f\xfcr'.decode('latin-1')
|
||||||
True
|
True
|
||||||
@@ -265,9 +263,7 @@ def decode_TEXT(value):
|
|||||||
|
|
||||||
|
|
||||||
def decode_TEXT_maybe(value):
|
def decode_TEXT_maybe(value):
|
||||||
"""
|
"""Decode the text but only if '=?' appears in it."""
|
||||||
Decode the text but only if '=?' appears in it.
|
|
||||||
"""
|
|
||||||
return decode_TEXT(value) if '=?' in value else value
|
return decode_TEXT(value) if '=?' in value else value
|
||||||
|
|
||||||
|
|
||||||
@@ -388,7 +384,6 @@ def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'):
|
|||||||
|
|
||||||
|
|
||||||
class CaseInsensitiveDict(jaraco.collections.KeyTransformingDict):
|
class CaseInsensitiveDict(jaraco.collections.KeyTransformingDict):
|
||||||
|
|
||||||
"""A case-insensitive dict subclass.
|
"""A case-insensitive dict subclass.
|
||||||
|
|
||||||
Each key is changed on entry to title case.
|
Each key is changed on entry to title case.
|
||||||
@@ -417,7 +412,6 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
class HeaderMap(CaseInsensitiveDict):
|
class HeaderMap(CaseInsensitiveDict):
|
||||||
|
|
||||||
"""A dict subclass for HTTP request and response headers.
|
"""A dict subclass for HTTP request and response headers.
|
||||||
|
|
||||||
Each key is changed on entry to str(key).title(). This allows headers
|
Each key is changed on entry to str(key).title(). This allows headers
|
||||||
@@ -494,7 +488,6 @@ class HeaderMap(CaseInsensitiveDict):
|
|||||||
|
|
||||||
|
|
||||||
class Host(object):
|
class Host(object):
|
||||||
|
|
||||||
"""An internet address.
|
"""An internet address.
|
||||||
|
|
||||||
name
|
name
|
||||||
|
|||||||
@@ -7,22 +7,22 @@ class NeverExpires(object):
|
|||||||
|
|
||||||
|
|
||||||
class Timer(object):
|
class Timer(object):
|
||||||
"""
|
"""A simple timer that will indicate when an expiration time has passed."""
|
||||||
A simple timer that will indicate when an expiration time has passed.
|
|
||||||
"""
|
|
||||||
def __init__(self, expiration):
|
def __init__(self, expiration):
|
||||||
'Create a timer that expires at `expiration` (UTC datetime)'
|
'Create a timer that expires at `expiration` (UTC datetime)'
|
||||||
self.expiration = expiration
|
self.expiration = expiration
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def after(cls, elapsed):
|
def after(cls, elapsed):
|
||||||
"""
|
"""Return a timer that will expire after `elapsed` passes."""
|
||||||
Return a timer that will expire after `elapsed` passes.
|
return cls(
|
||||||
"""
|
datetime.datetime.now(datetime.timezone.utc) + elapsed,
|
||||||
return cls(datetime.datetime.utcnow() + elapsed)
|
)
|
||||||
|
|
||||||
def expired(self):
|
def expired(self):
|
||||||
return datetime.datetime.utcnow() >= self.expiration
|
return datetime.datetime.now(
|
||||||
|
datetime.timezone.utc,
|
||||||
|
) >= self.expiration
|
||||||
|
|
||||||
|
|
||||||
class LockTimeout(Exception):
|
class LockTimeout(Exception):
|
||||||
@@ -30,9 +30,7 @@ class LockTimeout(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class LockChecker(object):
|
class LockChecker(object):
|
||||||
"""
|
"""Keep track of the time and detect if a timeout has expired."""
|
||||||
Keep track of the time and detect if a timeout has expired
|
|
||||||
"""
|
|
||||||
def __init__(self, session_id, timeout):
|
def __init__(self, session_id, timeout):
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
if timeout:
|
if timeout:
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ to get a quick sanity-check on overall CP performance. Use the
|
|||||||
``--profile`` flag when running the test suite. Then, use the ``serve()``
|
``--profile`` flag when running the test suite. Then, use the ``serve()``
|
||||||
function to browse the results in a web browser. If you run this
|
function to browse the results in a web browser. If you run this
|
||||||
module from the command line, it will call ``serve()`` for you.
|
module from the command line, it will call ``serve()`` for you.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
@@ -47,7 +46,9 @@ try:
|
|||||||
import pstats
|
import pstats
|
||||||
|
|
||||||
def new_func_strip_path(func_name):
|
def new_func_strip_path(func_name):
|
||||||
"""Make profiler output more readable by adding `__init__` modules' parents
|
"""Add ``__init__`` modules' parents.
|
||||||
|
|
||||||
|
This makes the profiler output more readable.
|
||||||
"""
|
"""
|
||||||
filename, line, name = func_name
|
filename, line, name = func_name
|
||||||
if filename.endswith('__init__.py'):
|
if filename.endswith('__init__.py'):
|
||||||
|
|||||||
@@ -27,18 +27,17 @@ from cherrypy._cpcompat import text_or_bytes
|
|||||||
|
|
||||||
|
|
||||||
class NamespaceSet(dict):
|
class NamespaceSet(dict):
|
||||||
|
|
||||||
"""A dict of config namespace names and handlers.
|
"""A dict of config namespace names and handlers.
|
||||||
|
|
||||||
Each config entry should begin with a namespace name; the corresponding
|
Each config entry should begin with a namespace name; the
|
||||||
namespace handler will be called once for each config entry in that
|
corresponding namespace handler will be called once for each config
|
||||||
namespace, and will be passed two arguments: the config key (with the
|
entry in that namespace, and will be passed two arguments: the
|
||||||
namespace removed) and the config value.
|
config key (with the namespace removed) and the config value.
|
||||||
|
|
||||||
Namespace handlers may be any Python callable; they may also be
|
Namespace handlers may be any Python callable; they may also be
|
||||||
context managers, in which case their __enter__
|
context managers, in which case their __enter__ method should return
|
||||||
method should return a callable to be used as the handler.
|
a callable to be used as the handler. See cherrypy.tools (the
|
||||||
See cherrypy.tools (the Toolbox class) for an example.
|
Toolbox class) for an example.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __call__(self, config):
|
def __call__(self, config):
|
||||||
@@ -48,9 +47,10 @@ class NamespaceSet(dict):
|
|||||||
A flat dict, where keys use dots to separate
|
A flat dict, where keys use dots to separate
|
||||||
namespaces, and values are arbitrary.
|
namespaces, and values are arbitrary.
|
||||||
|
|
||||||
The first name in each config key is used to look up the corresponding
|
The first name in each config key is used to look up the
|
||||||
namespace handler. For example, a config entry of {'tools.gzip.on': v}
|
corresponding namespace handler. For example, a config entry of
|
||||||
will call the 'tools' namespace handler with the args: ('gzip.on', v)
|
{'tools.gzip.on': v} will call the 'tools' namespace handler
|
||||||
|
with the args: ('gzip.on', v)
|
||||||
"""
|
"""
|
||||||
# Separate the given config into namespaces
|
# Separate the given config into namespaces
|
||||||
ns_confs = {}
|
ns_confs = {}
|
||||||
@@ -103,7 +103,6 @@ class NamespaceSet(dict):
|
|||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
class Config(dict):
|
||||||
|
|
||||||
"""A dict-like set of configuration data, with defaults and namespaces.
|
"""A dict-like set of configuration data, with defaults and namespaces.
|
||||||
|
|
||||||
May take a file, filename, or dict.
|
May take a file, filename, or dict.
|
||||||
@@ -167,7 +166,7 @@ class Parser(configparser.ConfigParser):
|
|||||||
self._read(fp, filename)
|
self._read(fp, filename)
|
||||||
|
|
||||||
def as_dict(self, raw=False, vars=None):
|
def as_dict(self, raw=False, vars=None):
|
||||||
"""Convert an INI file to a dictionary"""
|
"""Convert an INI file to a dictionary."""
|
||||||
# Load INI file into a dict
|
# Load INI file into a dict
|
||||||
result = {}
|
result = {}
|
||||||
for section in self.sections():
|
for section in self.sections():
|
||||||
@@ -188,7 +187,7 @@ class Parser(configparser.ConfigParser):
|
|||||||
|
|
||||||
def dict_from_file(self, file):
|
def dict_from_file(self, file):
|
||||||
if hasattr(file, 'read'):
|
if hasattr(file, 'read'):
|
||||||
self.readfp(file)
|
self.read_file(file)
|
||||||
else:
|
else:
|
||||||
self.read(file)
|
self.read(file)
|
||||||
return self.as_dict()
|
return self.as_dict()
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ missing = object()
|
|||||||
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
|
|
||||||
"""A CherryPy dict-like Session object (one per request)."""
|
"""A CherryPy dict-like Session object (one per request)."""
|
||||||
|
|
||||||
_id = None
|
_id = None
|
||||||
@@ -148,9 +147,11 @@ class Session(object):
|
|||||||
to session data."""
|
to session data."""
|
||||||
|
|
||||||
loaded = False
|
loaded = False
|
||||||
|
"""If True, data has been retrieved from storage.
|
||||||
|
|
||||||
|
This should happen automatically on the first attempt to access
|
||||||
|
session data.
|
||||||
"""
|
"""
|
||||||
If True, data has been retrieved from storage. This should happen
|
|
||||||
automatically on the first attempt to access session data."""
|
|
||||||
|
|
||||||
clean_thread = None
|
clean_thread = None
|
||||||
'Class-level Monitor which calls self.clean_up.'
|
'Class-level Monitor which calls self.clean_up.'
|
||||||
@@ -165,9 +166,10 @@ class Session(object):
|
|||||||
'True if the session requested by the client did not exist.'
|
'True if the session requested by the client did not exist.'
|
||||||
|
|
||||||
regenerated = False
|
regenerated = False
|
||||||
|
"""True if the application called session.regenerate().
|
||||||
|
|
||||||
|
This is not set by internal calls to regenerate the session id.
|
||||||
"""
|
"""
|
||||||
True if the application called session.regenerate(). This is not set by
|
|
||||||
internal calls to regenerate the session id."""
|
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
'If True, log debug information.'
|
'If True, log debug information.'
|
||||||
@@ -335,8 +337,9 @@ class Session(object):
|
|||||||
|
|
||||||
def pop(self, key, default=missing):
|
def pop(self, key, default=missing):
|
||||||
"""Remove the specified key and return the corresponding value.
|
"""Remove the specified key and return the corresponding value.
|
||||||
If key is not found, default is returned if given,
|
|
||||||
otherwise KeyError is raised.
|
If key is not found, default is returned if given, otherwise
|
||||||
|
KeyError is raised.
|
||||||
"""
|
"""
|
||||||
if not self.loaded:
|
if not self.loaded:
|
||||||
self.load()
|
self.load()
|
||||||
@@ -351,13 +354,19 @@ class Session(object):
|
|||||||
return key in self._data
|
return key in self._data
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None."""
|
"""D.get(k[,d]) -> D[k] if k in D, else d.
|
||||||
|
|
||||||
|
d defaults to None.
|
||||||
|
"""
|
||||||
if not self.loaded:
|
if not self.loaded:
|
||||||
self.load()
|
self.load()
|
||||||
return self._data.get(key, default)
|
return self._data.get(key, default)
|
||||||
|
|
||||||
def update(self, d):
|
def update(self, d):
|
||||||
"""D.update(E) -> None. Update D from E: for k in E: D[k] = E[k]."""
|
"""D.update(E) -> None.
|
||||||
|
|
||||||
|
Update D from E: for k in E: D[k] = E[k].
|
||||||
|
"""
|
||||||
if not self.loaded:
|
if not self.loaded:
|
||||||
self.load()
|
self.load()
|
||||||
self._data.update(d)
|
self._data.update(d)
|
||||||
@@ -369,7 +378,10 @@ class Session(object):
|
|||||||
return self._data.setdefault(key, default)
|
return self._data.setdefault(key, default)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""D.clear() -> None. Remove all items from D."""
|
"""D.clear() -> None.
|
||||||
|
|
||||||
|
Remove all items from D.
|
||||||
|
"""
|
||||||
if not self.loaded:
|
if not self.loaded:
|
||||||
self.load()
|
self.load()
|
||||||
self._data.clear()
|
self._data.clear()
|
||||||
@@ -492,7 +504,8 @@ class FileSession(Session):
|
|||||||
"""Set up the storage system for file-based sessions.
|
"""Set up the storage system for file-based sessions.
|
||||||
|
|
||||||
This should only be called once per process; this will be done
|
This should only be called once per process; this will be done
|
||||||
automatically when using sessions.init (as the built-in Tool does).
|
automatically when using sessions.init (as the built-in Tool
|
||||||
|
does).
|
||||||
"""
|
"""
|
||||||
# The 'storage_path' arg is required for file-based sessions.
|
# The 'storage_path' arg is required for file-based sessions.
|
||||||
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
|
||||||
@@ -616,7 +629,8 @@ class MemcachedSession(Session):
|
|||||||
"""Set up the storage system for memcached-based sessions.
|
"""Set up the storage system for memcached-based sessions.
|
||||||
|
|
||||||
This should only be called once per process; this will be done
|
This should only be called once per process; this will be done
|
||||||
automatically when using sessions.init (as the built-in Tool does).
|
automatically when using sessions.init (as the built-in Tool
|
||||||
|
does).
|
||||||
"""
|
"""
|
||||||
for k, v in kwargs.items():
|
for k, v in kwargs.items():
|
||||||
setattr(cls, k, v)
|
setattr(cls, k, v)
|
||||||
|
|||||||
+15
-13
@@ -1,19 +1,18 @@
|
|||||||
"""Module with helpers for serving static files."""
|
"""Module with helpers for serving static files."""
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import mimetypes
|
|
||||||
import urllib.parse
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
import urllib.parse
|
||||||
from email.generator import _make_boundary as make_boundary
|
from email.generator import _make_boundary as make_boundary
|
||||||
from io import UnsupportedOperation
|
from io import UnsupportedOperation
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
from cherrypy._cpcompat import ntob
|
from cherrypy._cpcompat import ntob
|
||||||
from cherrypy.lib import cptools, httputil, file_generator_limited
|
from cherrypy.lib import cptools, file_generator_limited, httputil
|
||||||
|
|
||||||
|
|
||||||
def _setup_mimetypes():
|
def _setup_mimetypes():
|
||||||
@@ -57,15 +56,15 @@ def serve_file(path, content_type=None, disposition=None, name=None,
|
|||||||
debug=False):
|
debug=False):
|
||||||
"""Set status, headers, and body in order to serve the given path.
|
"""Set status, headers, and body in order to serve the given path.
|
||||||
|
|
||||||
The Content-Type header will be set to the content_type arg, if provided.
|
The Content-Type header will be set to the content_type arg, if
|
||||||
If not provided, the Content-Type will be guessed by the file extension
|
provided. If not provided, the Content-Type will be guessed by the
|
||||||
of the 'path' argument.
|
file extension of the 'path' argument.
|
||||||
|
|
||||||
If disposition is not None, the Content-Disposition header will be set
|
If disposition is not None, the Content-Disposition header will be
|
||||||
to "<disposition>; filename=<name>; filename*=utf-8''<name>"
|
set to "<disposition>; filename=<name>; filename*=utf-8''<name>" as
|
||||||
as described in :rfc:`6266#appendix-D`.
|
described in :rfc:`6266#appendix-D`. If name is None, it will be set
|
||||||
If name is None, it will be set to the basename of path.
|
to the basename of path. If disposition is None, no Content-
|
||||||
If disposition is None, no Content-Disposition header will be written.
|
Disposition header will be written.
|
||||||
"""
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
@@ -185,7 +184,10 @@ def serve_fileobj(fileobj, content_type=None, disposition=None, name=None,
|
|||||||
|
|
||||||
|
|
||||||
def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
def _serve_fileobj(fileobj, content_type, content_length, debug=False):
|
||||||
"""Internal. Set response.body to the given file object, perhaps ranged."""
|
"""Set ``response.body`` to the given file object, perhaps ranged.
|
||||||
|
|
||||||
|
Internal helper.
|
||||||
|
"""
|
||||||
response = cherrypy.serving.response
|
response = cherrypy.serving.response
|
||||||
|
|
||||||
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
|
# HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ _module__file__base = os.getcwd()
|
|||||||
|
|
||||||
|
|
||||||
class SimplePlugin(object):
|
class SimplePlugin(object):
|
||||||
|
|
||||||
"""Plugin base class which auto-subscribes methods for known channels."""
|
"""Plugin base class which auto-subscribes methods for known channels."""
|
||||||
|
|
||||||
bus = None
|
bus = None
|
||||||
@@ -59,7 +58,6 @@ class SimplePlugin(object):
|
|||||||
|
|
||||||
|
|
||||||
class SignalHandler(object):
|
class SignalHandler(object):
|
||||||
|
|
||||||
"""Register bus channels (and listeners) for system signals.
|
"""Register bus channels (and listeners) for system signals.
|
||||||
|
|
||||||
You can modify what signals your application listens for, and what it does
|
You can modify what signals your application listens for, and what it does
|
||||||
@@ -171,8 +169,8 @@ class SignalHandler(object):
|
|||||||
If the optional 'listener' argument is provided, it will be
|
If the optional 'listener' argument is provided, it will be
|
||||||
subscribed as a listener for the given signal's channel.
|
subscribed as a listener for the given signal's channel.
|
||||||
|
|
||||||
If the given signal name or number is not available on the current
|
If the given signal name or number is not available on the
|
||||||
platform, ValueError is raised.
|
current platform, ValueError is raised.
|
||||||
"""
|
"""
|
||||||
if isinstance(signal, text_or_bytes):
|
if isinstance(signal, text_or_bytes):
|
||||||
signum = getattr(_signal, signal, None)
|
signum = getattr(_signal, signal, None)
|
||||||
@@ -218,11 +216,10 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
class DropPrivileges(SimplePlugin):
|
class DropPrivileges(SimplePlugin):
|
||||||
|
|
||||||
"""Drop privileges. uid/gid arguments not available on Windows.
|
"""Drop privileges. uid/gid arguments not available on Windows.
|
||||||
|
|
||||||
Special thanks to `Gavin Baker
|
Special thanks to `Gavin Baker
|
||||||
<http://antonym.org/2005/12/dropping-privileges-in-python.html>`_
|
<http://antonym.org/2005/12/dropping-privileges-in-python.html>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, bus, umask=None, uid=None, gid=None):
|
def __init__(self, bus, umask=None, uid=None, gid=None):
|
||||||
@@ -234,7 +231,10 @@ class DropPrivileges(SimplePlugin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def uid(self):
|
def uid(self):
|
||||||
"""The uid under which to run. Availability: Unix."""
|
"""The uid under which to run.
|
||||||
|
|
||||||
|
Availability: Unix.
|
||||||
|
"""
|
||||||
return self._uid
|
return self._uid
|
||||||
|
|
||||||
@uid.setter
|
@uid.setter
|
||||||
@@ -250,7 +250,10 @@ class DropPrivileges(SimplePlugin):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def gid(self):
|
def gid(self):
|
||||||
"""The gid under which to run. Availability: Unix."""
|
"""The gid under which to run.
|
||||||
|
|
||||||
|
Availability: Unix.
|
||||||
|
"""
|
||||||
return self._gid
|
return self._gid
|
||||||
|
|
||||||
@gid.setter
|
@gid.setter
|
||||||
@@ -332,7 +335,6 @@ class DropPrivileges(SimplePlugin):
|
|||||||
|
|
||||||
|
|
||||||
class Daemonizer(SimplePlugin):
|
class Daemonizer(SimplePlugin):
|
||||||
|
|
||||||
"""Daemonize the running script.
|
"""Daemonize the running script.
|
||||||
|
|
||||||
Use this with a Web Site Process Bus via::
|
Use this with a Web Site Process Bus via::
|
||||||
@@ -423,7 +425,6 @@ class Daemonizer(SimplePlugin):
|
|||||||
|
|
||||||
|
|
||||||
class PIDFile(SimplePlugin):
|
class PIDFile(SimplePlugin):
|
||||||
|
|
||||||
"""Maintain a PID file via a WSPBus."""
|
"""Maintain a PID file via a WSPBus."""
|
||||||
|
|
||||||
def __init__(self, bus, pidfile):
|
def __init__(self, bus, pidfile):
|
||||||
@@ -453,12 +454,11 @@ class PIDFile(SimplePlugin):
|
|||||||
|
|
||||||
|
|
||||||
class PerpetualTimer(threading.Timer):
|
class PerpetualTimer(threading.Timer):
|
||||||
|
|
||||||
"""A responsive subclass of threading.Timer whose run() method repeats.
|
"""A responsive subclass of threading.Timer whose run() method repeats.
|
||||||
|
|
||||||
Use this timer only when you really need a very interruptible timer;
|
Use this timer only when you really need a very interruptible timer;
|
||||||
this checks its 'finished' condition up to 20 times a second, which can
|
this checks its 'finished' condition up to 20 times a second, which
|
||||||
results in pretty high CPU usage
|
can results in pretty high CPU usage
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -483,14 +483,14 @@ class PerpetualTimer(threading.Timer):
|
|||||||
|
|
||||||
|
|
||||||
class BackgroundTask(threading.Thread):
|
class BackgroundTask(threading.Thread):
|
||||||
|
|
||||||
"""A subclass of threading.Thread whose run() method repeats.
|
"""A subclass of threading.Thread whose run() method repeats.
|
||||||
|
|
||||||
Use this class for most repeating tasks. It uses time.sleep() to wait
|
Use this class for most repeating tasks. It uses time.sleep() to
|
||||||
for each interval, which isn't very responsive; that is, even if you call
|
wait for each interval, which isn't very responsive; that is, even
|
||||||
self.cancel(), you'll have to wait until the sleep() call finishes before
|
if you call self.cancel(), you'll have to wait until the sleep()
|
||||||
the thread stops. To compensate, it defaults to being daemonic, which means
|
call finishes before the thread stops. To compensate, it defaults to
|
||||||
it won't delay stopping the whole process.
|
being daemonic, which means it won't delay stopping the whole
|
||||||
|
process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
|
def __init__(self, interval, function, args=[], kwargs={}, bus=None):
|
||||||
@@ -525,7 +525,6 @@ class BackgroundTask(threading.Thread):
|
|||||||
|
|
||||||
|
|
||||||
class Monitor(SimplePlugin):
|
class Monitor(SimplePlugin):
|
||||||
|
|
||||||
"""WSPBus listener to periodically run a callback in its own thread."""
|
"""WSPBus listener to periodically run a callback in its own thread."""
|
||||||
|
|
||||||
callback = None
|
callback = None
|
||||||
@@ -582,7 +581,6 @@ class Monitor(SimplePlugin):
|
|||||||
|
|
||||||
|
|
||||||
class Autoreloader(Monitor):
|
class Autoreloader(Monitor):
|
||||||
|
|
||||||
"""Monitor which re-executes the process when files change.
|
"""Monitor which re-executes the process when files change.
|
||||||
|
|
||||||
This :ref:`plugin<plugins>` restarts the process (via :func:`os.execv`)
|
This :ref:`plugin<plugins>` restarts the process (via :func:`os.execv`)
|
||||||
@@ -699,20 +697,20 @@ class Autoreloader(Monitor):
|
|||||||
|
|
||||||
|
|
||||||
class ThreadManager(SimplePlugin):
|
class ThreadManager(SimplePlugin):
|
||||||
|
|
||||||
"""Manager for HTTP request threads.
|
"""Manager for HTTP request threads.
|
||||||
|
|
||||||
If you have control over thread creation and destruction, publish to
|
If you have control over thread creation and destruction, publish to
|
||||||
the 'acquire_thread' and 'release_thread' channels (for each thread).
|
the 'acquire_thread' and 'release_thread' channels (for each
|
||||||
This will register/unregister the current thread and publish to
|
thread). This will register/unregister the current thread and
|
||||||
'start_thread' and 'stop_thread' listeners in the bus as needed.
|
publish to 'start_thread' and 'stop_thread' listeners in the bus as
|
||||||
|
needed.
|
||||||
|
|
||||||
If threads are created and destroyed by code you do not control
|
If threads are created and destroyed by code you do not control
|
||||||
(e.g., Apache), then, at the beginning of every HTTP request,
|
(e.g., Apache), then, at the beginning of every HTTP request,
|
||||||
publish to 'acquire_thread' only. You should not publish to
|
publish to 'acquire_thread' only. You should not publish to
|
||||||
'release_thread' in this case, since you do not know whether
|
'release_thread' in this case, since you do not know whether the
|
||||||
the thread will be re-used or not. The bus will call
|
thread will be re-used or not. The bus will call 'stop_thread'
|
||||||
'stop_thread' listeners for you when it stops.
|
listeners for you when it stops.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
threads = None
|
threads = None
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ class Timeouts:
|
|||||||
|
|
||||||
|
|
||||||
class ServerAdapter(object):
|
class ServerAdapter(object):
|
||||||
|
|
||||||
"""Adapter for an HTTP server.
|
"""Adapter for an HTTP server.
|
||||||
|
|
||||||
If you need to start more than one HTTP server (to serve on multiple
|
If you need to start more than one HTTP server (to serve on multiple
|
||||||
@@ -188,9 +187,7 @@ class ServerAdapter(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""
|
"""A description about where this server is bound."""
|
||||||
A description about where this server is bound.
|
|
||||||
"""
|
|
||||||
if self.bind_addr is None:
|
if self.bind_addr is None:
|
||||||
on_what = 'unknown interface (dynamic?)'
|
on_what = 'unknown interface (dynamic?)'
|
||||||
elif isinstance(self.bind_addr, tuple):
|
elif isinstance(self.bind_addr, tuple):
|
||||||
@@ -292,7 +289,6 @@ class ServerAdapter(object):
|
|||||||
|
|
||||||
|
|
||||||
class FlupCGIServer(object):
|
class FlupCGIServer(object):
|
||||||
|
|
||||||
"""Adapter for a flup.server.cgi.WSGIServer."""
|
"""Adapter for a flup.server.cgi.WSGIServer."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -316,7 +312,6 @@ class FlupCGIServer(object):
|
|||||||
|
|
||||||
|
|
||||||
class FlupFCGIServer(object):
|
class FlupFCGIServer(object):
|
||||||
|
|
||||||
"""Adapter for a flup.server.fcgi.WSGIServer."""
|
"""Adapter for a flup.server.fcgi.WSGIServer."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -362,7 +357,6 @@ class FlupFCGIServer(object):
|
|||||||
|
|
||||||
|
|
||||||
class FlupSCGIServer(object):
|
class FlupSCGIServer(object):
|
||||||
|
|
||||||
"""Adapter for a flup.server.scgi.WSGIServer."""
|
"""Adapter for a flup.server.scgi.WSGIServer."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
"""Windows service. Requires pywin32."""
|
"""Windows service.
|
||||||
|
|
||||||
|
Requires pywin32.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import win32api
|
import win32api
|
||||||
@@ -11,7 +14,6 @@ from cherrypy.process import wspbus, plugins
|
|||||||
|
|
||||||
|
|
||||||
class ConsoleCtrlHandler(plugins.SimplePlugin):
|
class ConsoleCtrlHandler(plugins.SimplePlugin):
|
||||||
|
|
||||||
"""A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
|
"""A WSPBus plugin for handling Win32 console events (like Ctrl-C)."""
|
||||||
|
|
||||||
def __init__(self, bus):
|
def __init__(self, bus):
|
||||||
@@ -69,10 +71,10 @@ class ConsoleCtrlHandler(plugins.SimplePlugin):
|
|||||||
|
|
||||||
|
|
||||||
class Win32Bus(wspbus.Bus):
|
class Win32Bus(wspbus.Bus):
|
||||||
|
|
||||||
"""A Web Site Process Bus implementation for Win32.
|
"""A Web Site Process Bus implementation for Win32.
|
||||||
|
|
||||||
Instead of time.sleep, this bus blocks using native win32event objects.
|
Instead of time.sleep, this bus blocks using native win32event
|
||||||
|
objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -120,7 +122,6 @@ class Win32Bus(wspbus.Bus):
|
|||||||
|
|
||||||
|
|
||||||
class _ControlCodes(dict):
|
class _ControlCodes(dict):
|
||||||
|
|
||||||
"""Control codes used to "signal" a service via ControlService.
|
"""Control codes used to "signal" a service via ControlService.
|
||||||
|
|
||||||
User-defined control codes are in the range 128-255. We generally use
|
User-defined control codes are in the range 128-255. We generally use
|
||||||
@@ -152,7 +153,6 @@ def signal_child(service, command):
|
|||||||
|
|
||||||
|
|
||||||
class PyWebService(win32serviceutil.ServiceFramework):
|
class PyWebService(win32serviceutil.ServiceFramework):
|
||||||
|
|
||||||
"""Python Web Service."""
|
"""Python Web Service."""
|
||||||
|
|
||||||
_svc_name_ = 'Python Web Service'
|
_svc_name_ = 'Python Web Service'
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ the new state.::
|
|||||||
| \ |
|
| \ |
|
||||||
| V V
|
| V V
|
||||||
STARTED <-- STARTING
|
STARTED <-- STARTING
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
@@ -65,7 +64,7 @@ import atexit
|
|||||||
try:
|
try:
|
||||||
import ctypes
|
import ctypes
|
||||||
except ImportError:
|
except ImportError:
|
||||||
"""Google AppEngine is shipped without ctypes
|
"""Google AppEngine is shipped without ctypes.
|
||||||
|
|
||||||
:seealso: http://stackoverflow.com/a/6523777/70170
|
:seealso: http://stackoverflow.com/a/6523777/70170
|
||||||
"""
|
"""
|
||||||
@@ -165,8 +164,8 @@ class Bus(object):
|
|||||||
All listeners for a given channel are guaranteed to be called even
|
All listeners for a given channel are guaranteed to be called even
|
||||||
if others at the same channel fail. Each failure is logged, but
|
if others at the same channel fail. Each failure is logged, but
|
||||||
execution proceeds on to the next listener. The only way to stop all
|
execution proceeds on to the next listener. The only way to stop all
|
||||||
processing from inside a listener is to raise SystemExit and stop the
|
processing from inside a listener is to raise SystemExit and stop
|
||||||
whole server.
|
the whole server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
states = states
|
states = states
|
||||||
@@ -312,8 +311,9 @@ class Bus(object):
|
|||||||
def restart(self):
|
def restart(self):
|
||||||
"""Restart the process (may close connections).
|
"""Restart the process (may close connections).
|
||||||
|
|
||||||
This method does not restart the process from the calling thread;
|
This method does not restart the process from the calling
|
||||||
instead, it stops the bus and asks the main thread to call execv.
|
thread; instead, it stops the bus and asks the main thread to
|
||||||
|
call execv.
|
||||||
"""
|
"""
|
||||||
self.execv = True
|
self.execv = True
|
||||||
self.exit()
|
self.exit()
|
||||||
@@ -327,10 +327,11 @@ class Bus(object):
|
|||||||
"""Wait for the EXITING state, KeyboardInterrupt or SystemExit.
|
"""Wait for the EXITING state, KeyboardInterrupt or SystemExit.
|
||||||
|
|
||||||
This function is intended to be called only by the main thread.
|
This function is intended to be called only by the main thread.
|
||||||
After waiting for the EXITING state, it also waits for all threads
|
After waiting for the EXITING state, it also waits for all
|
||||||
to terminate, and then calls os.execv if self.execv is True. This
|
threads to terminate, and then calls os.execv if self.execv is
|
||||||
design allows another thread to call bus.restart, yet have the main
|
True. This design allows another thread to call bus.restart, yet
|
||||||
thread perform the actual execv call (required on some platforms).
|
have the main thread perform the actual execv call (required on
|
||||||
|
some platforms).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.wait(states.EXITING, interval=interval, channel='main')
|
self.wait(states.EXITING, interval=interval, channel='main')
|
||||||
@@ -379,13 +380,14 @@ class Bus(object):
|
|||||||
def _do_execv(self):
|
def _do_execv(self):
|
||||||
"""Re-execute the current process.
|
"""Re-execute the current process.
|
||||||
|
|
||||||
This must be called from the main thread, because certain platforms
|
This must be called from the main thread, because certain
|
||||||
(OS X) don't allow execv to be called in a child thread very well.
|
platforms (OS X) don't allow execv to be called in a child
|
||||||
|
thread very well.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
args = self._get_true_argv()
|
args = self._get_true_argv()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
"""It's probably win32 or GAE"""
|
"""It's probably win32 or GAE."""
|
||||||
args = [sys.executable] + self._get_interpreter_argv() + sys.argv
|
args = [sys.executable] + self._get_interpreter_argv() + sys.argv
|
||||||
|
|
||||||
self.log('Re-spawning %s' % ' '.join(args))
|
self.log('Re-spawning %s' % ' '.join(args))
|
||||||
@@ -472,7 +474,7 @@ class Bus(object):
|
|||||||
c_ind = None
|
c_ind = None
|
||||||
|
|
||||||
if is_module:
|
if is_module:
|
||||||
"""It's containing `-m -m` sequence of arguments"""
|
"""It's containing `-m -m` sequence of arguments."""
|
||||||
if is_command and c_ind < m_ind:
|
if is_command and c_ind < m_ind:
|
||||||
"""There's `-c -c` before `-m`"""
|
"""There's `-c -c` before `-m`"""
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@@ -481,7 +483,7 @@ class Bus(object):
|
|||||||
# Survive module argument here
|
# Survive module argument here
|
||||||
original_module = sys.argv[0]
|
original_module = sys.argv[0]
|
||||||
if not os.access(original_module, os.R_OK):
|
if not os.access(original_module, os.R_OK):
|
||||||
"""There's no such module exist"""
|
"""There's no such module exist."""
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
"{} doesn't seem to be a module "
|
"{} doesn't seem to be a module "
|
||||||
'accessible by current user'.format(original_module))
|
'accessible by current user'.format(original_module))
|
||||||
@@ -489,12 +491,12 @@ class Bus(object):
|
|||||||
# ... and substitute it with the original module path:
|
# ... and substitute it with the original module path:
|
||||||
_argv.insert(m_ind, original_module)
|
_argv.insert(m_ind, original_module)
|
||||||
elif is_command:
|
elif is_command:
|
||||||
"""It's containing just `-c -c` sequence of arguments"""
|
"""It's containing just `-c -c` sequence of arguments."""
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Cannot reconstruct command from '-c'. "
|
"Cannot reconstruct command from '-c'. "
|
||||||
'Ref: https://github.com/cherrypy/cherrypy/issues/1545')
|
'Ref: https://github.com/cherrypy/cherrypy/issues/1545')
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
"""It looks Py_GetArgcArgv is completely absent in some environments
|
"""It looks Py_GetArgcArgv's completely absent in some environments
|
||||||
|
|
||||||
It is known, that there's no Py_GetArgcArgv in MS Windows and
|
It is known, that there's no Py_GetArgcArgv in MS Windows and
|
||||||
``ctypes`` module is completely absent in Google AppEngine
|
``ctypes`` module is completely absent in Google AppEngine
|
||||||
@@ -512,13 +514,13 @@ class Bus(object):
|
|||||||
"""Prepend current working dir to PATH environment variable if needed.
|
"""Prepend current working dir to PATH environment variable if needed.
|
||||||
|
|
||||||
If sys.path[0] is an empty string, the interpreter was likely
|
If sys.path[0] is an empty string, the interpreter was likely
|
||||||
invoked with -m and the effective path is about to change on
|
invoked with -m and the effective path is about to change on re-
|
||||||
re-exec. Add the current directory to $PYTHONPATH to ensure
|
exec. Add the current directory to $PYTHONPATH to ensure that
|
||||||
that the new process sees the same path.
|
the new process sees the same path.
|
||||||
|
|
||||||
This issue cannot be addressed in the general case because
|
This issue cannot be addressed in the general case because
|
||||||
Python cannot reliably reconstruct the
|
Python cannot reliably reconstruct the original command line (
|
||||||
original command line (http://bugs.python.org/issue14208).
|
http://bugs.python.org/issue14208).
|
||||||
|
|
||||||
(This idea filched from tornado.autoreload)
|
(This idea filched from tornado.autoreload)
|
||||||
"""
|
"""
|
||||||
@@ -536,10 +538,10 @@ class Bus(object):
|
|||||||
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
"""Set the CLOEXEC flag on all open files (except stdin/out/err).
|
||||||
|
|
||||||
If self.max_cloexec_files is an integer (the default), then on
|
If self.max_cloexec_files is an integer (the default), then on
|
||||||
platforms which support it, it represents the max open files setting
|
platforms which support it, it represents the max open files
|
||||||
for the operating system. This function will be called just before
|
setting for the operating system. This function will be called
|
||||||
the process is restarted via os.execv() to prevent open files
|
just before the process is restarted via os.execv() to prevent
|
||||||
from persisting into the new process.
|
open files from persisting into the new process.
|
||||||
|
|
||||||
Set self.max_cloexec_files to 0 to disable this behavior.
|
Set self.max_cloexec_files to 0 to disable this behavior.
|
||||||
"""
|
"""
|
||||||
@@ -578,7 +580,10 @@ class Bus(object):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
def log(self, msg='', level=20, traceback=False):
|
def log(self, msg='', level=20, traceback=False):
|
||||||
"""Log the given message. Append the last traceback if requested."""
|
"""Log the given message.
|
||||||
|
|
||||||
|
Append the last traceback if requested.
|
||||||
|
"""
|
||||||
if traceback:
|
if traceback:
|
||||||
msg += '\n' + ''.join(_traceback.format_exception(*sys.exc_info()))
|
msg += '\n' + ''.join(_traceback.format_exception(*sys.exc_info()))
|
||||||
self.publish('log', msg, level)
|
self.publish('log', msg, level)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Even before any tweaking, this should serve a few demonstration pages.
|
|||||||
Change to this directory and run:
|
Change to this directory and run:
|
||||||
|
|
||||||
cherryd -c site.conf
|
cherryd -c site.conf
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|||||||
Reference in New Issue
Block a user