1
0
mirror of https://github.com/Tygs/0bin.git synced 2023-08-10 21:13:00 +03:00

Expiration and burn after reading works

This commit is contained in:
sam 2012-04-27 04:41:20 +07:00
parent 19480bc1f0
commit 1a46d998ac
76 changed files with 80 additions and 11750 deletions

View File

@ -1,61 +0,0 @@
"""<MyProject>, a CherryPy application.
Use this as a base for creating new CherryPy applications. When you want
to make a new app, copy and paste this folder to some other location
(maybe site-packages) and rename it to the name of your project,
then tweak as desired.
Even before any tweaking, this should serve a few demonstration pages.
Change to this directory and run:
../cherryd -c site.conf
"""
import cherrypy
from cherrypy import tools, url
import os
local_dir = os.path.join(os.getcwd(), os.path.dirname(__file__))
class Root:
_cp_config = {'tools.log_tracebacks.on': True,
}
def index(self):
return """<html>
<body>Try some <a href='%s?a=7'>other</a> path,
or a <a href='%s?n=14'>default</a> path.<br />
Or, just look at the pretty picture:<br />
<img src='%s' />
</body></html>""" % (url("other"), url("else"),
url("files/made_with_cherrypy_small.png"))
index.exposed = True
def default(self, *args, **kwargs):
return "args: %s kwargs: %s" % (args, kwargs)
default.exposed = True
def other(self, a=2, b='bananas', c=None):
cherrypy.response.headers['Content-Type'] = 'text/plain'
if c is None:
return "Have %d %s." % (int(a), b)
else:
return "Have %d %s, %s." % (int(a), b, c)
other.exposed = True
files = cherrypy.tools.staticdir.handler(
section="/files",
dir=os.path.join(local_dir, "static"),
# Ignore .php files, etc.
match=r'\.(css|gif|html?|ico|jpe?g|js|png|swf|xml)$',
)
root = Root()
# Uncomment the following to use your own favicon instead of CP's default.
#favicon_path = os.path.join(local_dir, "favicon.ico")
#root.favicon_ico = tools.staticfile.handler(filename=favicon_path)

View File

@ -1,3 +0,0 @@
[/]
log.error_file: "error.log"
log.access_file: "access.log"

View File

@ -1,14 +0,0 @@
[global]
# Uncomment this when you're done developing
#environment: "production"
server.socket_host: "0.0.0.0"
server.socket_port: 8088
# Uncomment the following lines to run on HTTPS at the same time
#server.2.socket_host: "0.0.0.0"
#server.2.socket_port: 8433
#server.2.ssl_certificate: '../test/test.pem'
#server.2.ssl_private_key: '../test/test.pem'
tree.myapp: cherrypy.Application(scaffold.root, "/", "example.conf")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,27 +0,0 @@
"""Regression test suite for CherryPy.
Run 'nosetests -s test/' to exercise all tests.
The '-s' flag instructs nose to output stdout messages, wihch is crucial to
the 'interactive' mode of webtest.py. If you run these tests without the '-s'
flag, don't be surprised if the test seems to hang: it's waiting for your
interactive input.
"""
import os
import sys
def newexit():
os._exit(1)
def setup():
# We want to monkey patch sys.exit so that we can get some
# information about where exit is being called.
newexit._old = sys.exit
sys.exit = newexit
def teardown():
try:
sys.exit = sys.exit._old
except AttributeError:
sys.exit = sys._exit

View File

@ -1,41 +0,0 @@
"""Test module for the @-decorator syntax, which is version-specific"""
from cherrypy import expose, tools
from cherrypy._cpcompat import ntob
class ExposeExamples(object):
@expose
def no_call(self):
return "Mr E. R. Bradshaw"
@expose()
def call_empty(self):
return "Mrs. B.J. Smegma"
@expose("call_alias")
def nesbitt(self):
return "Mr Nesbitt"
@expose(["alias1", "alias2"])
def andrews(self):
return "Mr Ken Andrews"
@expose(alias="alias3")
def watson(self):
return "Mr. and Mrs. Watson"
class ToolExamples(object):
@expose
@tools.response_headers(headers=[('Content-Type', 'application/data')])
def blah(self):
yield ntob("blah")
# This is here to demonstrate that _cp_config = {...} overwrites
# the _cp_config attribute added by the Tool decorator. You have
# to write _cp_config[k] = v or _cp_config.update(...) instead.
blah._cp_config['response.stream'] = True

View File

@ -1,66 +0,0 @@
import os
import sys
import time
starttime = time.time()
import cherrypy
class Root:
def index(self):
return "Hello World"
index.exposed = True
def mtimes(self):
return repr(cherrypy.engine.publish("Autoreloader", "mtimes"))
mtimes.exposed = True
def pid(self):
return str(os.getpid())
pid.exposed = True
def start(self):
return repr(starttime)
start.exposed = True
def exit(self):
# This handler might be called before the engine is STARTED if an
# HTTP worker thread handles it before the HTTP server returns
# control to engine.start. We avoid that race condition here
# by waiting for the Bus to be STARTED.
cherrypy.engine.wait(state=cherrypy.engine.states.STARTED)
cherrypy.engine.exit()
exit.exposed = True
def unsub_sig():
cherrypy.log("unsubsig: %s" % cherrypy.config.get('unsubsig', False))
if cherrypy.config.get('unsubsig', False):
cherrypy.log("Unsubscribing the default cherrypy signal handler")
cherrypy.engine.signal_handler.unsubscribe()
try:
from signal import signal, SIGTERM
except ImportError:
pass
else:
def old_term_handler(signum=None, frame=None):
cherrypy.log("I am an old SIGTERM handler.")
sys.exit(0)
cherrypy.log("Subscribing the new one.")
signal(SIGTERM, old_term_handler)
cherrypy.engine.subscribe('start', unsub_sig, priority=100)
def starterror():
if cherrypy.config.get('starterror', False):
zerodiv = 1 / 0
cherrypy.engine.subscribe('start', starterror, priority=6)
def log_test_case_name():
if cherrypy.config.get('test_case_name', False):
cherrypy.log("STARTED FROM: %s" % cherrypy.config.get('test_case_name'))
cherrypy.engine.subscribe('start', log_test_case_name, priority=6)
cherrypy.tree.mount(Root(), '/', {'/': {}})

View File

@ -1,409 +0,0 @@
"""CherryPy Benchmark Tool
Usage:
benchmark.py --null --notests --help --cpmodpy --modpython --ab=path --apache=path
--null: use a null Request object (to bench the HTTP server only)
--notests: start the server but do not run the tests; this allows
you to check the tested pages with a browser
--help: show this help message
--cpmodpy: run tests via apache on 54583 (with the builtin _cpmodpy)
--modpython: run tests via apache on 54583 (with modpython_gateway)
--ab=path: Use the ab script/executable at 'path' (see below)
--apache=path: Use the apache script/exe at 'path' (see below)
To run the benchmarks, the Apache Benchmark tool "ab" must either be on
your system path, or specified via the --ab=path option.
To run the modpython tests, the "apache" executable or script must be
on your system path, or provided via the --apache=path option. On some
platforms, "apache" may be called "apachectl" or "apache2ctl"--create
a symlink to them if needed.
"""
import getopt
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import re
import sys
import time
import traceback
import cherrypy
from cherrypy._cpcompat import ntob
from cherrypy import _cperror, _cpmodpy
from cherrypy.lib import httputil
AB_PATH = ""
APACHE_PATH = "apache"
SCRIPT_NAME = "/cpbench/users/rdelon/apps/blog"
__all__ = ['ABSession', 'Root', 'print_report',
'run_standard_benchmarks', 'safe_threads',
'size_report', 'startup', 'thread_report',
]
size_cache = {}
class Root:
def index(self):
return """<html>
<head>
<title>CherryPy Benchmark</title>
</head>
<body>
<ul>
<li><a href="hello">Hello, world! (14 byte dynamic)</a></li>
<li><a href="static/index.html">Static file (14 bytes static)</a></li>
<li><form action="sizer">Response of length:
<input type='text' name='size' value='10' /></form>
</li>
</ul>
</body>
</html>"""
index.exposed = True
def hello(self):
return "Hello, world\r\n"
hello.exposed = True
def sizer(self, size):
resp = size_cache.get(size, None)
if resp is None:
size_cache[size] = resp = "X" * int(size)
return resp
sizer.exposed = True
cherrypy.config.update({
'log.error.file': '',
'environment': 'production',
'server.socket_host': '127.0.0.1',
'server.socket_port': 54583,
'server.max_request_header_size': 0,
'server.max_request_body_size': 0,
'engine.deadlock_poll_freq': 0,
})
# Cheat mode on ;)
del cherrypy.config['tools.log_tracebacks.on']
del cherrypy.config['tools.log_headers.on']
del cherrypy.config['tools.trailing_slash.on']
appconf = {
'/static': {
'tools.staticdir.on': True,
'tools.staticdir.dir': 'static',
'tools.staticdir.root': curdir,
},
}
app = cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf)
class NullRequest:
"""A null HTTP request class, returning 200 and an empty body."""
def __init__(self, local, remote, scheme="http"):
pass
def close(self):
pass
def run(self, method, path, query_string, protocol, headers, rfile):
cherrypy.response.status = "200 OK"
cherrypy.response.header_list = [("Content-Type", 'text/html'),
("Server", "Null CherryPy"),
("Date", httputil.HTTPDate()),
("Content-Length", "0"),
]
cherrypy.response.body = [""]
return cherrypy.response
class NullResponse:
pass
class ABSession:
"""A session of 'ab', the Apache HTTP server benchmarking tool.
Example output from ab:
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
Benchmarking 127.0.0.1 (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Server Software: CherryPy/3.1beta
Server Hostname: 127.0.0.1
Server Port: 54583
Document Path: /static/index.html
Document Length: 14 bytes
Concurrency Level: 10
Time taken for tests: 9.643867 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 189000 bytes
HTML transferred: 14000 bytes
Requests per second: 103.69 [#/sec] (mean)
Time per request: 96.439 [ms] (mean)
Time per request: 9.644 [ms] (mean, across all concurrent requests)
Transfer rate: 19.08 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 2.9 0 10
Processing: 20 94 7.3 90 130
Waiting: 0 43 28.1 40 100
Total: 20 95 7.3 100 130
Percentage of the requests served within a certain time (ms)
50% 100
66% 100
75% 100
80% 100
90% 100
95% 100
98% 100
99% 110
100% 130 (longest request)
Finished 1000 requests
"""
parse_patterns = [('complete_requests', 'Completed',
ntob(r'^Complete requests:\s*(\d+)')),
('failed_requests', 'Failed',
ntob(r'^Failed requests:\s*(\d+)')),
('requests_per_second', 'req/sec',
ntob(r'^Requests per second:\s*([0-9.]+)')),
('time_per_request_concurrent', 'msec/req',
ntob(r'^Time per request:\s*([0-9.]+).*concurrent requests\)$')),
('transfer_rate', 'KB/sec',
ntob(r'^Transfer rate:\s*([0-9.]+)')),
]
def __init__(self, path=SCRIPT_NAME + "/hello", requests=1000, concurrency=10):
self.path = path
self.requests = requests
self.concurrency = concurrency
def args(self):
port = cherrypy.server.socket_port
assert self.concurrency > 0
assert self.requests > 0
# Don't use "localhost".
# Cf http://mail.python.org/pipermail/python-win32/2008-March/007050.html
return ("-k -n %s -c %s http://127.0.0.1:%s%s" %
(self.requests, self.concurrency, port, self.path))
def run(self):
# Parse output of ab, setting attributes on self
try:
self.output = _cpmodpy.read_process(AB_PATH or "ab", self.args())
except:
print(_cperror.format_exc())
raise
for attr, name, pattern in self.parse_patterns:
val = re.search(pattern, self.output, re.MULTILINE)
if val:
val = val.group(1)
setattr(self, attr, val)
else:
setattr(self, attr, None)
safe_threads = (25, 50, 100, 200, 400)
if sys.platform in ("win32",):
# For some reason, ab crashes with > 50 threads on my Win2k laptop.
safe_threads = (10, 20, 30, 40, 50)
def thread_report(path=SCRIPT_NAME + "/hello", concurrency=safe_threads):
sess = ABSession(path)
attrs, names, patterns = list(zip(*sess.parse_patterns))
avg = dict.fromkeys(attrs, 0.0)
yield ('threads',) + names
for c in concurrency:
sess.concurrency = c
sess.run()
row = [c]
for attr in attrs:
val = getattr(sess, attr)
if val is None:
print(sess.output)
row = None
break
val = float(val)
avg[attr] += float(val)
row.append(val)
if row:
yield row
# Add a row of averages.
yield ["Average"] + [str(avg[attr] / len(concurrency)) for attr in attrs]
def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000),
concurrency=50):
sess = ABSession(concurrency=concurrency)
attrs, names, patterns = list(zip(*sess.parse_patterns))
yield ('bytes',) + names
for sz in sizes:
sess.path = "%s/sizer?size=%s" % (SCRIPT_NAME, sz)
sess.run()
yield [sz] + [getattr(sess, attr) for attr in attrs]
def print_report(rows):
for row in rows:
print("")
for i, val in enumerate(row):
sys.stdout.write(str(val).rjust(10) + " | ")
print("")
def run_standard_benchmarks():
print("")
print("Client Thread Report (1000 requests, 14 byte response body, "
"%s server threads):" % cherrypy.server.thread_pool)
print_report(thread_report())
print("")
print("Client Thread Report (1000 requests, 14 bytes via staticdir, "
"%s server threads):" % cherrypy.server.thread_pool)
print_report(thread_report("%s/static/index.html" % SCRIPT_NAME))
print("")
print("Size Report (1000 requests, 50 client threads, "
"%s server threads):" % cherrypy.server.thread_pool)
print_report(size_report())
# modpython and other WSGI #
def startup_modpython(req=None):
"""Start the CherryPy app server in 'serverless' mode (for modpython/WSGI)."""
if cherrypy.engine.state == cherrypy._cpengine.STOPPED:
if req:
if "nullreq" in req.get_options():
cherrypy.engine.request_class = NullRequest
cherrypy.engine.response_class = NullResponse
ab_opt = req.get_options().get("ab", "")
if ab_opt:
global AB_PATH
AB_PATH = ab_opt
cherrypy.engine.start()
if cherrypy.engine.state == cherrypy._cpengine.STARTING:
cherrypy.engine.wait()
return 0 # apache.OK
def run_modpython(use_wsgi=False):
print("Starting mod_python...")
pyopts = []
# Pass the null and ab=path options through Apache
if "--null" in opts:
pyopts.append(("nullreq", ""))
if "--ab" in opts:
pyopts.append(("ab", opts["--ab"]))
s = _cpmodpy.ModPythonServer
if use_wsgi:
pyopts.append(("wsgi.application", "cherrypy::tree"))
pyopts.append(("wsgi.startup", "cherrypy.test.benchmark::startup_modpython"))
handler = "modpython_gateway::handler"
s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH, handler=handler)
else:
pyopts.append(("cherrypy.setup", "cherrypy.test.benchmark::startup_modpython"))
s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH)
try:
s.start()
run()
finally:
s.stop()
if __name__ == '__main__':
longopts = ['cpmodpy', 'modpython', 'null', 'notests',
'help', 'ab=', 'apache=']
try:
switches, args = getopt.getopt(sys.argv[1:], "", longopts)
opts = dict(switches)
except getopt.GetoptError:
print(__doc__)
sys.exit(2)
if "--help" in opts:
print(__doc__)
sys.exit(0)
if "--ab" in opts:
AB_PATH = opts['--ab']
if "--notests" in opts:
# Return without stopping the server, so that the pages
# can be tested from a standard web browser.
def run():
port = cherrypy.server.socket_port
print("You may now open http://127.0.0.1:%s%s/" %
(port, SCRIPT_NAME))
if "--null" in opts:
print("Using null Request object")
else:
def run():
end = time.time() - start
print("Started in %s seconds" % end)
if "--null" in opts:
print("\nUsing null Request object")
try:
try:
run_standard_benchmarks()
except:
print(_cperror.format_exc())
raise
finally:
cherrypy.engine.exit()
print("Starting CherryPy app server...")
class NullWriter(object):
"""Suppresses the printing of socket errors."""
def write(self, data):
pass
sys.stderr = NullWriter()
start = time.time()
if "--cpmodpy" in opts:
run_modpython()
elif "--modpython" in opts:
run_modpython(use_wsgi=True)
else:
if "--null" in opts:
cherrypy.server.request_class = NullRequest
cherrypy.server.response_class = NullResponse
cherrypy.engine.start_with_callback(run)
cherrypy.engine.block()

View File

@ -1,47 +0,0 @@
"""Demonstration app for cherrypy.checker.
This application is intentionally broken and badly designed.
To demonstrate the output of the CherryPy Checker, simply execute
this module.
"""
import os
import cherrypy
thisdir = os.path.dirname(os.path.abspath(__file__))
class Root:
pass
if __name__ == '__main__':
conf = {'/base': {'tools.staticdir.root': thisdir,
# Obsolete key.
'throw_errors': True,
},
# This entry should be OK.
'/base/static': {'tools.staticdir.on': True,
'tools.staticdir.dir': 'static'},
# Warn on missing folder.
'/base/js': {'tools.staticdir.on': True,
'tools.staticdir.dir': 'js'},
# Warn on dir with an abs path even though we provide root.
'/base/static2': {'tools.staticdir.on': True,
'tools.staticdir.dir': '/static'},
# Warn on dir with a relative path with no root.
'/static3': {'tools.staticdir.on': True,
'tools.staticdir.dir': 'static'},
# Warn on unknown namespace
'/unknown': {'toobles.gzip.on': True},
# Warn special on cherrypy.<known ns>.*
'/cpknown': {'cherrypy.tools.encode.on': True},
# Warn on mismatched types
'/conftype': {'request.show_tracebacks': 14},
# Warn on unknown tool.
'/web': {'tools.unknown.on': True},
# Warn on server.* in app config.
'/app1': {'server.socket_host': '0.0.0.0'},
# Warn on 'localhost'
'global': {'server.socket_host': 'localhost'},
# Warn on '[name]'
'[/extra_brackets]': {},
}
cherrypy.quickstart(Root(), config=conf)

View File

@ -1,494 +0,0 @@
"""A library of helper functions for the CherryPy test suite."""
import datetime
import logging
log = logging.getLogger(__name__)
import os
thisdir = os.path.abspath(os.path.dirname(__file__))
serverpem = os.path.join(os.getcwd(), thisdir, 'test.pem')
import re
import sys
import time
import warnings
import cherrypy
from cherrypy._cpcompat import basestring, copyitems, HTTPSConnection, ntob
from cherrypy.lib import httputil
from cherrypy.lib import gctools
from cherrypy.lib.reprconf import unrepr
from cherrypy.test import webtest
import nose
_testconfig = None
def get_tst_config(overconf = {}):
global _testconfig
if _testconfig is None:
conf = {
'scheme': 'http',
'protocol': "HTTP/1.1",
'port': 54583,
'host': '127.0.0.1',
'validate': False,
'conquer': False,
'server': 'wsgi',
}
try:
import testconfig
_conf = testconfig.config.get('supervisor', None)
if _conf is not None:
for k, v in _conf.items():
if isinstance(v, basestring):
_conf[k] = unrepr(v)
conf.update(_conf)
except ImportError:
pass
_testconfig = conf
conf = _testconfig.copy()
conf.update(overconf)
return conf
class Supervisor(object):
"""Base class for modeling and controlling servers during testing."""
def __init__(self, **kwargs):
for k, v in kwargs.items():
if k == 'port':
setattr(self, k, int(v))
setattr(self, k, v)
log_to_stderr = lambda msg, level: sys.stderr.write(msg + os.linesep)
class LocalSupervisor(Supervisor):
"""Base class for modeling/controlling servers which run in the same process.
When the server side runs in a different process, start/stop can dump all
state between each test module easily. When the server side runs in the
same process as the client, however, we have to do a bit more work to ensure
config and mounted apps are reset between tests.
"""
using_apache = False
using_wsgi = False
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
cherrypy.server.httpserver = self.httpserver_class
# This is perhaps the wrong place for this call but this is the only
# place that i've found so far that I KNOW is early enough to set this.
cherrypy.config.update({'log.screen': False})
engine = cherrypy.engine
if hasattr(engine, "signal_handler"):
engine.signal_handler.subscribe()
if hasattr(engine, "console_control_handler"):
engine.console_control_handler.subscribe()
#engine.subscribe('log', log_to_stderr)
def start(self, modulename=None):
"""Load and start the HTTP server."""
if modulename:
# Unhook httpserver so cherrypy.server.start() creates a new
# one (with config from setup_server, if declared).
cherrypy.server.httpserver = None
cherrypy.engine.start()
self.sync_apps()
def sync_apps(self):
"""Tell the server about any apps which the setup functions mounted."""
pass
def stop(self):
td = getattr(self, 'teardown', None)
if td:
td()
cherrypy.engine.exit()
for name, server in copyitems(getattr(cherrypy, 'servers', {})):
server.unsubscribe()
del cherrypy.servers[name]
class NativeServerSupervisor(LocalSupervisor):
"""Server supervisor for the builtin HTTP server."""
httpserver_class = "cherrypy._cpnative_server.CPHTTPServer"
using_apache = False
using_wsgi = False
def __str__(self):
return "Builtin HTTP Server on %s:%s" % (self.host, self.port)
class LocalWSGISupervisor(LocalSupervisor):
"""Server supervisor for the builtin WSGI server."""
httpserver_class = "cherrypy._cpwsgi_server.CPWSGIServer"
using_apache = False
using_wsgi = True
def __str__(self):
return "Builtin WSGI Server on %s:%s" % (self.host, self.port)
def sync_apps(self):
"""Hook a new WSGI app into the origin server."""
cherrypy.server.httpserver.wsgi_app = self.get_app()
def get_app(self, app=None):
"""Obtain a new (decorated) WSGI app to hook into the origin server."""
if app is None:
app = cherrypy.tree
if self.conquer:
try:
import wsgiconq
except ImportError:
warnings.warn("Error importing wsgiconq. pyconquer will not run.")
else:
app = wsgiconq.WSGILogger(app, c_calls=True)
if self.validate:
try:
from wsgiref import validate
except ImportError:
warnings.warn("Error importing wsgiref. The validator will not run.")
else:
#wraps the app in the validator
app = validate.validator(app)
return app
def get_cpmodpy_supervisor(**options):
from cherrypy.test import modpy
sup = modpy.ModPythonSupervisor(**options)
sup.template = modpy.conf_cpmodpy
return sup
def get_modpygw_supervisor(**options):
from cherrypy.test import modpy
sup = modpy.ModPythonSupervisor(**options)
sup.template = modpy.conf_modpython_gateway
sup.using_wsgi = True
return sup
def get_modwsgi_supervisor(**options):
from cherrypy.test import modwsgi
return modwsgi.ModWSGISupervisor(**options)
def get_modfcgid_supervisor(**options):
from cherrypy.test import modfcgid
return modfcgid.ModFCGISupervisor(**options)
def get_modfastcgi_supervisor(**options):
from cherrypy.test import modfastcgi
return modfastcgi.ModFCGISupervisor(**options)
def get_wsgi_u_supervisor(**options):
cherrypy.server.wsgi_version = ('u', 0)
return LocalWSGISupervisor(**options)
class CPWebCase(webtest.WebCase):
script_name = ""
scheme = "http"
available_servers = {'wsgi': LocalWSGISupervisor,
'wsgi_u': get_wsgi_u_supervisor,
'native': NativeServerSupervisor,
'cpmodpy': get_cpmodpy_supervisor,
'modpygw': get_modpygw_supervisor,
'modwsgi': get_modwsgi_supervisor,
'modfcgid': get_modfcgid_supervisor,
'modfastcgi': get_modfastcgi_supervisor,
}
default_server = "wsgi"
def _setup_server(cls, supervisor, conf):
v = sys.version.split()[0]
log.info("Python version used to run this test script: %s" % v)
log.info("CherryPy version: %s" % cherrypy.__version__)
if supervisor.scheme == "https":
ssl = " (ssl)"
else:
ssl = ""
log.info("HTTP server version: %s%s" % (supervisor.protocol, ssl))
log.info("PID: %s" % os.getpid())
cherrypy.server.using_apache = supervisor.using_apache
cherrypy.server.using_wsgi = supervisor.using_wsgi
if sys.platform[:4] == 'java':
cherrypy.config.update({'server.nodelay': False})
if isinstance(conf, basestring):
parser = cherrypy.lib.reprconf.Parser()
conf = parser.dict_from_file(conf).get('global', {})
else:
conf = conf or {}
baseconf = conf.copy()
baseconf.update({'server.socket_host': supervisor.host,
'server.socket_port': supervisor.port,
'server.protocol_version': supervisor.protocol,
'environment': "test_suite",
})
if supervisor.scheme == "https":
#baseconf['server.ssl_module'] = 'builtin'
baseconf['server.ssl_certificate'] = serverpem
baseconf['server.ssl_private_key'] = serverpem
# helper must be imported lazily so the coverage tool
# can run against module-level statements within cherrypy.
# Also, we have to do "from cherrypy.test import helper",
# exactly like each test module does, because a relative import
# would stick a second instance of webtest in sys.modules,
# and we wouldn't be able to globally override the port anymore.
if supervisor.scheme == "https":
webtest.WebCase.HTTP_CONN = HTTPSConnection
return baseconf
_setup_server = classmethod(_setup_server)
def setup_class(cls):
''
#Creates a server
conf = get_tst_config()
supervisor_factory = cls.available_servers.get(conf.get('server', 'wsgi'))
if supervisor_factory is None:
raise RuntimeError('Unknown server in config: %s' % conf['server'])
supervisor = supervisor_factory(**conf)
#Copied from "run_test_suite"
cherrypy.config.reset()
baseconf = cls._setup_server(supervisor, conf)
cherrypy.config.update(baseconf)
setup_client()
if hasattr(cls, 'setup_server'):
# Clear the cherrypy tree and clear the wsgi server so that
# it can be updated with the new root
cherrypy.tree = cherrypy._cptree.Tree()
cherrypy.server.httpserver = None
cls.setup_server()
# Add a resource for verifying there are no refleaks
# to *every* test class.
cherrypy.tree.mount(gctools.GCRoot(), '/gc')
cls.do_gc_test = True
supervisor.start(cls.__module__)
cls.supervisor = supervisor
setup_class = classmethod(setup_class)
def teardown_class(cls):
''
if hasattr(cls, 'setup_server'):
cls.supervisor.stop()
teardown_class = classmethod(teardown_class)
do_gc_test = False
def test_gc(self):
if self.do_gc_test:
self.getPage("/gc/stats")
self.assertBody("Statistics:")
# Tell nose to run this last in each class.
# Prefer sys.maxint for Python 2.3, which didn't have float('inf')
test_gc.compat_co_firstlineno = getattr(sys, 'maxint', None) or float('inf')
def prefix(self):
return self.script_name.rstrip("/")
def base(self):
if ((self.scheme == "http" and self.PORT == 80) or
(self.scheme == "https" and self.PORT == 443)):
port = ""
else:
port = ":%s" % self.PORT
return "%s://%s%s%s" % (self.scheme, self.HOST, port,
self.script_name.rstrip("/"))
def exit(self):
sys.exit()
def getPage(self, url, headers=None, method="GET", body=None, protocol=None):
"""Open the url. Return status, headers, body."""
if self.script_name:
url = httputil.urljoin(self.script_name, url)
return webtest.WebCase.getPage(self, url, headers, method, body, protocol)
def skip(self, msg='skipped '):
raise nose.SkipTest(msg)
def assertErrorPage(self, status, message=None, pattern=''):
"""Compare the response body with a built in error page.
The function will optionally look for the regexp pattern,
within the exception embedded in the error page."""
# This will never contain a traceback
page = cherrypy._cperror.get_error_page(status, message=message)
# First, test the response body without checking the traceback.
# Stick a match-all group (.*) in to grab the traceback.
esc = re.escape
epage = esc(page)
epage = epage.replace(esc('<pre id="traceback"></pre>'),
esc('<pre id="traceback">') + '(.*)' + esc('</pre>'))
m = re.match(ntob(epage, self.encoding), self.body, re.DOTALL)
if not m:
self._handlewebError('Error page does not match; expected:\n' + page)
return
# Now test the pattern against the traceback
if pattern is None:
# Special-case None to mean that there should be *no* traceback.
if m and m.group(1):
self._handlewebError('Error page contains traceback')
else:
if (m is None) or (
not re.search(ntob(re.escape(pattern), self.encoding),
m.group(1))):
msg = 'Error page does not contain %s in traceback'
self._handlewebError(msg % repr(pattern))
date_tolerance = 2
def assertEqualDates(self, dt1, dt2, seconds=None):
"""Assert abs(dt1 - dt2) is within Y seconds."""
if seconds is None:
seconds = self.date_tolerance
if dt1 > dt2:
diff = dt1 - dt2
else:
diff = dt2 - dt1
if not diff < datetime.timedelta(seconds=seconds):
raise AssertionError('%r and %r are not within %r seconds.' %
(dt1, dt2, seconds))
def setup_client():
"""Set up the WebCase classes to match the server's socket settings."""
webtest.WebCase.PORT = cherrypy.server.socket_port
webtest.WebCase.HOST = cherrypy.server.socket_host
if cherrypy.server.ssl_certificate:
CPWebCase.scheme = 'https'
# --------------------------- Spawning helpers --------------------------- #
class CPProcess(object):
pid_file = os.path.join(thisdir, 'test.pid')
config_file = os.path.join(thisdir, 'test.conf')
config_template = """[global]
server.socket_host: '%(host)s'
server.socket_port: %(port)s
checker.on: False
log.screen: False
log.error_file: r'%(error_log)s'
log.access_file: r'%(access_log)s'
%(ssl)s
%(extra)s
"""
error_log = os.path.join(thisdir, 'test.error.log')
access_log = os.path.join(thisdir, 'test.access.log')
def __init__(self, wait=False, daemonize=False, ssl=False, socket_host=None, socket_port=None):
self.wait = wait
self.daemonize = daemonize
self.ssl = ssl
self.host = socket_host or cherrypy.server.socket_host
self.port = socket_port or cherrypy.server.socket_port
def write_conf(self, extra=""):
if self.ssl:
serverpem = os.path.join(thisdir, 'test.pem')
ssl = """
server.ssl_certificate: r'%s'
server.ssl_private_key: r'%s'
""" % (serverpem, serverpem)
else:
ssl = ""
conf = self.config_template % {
'host': self.host,
'port': self.port,
'error_log': self.error_log,
'access_log': self.access_log,
'ssl': ssl,
'extra': extra,
}
f = open(self.config_file, 'wb')
f.write(ntob(conf, 'utf-8'))
f.close()
def start(self, imports=None):
"""Start cherryd in a subprocess."""
cherrypy._cpserver.wait_for_free_port(self.host, self.port)
args = [sys.executable, os.path.join(thisdir, '..', 'cherryd'),
'-c', self.config_file, '-p', self.pid_file]
if not isinstance(imports, (list, tuple)):
imports = [imports]
for i in imports:
if i:
args.append('-i')
args.append(i)
if self.daemonize:
args.append('-d')
env = os.environ.copy()
# Make sure we import the cherrypy package in which this module is defined.
grandparentdir = os.path.abspath(os.path.join(thisdir, '..', '..'))
if env.get('PYTHONPATH', ''):
env['PYTHONPATH'] = os.pathsep.join((grandparentdir, env['PYTHONPATH']))
else:
env['PYTHONPATH'] = grandparentdir
if self.wait:
self.exit_code = os.spawnve(os.P_WAIT, sys.executable, args, env)
else:
os.spawnve(os.P_NOWAIT, sys.executable, args, env)
cherrypy._cpserver.wait_for_occupied_port(self.host, self.port)
# Give the engine a wee bit more time to finish STARTING
if self.daemonize:
time.sleep(2)
else:
time.sleep(1)
def get_pid(self):
return int(open(self.pid_file, 'rb').read())
def join(self):
"""Wait for the process to exit."""
try:
try:
# Mac, UNIX
os.wait()
except AttributeError:
# Windows
try:
pid = self.get_pid()
except IOError:
# Assume the subprocess deleted the pidfile on shutdown.
pass
else:
os.waitpid(pid, 0)
except OSError:
x = sys.exc_info()[1]
if x.args != (10, 'No child processes'):
raise

View File

@ -1,188 +0,0 @@
"""logtest, a unittest.TestCase helper for testing log output."""
import sys
import time
import cherrypy
from cherrypy._cpcompat import basestring, ntob, unicodestr
try:
# On Windows, msvcrt.getch reads a single char without output.
import msvcrt
def getchar():
return msvcrt.getch()
except ImportError:
# Unix getchr
import tty, termios
def getchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class LogCase(object):
"""unittest.TestCase mixin for testing log messages.
logfile: a filename for the desired log. Yes, I know modes are evil,
but it makes the test functions so much cleaner to set this once.
lastmarker: the last marker in the log. This can be used to search for
messages since the last marker.
markerPrefix: a string with which to prefix log markers. This should be
unique enough from normal log output to use for marker identification.
"""
logfile = None
lastmarker = None
markerPrefix = ntob("test suite marker: ")
def _handleLogError(self, msg, data, marker, pattern):
print("")
print(" ERROR: %s" % msg)
if not self.interactive:
raise self.failureException(msg)
p = " Show: [L]og [M]arker [P]attern; [I]gnore, [R]aise, or sys.e[X]it >> "
sys.stdout.write(p + ' ')
# ARGH
sys.stdout.flush()
while True:
i = getchar().upper()
if i not in "MPLIRX":
continue
print(i.upper()) # Also prints new line
if i == "L":
for x, line in enumerate(data):
if (x + 1) % self.console_height == 0:
# The \r and comma should make the next line overwrite
sys.stdout.write("<-- More -->\r ")
m = getchar().lower()
# Erase our "More" prompt
sys.stdout.write(" \r ")
if m == "q":
break
print(line.rstrip())
elif i == "M":
print(repr(marker or self.lastmarker))
elif i == "P":
print(repr(pattern))
elif i == "I":
# return without raising the normal exception
return
elif i == "R":
raise self.failureException(msg)
elif i == "X":
self.exit()
sys.stdout.write(p + ' ')
def exit(self):
sys.exit()
def emptyLog(self):
"""Overwrite self.logfile with 0 bytes."""
open(self.logfile, 'wb').write("")
def markLog(self, key=None):
"""Insert a marker line into the log and set self.lastmarker."""
if key is None:
key = str(time.time())
self.lastmarker = key
open(self.logfile, 'ab+').write(ntob("%s%s\n" % (self.markerPrefix, key),"utf-8"))
def _read_marked_region(self, marker=None):
"""Return lines from self.logfile in the marked region.
If marker is None, self.lastmarker is used. If the log hasn't
been marked (using self.markLog), the entire log will be returned.
"""
## # Give the logger time to finish writing?
## time.sleep(0.5)
logfile = self.logfile
marker = marker or self.lastmarker
if marker is None:
return open(logfile, 'rb').readlines()
if isinstance(marker, unicodestr):
marker = marker.encode('utf-8')
data = []
in_region = False
for line in open(logfile, 'rb'):
if in_region:
if (line.startswith(self.markerPrefix) and not marker in line):
break
else:
data.append(line)
elif marker in line:
in_region = True
return data
def assertInLog(self, line, marker=None):
"""Fail if the given (partial) line is not in the log.
The log will be searched from the given marker to the next marker.
If marker is None, self.lastmarker is used. If the log hasn't
been marked (using self.markLog), the entire log will be searched.
"""
data = self._read_marked_region(marker)
for logline in data:
if line in logline:
return
msg = "%r not found in log" % line
self._handleLogError(msg, data, marker, line)
def assertNotInLog(self, line, marker=None):
"""Fail if the given (partial) line is in the log.
The log will be searched from the given marker to the next marker.
If marker is None, self.lastmarker is used. If the log hasn't
been marked (using self.markLog), the entire log will be searched.
"""
data = self._read_marked_region(marker)
for logline in data:
if line in logline:
msg = "%r found in log" % line
self._handleLogError(msg, data, marker, line)
def assertLog(self, sliceargs, lines, marker=None):
"""Fail if log.readlines()[sliceargs] is not contained in 'lines'.
The log will be searched from the given marker to the next marker.
If marker is None, self.lastmarker is used. If the log hasn't
been marked (using self.markLog), the entire log will be searched.
"""
data = self._read_marked_region(marker)
if isinstance(sliceargs, int):
# Single arg. Use __getitem__ and allow lines to be str or list.
if isinstance(lines, (tuple, list)):
lines = lines[0]
if isinstance(lines, unicodestr):
lines = lines.encode('utf-8')
if lines not in data[sliceargs]:
msg = "%r not found on log line %r" % (lines, sliceargs)
self._handleLogError(msg, [data[sliceargs],"--EXTRA CONTEXT--"] + data[sliceargs+1:sliceargs+6], marker, lines)
else:
# Multiple args. Use __getslice__ and require lines to be list.
if isinstance(lines, tuple):
lines = list(lines)
elif isinstance(lines, basestring):
raise TypeError("The 'lines' arg must be a list when "
"'sliceargs' is a tuple.")
start, stop = sliceargs
for line, logline in zip(lines, data[start:stop]):
if isinstance(line, unicodestr):
line = line.encode('utf-8')
if line not in logline:
msg = "%r not found in log" % line
self._handleLogError(msg, data[start:stop], marker, line)

View File

@ -1,135 +0,0 @@
"""Wrapper for mod_fastcgi, for use as a CherryPy HTTP server when testing.
To autostart fastcgi, the "apache" executable or script must be
on your system path, or you must override the global APACHE_PATH.
On some platforms, "apache" may be called "apachectl", "apache2ctl",
or "httpd"--create a symlink to them if needed.
You'll also need the WSGIServer from flup.servers.
See http://projects.amor.org/misc/wiki/ModPythonGateway
KNOWN BUGS
==========
1. Apache processes Range headers automatically; CherryPy's truncated
output is then truncated again by Apache. See test_core.testRanges.
This was worked around in http://www.cherrypy.org/changeset/1319.
2. Apache does not allow custom HTTP methods like CONNECT as per the spec.
See test_core.testHTTPMethods.
3. Max request header and body settings do not work with Apache.
4. Apache replaces status "reason phrases" automatically. For example,
CherryPy may set "304 Not modified" but Apache will write out
"304 Not Modified" (capital "M").
5. Apache does not allow custom error codes as per the spec.
6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the
Request-URI too early.
7. mod_python will not read request bodies which use the "chunked"
transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block
instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and
mod_python's requestobject.c).
8. Apache will output a "Content-Length: 0" response header even if there's
no response entity body. This isn't really a bug; it just differs from
the CherryPy default.
"""
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import re
import sys
import time
import cherrypy
from cherrypy.process import plugins, servers
from cherrypy.test import helper
def read_process(cmd, args=""):
pipein, pipeout = os.popen4("%s %s" % (cmd, args))
try:
firstline = pipeout.readline()
if (re.search(r"(not recognized|No such file|not found)", firstline,
re.IGNORECASE)):
raise IOError('%s must be on your system path.' % cmd)
output = firstline + pipeout.read()
finally:
pipeout.close()
return output
APACHE_PATH = "apache2ctl"
CONF_PATH = "fastcgi.conf"
conf_fastcgi = """
# Apache2 server conf file for testing CherryPy with mod_fastcgi.
# fumanchu: I had to hard-code paths due to crazy Debian layouts :(
ServerRoot /usr/lib/apache2
User #1000
ErrorLog %(root)s/mod_fastcgi.error.log
DocumentRoot "%(root)s"
ServerName 127.0.0.1
Listen %(port)s
LoadModule fastcgi_module modules/mod_fastcgi.so
LoadModule rewrite_module modules/mod_rewrite.so
Options +ExecCGI
SetHandler fastcgi-script
RewriteEngine On
RewriteRule ^(.*)$ /fastcgi.pyc [L]
FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000
"""
def erase_script_name(environ, start_response):
environ['SCRIPT_NAME'] = ''
return cherrypy.tree(environ, start_response)
class ModFCGISupervisor(helper.LocalWSGISupervisor):
httpserver_class = "cherrypy.process.servers.FlupFCGIServer"
using_apache = True
using_wsgi = True
template = conf_fastcgi
def __str__(self):
return "FCGI Server on %s:%s" % (self.host, self.port)
def start(self, modulename):
cherrypy.server.httpserver = servers.FlupFCGIServer(
application=erase_script_name, bindAddress=('127.0.0.1', 4000))
cherrypy.server.httpserver.bind_addr = ('127.0.0.1', 4000)
cherrypy.server.socket_port = 4000
# For FCGI, we both start apache...
self.start_apache()
# ...and our local server
cherrypy.engine.start()
self.sync_apps()
def start_apache(self):
fcgiconf = CONF_PATH
if not os.path.isabs(fcgiconf):
fcgiconf = os.path.join(curdir, fcgiconf)
# Write the Apache conf file.
f = open(fcgiconf, 'wb')
try:
server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1]
output = self.template % {'port': self.port, 'root': curdir,
'server': server}
output = output.replace('\r\n', '\n')
f.write(output)
finally:
f.close()
result = read_process(APACHE_PATH, "-k start -f %s" % fcgiconf)
if result:
print(result)
def stop(self):
"""Gracefully shutdown a server that is serving forever."""
read_process(APACHE_PATH, "-k stop")
helper.LocalWSGISupervisor.stop(self)
def sync_apps(self):
cherrypy.server.httpserver.fcgiserver.application = self.get_app(erase_script_name)

View File

@ -1,125 +0,0 @@
"""Wrapper for mod_fcgid, for use as a CherryPy HTTP server when testing.
To autostart fcgid, the "apache" executable or script must be
on your system path, or you must override the global APACHE_PATH.
On some platforms, "apache" may be called "apachectl", "apache2ctl",
or "httpd"--create a symlink to them if needed.
You'll also need the WSGIServer from flup.servers.
See http://projects.amor.org/misc/wiki/ModPythonGateway
KNOWN BUGS
==========
1. Apache processes Range headers automatically; CherryPy's truncated
output is then truncated again by Apache. See test_core.testRanges.
This was worked around in http://www.cherrypy.org/changeset/1319.
2. Apache does not allow custom HTTP methods like CONNECT as per the spec.
See test_core.testHTTPMethods.
3. Max request header and body settings do not work with Apache.
4. Apache replaces status "reason phrases" automatically. For example,
CherryPy may set "304 Not modified" but Apache will write out
"304 Not Modified" (capital "M").
5. Apache does not allow custom error codes as per the spec.
6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the
Request-URI too early.
7. mod_python will not read request bodies which use the "chunked"
transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block
instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and
mod_python's requestobject.c).
8. Apache will output a "Content-Length: 0" response header even if there's
no response entity body. This isn't really a bug; it just differs from
the CherryPy default.
"""
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import re
import sys
import time
import cherrypy
from cherrypy._cpcompat import ntob
from cherrypy.process import plugins, servers
from cherrypy.test import helper
def read_process(cmd, args=""):
pipein, pipeout = os.popen4("%s %s" % (cmd, args))
try:
firstline = pipeout.readline()
if (re.search(r"(not recognized|No such file|not found)", firstline,
re.IGNORECASE)):
raise IOError('%s must be on your system path.' % cmd)
output = firstline + pipeout.read()
finally:
pipeout.close()
return output
APACHE_PATH = "httpd"
CONF_PATH = "fcgi.conf"
conf_fcgid = """
# Apache2 server conf file for testing CherryPy with mod_fcgid.
DocumentRoot "%(root)s"
ServerName 127.0.0.1
Listen %(port)s
LoadModule fastcgi_module modules/mod_fastcgi.dll
LoadModule rewrite_module modules/mod_rewrite.so
Options ExecCGI
SetHandler fastcgi-script
RewriteEngine On
RewriteRule ^(.*)$ /fastcgi.pyc [L]
FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000
"""
class ModFCGISupervisor(helper.LocalSupervisor):
using_apache = True
using_wsgi = True
template = conf_fcgid
def __str__(self):
return "FCGI Server on %s:%s" % (self.host, self.port)
def start(self, modulename):
cherrypy.server.httpserver = servers.FlupFCGIServer(
application=cherrypy.tree, bindAddress=('127.0.0.1', 4000))
cherrypy.server.httpserver.bind_addr = ('127.0.0.1', 4000)
# For FCGI, we both start apache...
self.start_apache()
# ...and our local server
helper.LocalServer.start(self, modulename)
def start_apache(self):
fcgiconf = CONF_PATH
if not os.path.isabs(fcgiconf):
fcgiconf = os.path.join(curdir, fcgiconf)
# Write the Apache conf file.
f = open(fcgiconf, 'wb')
try:
server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1]
output = self.template % {'port': self.port, 'root': curdir,
'server': server}
output = ntob(output.replace('\r\n', '\n'))
f.write(output)
finally:
f.close()
result = read_process(APACHE_PATH, "-k start -f %s" % fcgiconf)
if result:
print(result)
def stop(self):
"""Gracefully shutdown a server that is serving forever."""
read_process(APACHE_PATH, "-k stop")
helper.LocalServer.stop(self)
def sync_apps(self):
cherrypy.server.httpserver.fcgiserver.application = self.get_app()

View File

@ -1,163 +0,0 @@
"""Wrapper for mod_python, for use as a CherryPy HTTP server when testing.
To autostart modpython, the "apache" executable or script must be
on your system path, or you must override the global APACHE_PATH.
On some platforms, "apache" may be called "apachectl" or "apache2ctl"--
create a symlink to them if needed.
If you wish to test the WSGI interface instead of our _cpmodpy interface,
you also need the 'modpython_gateway' module at:
http://projects.amor.org/misc/wiki/ModPythonGateway
KNOWN BUGS
==========
1. Apache processes Range headers automatically; CherryPy's truncated
output is then truncated again by Apache. See test_core.testRanges.
This was worked around in http://www.cherrypy.org/changeset/1319.
2. Apache does not allow custom HTTP methods like CONNECT as per the spec.
See test_core.testHTTPMethods.
3. Max request header and body settings do not work with Apache.
4. Apache replaces status "reason phrases" automatically. For example,
CherryPy may set "304 Not modified" but Apache will write out
"304 Not Modified" (capital "M").
5. Apache does not allow custom error codes as per the spec.
6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the
Request-URI too early.
7. mod_python will not read request bodies which use the "chunked"
transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block
instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and
mod_python's requestobject.c).
8. Apache will output a "Content-Length: 0" response header even if there's
no response entity body. This isn't really a bug; it just differs from
the CherryPy default.
"""
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import re
import time
from cherrypy.test import helper
def read_process(cmd, args=""):
pipein, pipeout = os.popen4("%s %s" % (cmd, args))
try:
firstline = pipeout.readline()
if (re.search(r"(not recognized|No such file|not found)", firstline,
re.IGNORECASE)):
raise IOError('%s must be on your system path.' % cmd)
output = firstline + pipeout.read()
finally:
pipeout.close()
return output
APACHE_PATH = "httpd"
CONF_PATH = "test_mp.conf"
conf_modpython_gateway = """
# Apache2 server conf file for testing CherryPy with modpython_gateway.
ServerName 127.0.0.1
DocumentRoot "/"
Listen %(port)s
LoadModule python_module modules/mod_python.so
SetHandler python-program
PythonFixupHandler cherrypy.test.modpy::wsgisetup
PythonOption testmod %(modulename)s
PythonHandler modpython_gateway::handler
PythonOption wsgi.application cherrypy::tree
PythonOption socket_host %(host)s
PythonDebug On
"""
conf_cpmodpy = """
# Apache2 server conf file for testing CherryPy with _cpmodpy.
ServerName 127.0.0.1
DocumentRoot "/"
Listen %(port)s
LoadModule python_module modules/mod_python.so
SetHandler python-program
PythonFixupHandler cherrypy.test.modpy::cpmodpysetup
PythonHandler cherrypy._cpmodpy::handler
PythonOption cherrypy.setup cherrypy.test.%(modulename)s::setup_server
PythonOption socket_host %(host)s
PythonDebug On
"""
class ModPythonSupervisor(helper.Supervisor):
using_apache = True
using_wsgi = False
template = None
def __str__(self):
return "ModPython Server on %s:%s" % (self.host, self.port)
def start(self, modulename):
mpconf = CONF_PATH
if not os.path.isabs(mpconf):
mpconf = os.path.join(curdir, mpconf)
f = open(mpconf, 'wb')
try:
f.write(self.template %
{'port': self.port, 'modulename': modulename,
'host': self.host})
finally:
f.close()
result = read_process(APACHE_PATH, "-k start -f %s" % mpconf)
if result:
print(result)
def stop(self):
"""Gracefully shutdown a server that is serving forever."""
read_process(APACHE_PATH, "-k stop")
loaded = False
def wsgisetup(req):
global loaded
if not loaded:
loaded = True
options = req.get_options()
import cherrypy
cherrypy.config.update({
"log.error_file": os.path.join(curdir, "test.log"),
"environment": "test_suite",
"server.socket_host": options['socket_host'],
})
modname = options['testmod']
mod = __import__(modname, globals(), locals(), [''])
mod.setup_server()
cherrypy.server.unsubscribe()
cherrypy.engine.start()
from mod_python import apache
return apache.OK
def cpmodpysetup(req):
global loaded
if not loaded:
loaded = True
options = req.get_options()
import cherrypy
cherrypy.config.update({
"log.error_file": os.path.join(curdir, "test.log"),
"environment": "test_suite",
"server.socket_host": options['socket_host'],
})
from mod_python import apache
return apache.OK

View File

@ -1,148 +0,0 @@
"""Wrapper for mod_wsgi, for use as a CherryPy HTTP server.
To autostart modwsgi, the "apache" executable or script must be
on your system path, or you must override the global APACHE_PATH.
On some platforms, "apache" may be called "apachectl" or "apache2ctl"--
create a symlink to them if needed.
KNOWN BUGS
==========
##1. Apache processes Range headers automatically; CherryPy's truncated
## output is then truncated again by Apache. See test_core.testRanges.
## This was worked around in http://www.cherrypy.org/changeset/1319.
2. Apache does not allow custom HTTP methods like CONNECT as per the spec.
See test_core.testHTTPMethods.
3. Max request header and body settings do not work with Apache.
##4. Apache replaces status "reason phrases" automatically. For example,
## CherryPy may set "304 Not modified" but Apache will write out
## "304 Not Modified" (capital "M").
##5. Apache does not allow custom error codes as per the spec.
##6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the
## Request-URI too early.
7. mod_wsgi will not read request bodies which use the "chunked"
transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block
instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and
mod_python's requestobject.c).
8. When responding with 204 No Content, mod_wsgi adds a Content-Length
header for you.
9. When an error is raised, mod_wsgi has no facility for printing a
traceback as the response content (it's sent to the Apache log instead).
10. Startup and shutdown of Apache when running mod_wsgi seems slow.
"""
import os
curdir = os.path.abspath(os.path.dirname(__file__))
import re
import sys
import time
import cherrypy
from cherrypy.test import helper, webtest
def read_process(cmd, args=""):
pipein, pipeout = os.popen4("%s %s" % (cmd, args))
try:
firstline = pipeout.readline()
if (re.search(r"(not recognized|No such file|not found)", firstline,
re.IGNORECASE)):
raise IOError('%s must be on your system path.' % cmd)
output = firstline + pipeout.read()
finally:
pipeout.close()
return output
if sys.platform == 'win32':
APACHE_PATH = "httpd"
else:
APACHE_PATH = "apache"
CONF_PATH = "test_mw.conf"
conf_modwsgi = r"""
# Apache2 server conf file for testing CherryPy with modpython_gateway.
ServerName 127.0.0.1
DocumentRoot "/"
Listen %(port)s
AllowEncodedSlashes On
LoadModule rewrite_module modules/mod_rewrite.so
RewriteEngine on
RewriteMap escaping int:escape
LoadModule log_config_module modules/mod_log_config.so
LogFormat "%%h %%l %%u %%t \"%%r\" %%>s %%b \"%%{Referer}i\" \"%%{User-agent}i\"" combined
CustomLog "%(curdir)s/apache.access.log" combined
ErrorLog "%(curdir)s/apache.error.log"
LogLevel debug
LoadModule wsgi_module modules/mod_wsgi.so
LoadModule env_module modules/mod_env.so
WSGIScriptAlias / "%(curdir)s/modwsgi.py"
SetEnv testmod %(testmod)s
"""
class ModWSGISupervisor(helper.Supervisor):
"""Server Controller for ModWSGI and CherryPy."""
using_apache = True
using_wsgi = True
template=conf_modwsgi
def __str__(self):
return "ModWSGI Server on %s:%s" % (self.host, self.port)
def start(self, modulename):
mpconf = CONF_PATH
if not os.path.isabs(mpconf):
mpconf = os.path.join(curdir, mpconf)
f = open(mpconf, 'wb')
try:
output = (self.template %
{'port': self.port, 'testmod': modulename,
'curdir': curdir})
f.write(output)
finally:
f.close()
result = read_process(APACHE_PATH, "-k start -f %s" % mpconf)
if result:
print(result)
# Make a request so mod_wsgi starts up our app.
# If we don't, concurrent initial requests will 404.
cherrypy._cpserver.wait_for_occupied_port("127.0.0.1", self.port)
webtest.openURL('/ihopetheresnodefault', port=self.port)
time.sleep(1)
def stop(self):
"""Gracefully shutdown a server that is serving forever."""
read_process(APACHE_PATH, "-k stop")
loaded = False
def application(environ, start_response):
import cherrypy
global loaded
if not loaded:
loaded = True
modname = "cherrypy.test." + environ['testmod']
mod = __import__(modname, globals(), locals(), [''])
mod.setup_server()
cherrypy.config.update({
"log.error_file": os.path.join(curdir, "test.error.log"),
"log.access_file": os.path.join(curdir, "test.access.log"),
"environment": "test_suite",
"engine.SIGHUP": None,
"engine.SIGTERM": None,
})
return cherrypy.tree(environ, start_response)

View File

@ -1,153 +0,0 @@
#!/usr/bin/python
"""A session demonstration app."""
import calendar
from datetime import datetime
import sys
import cherrypy
from cherrypy.lib import sessions
from cherrypy._cpcompat import copyitems
page = """
<html>
<head>
<style type='text/css'>
table { border-collapse: collapse; border: 1px solid #663333; }
th { text-align: right; background-color: #663333; color: white; padding: 0.5em; }
td { white-space: pre-wrap; font-family: monospace; padding: 0.5em;
border: 1px solid #663333; }
.warn { font-family: serif; color: #990000; }
</style>
<script type="text/javascript">
<!--
function twodigit(d) { return d < 10 ? "0" + d : d; }
function formattime(t) {
var month = t.getUTCMonth() + 1;
var day = t.getUTCDate();
var year = t.getUTCFullYear();
var hours = t.getUTCHours();
var minutes = t.getUTCMinutes();
return (year + "/" + twodigit(month) + "/" + twodigit(day) + " " +
hours + ":" + twodigit(minutes) + " UTC");
}
function interval(s) {
// Return the given interval (in seconds) as an English phrase
var seconds = s %% 60;
s = Math.floor(s / 60);
var minutes = s %% 60;
s = Math.floor(s / 60);
var hours = s %% 24;
var v = twodigit(hours) + ":" + twodigit(minutes) + ":" + twodigit(seconds);
var days = Math.floor(s / 24);
if (days != 0) v = days + ' days, ' + v;
return v;
}
var fudge_seconds = 5;
function init() {
// Set the content of the 'btime' cell.
var currentTime = new Date();
var bunixtime = Math.floor(currentTime.getTime() / 1000);
var v = formattime(currentTime);
v += " (Unix time: " + bunixtime + ")";
var diff = Math.abs(%(serverunixtime)s - bunixtime);
if (diff > fudge_seconds) v += "<p class='warn'>Browser and Server times disagree.</p>";
document.getElementById('btime').innerHTML = v;
// Warn if response cookie expires is not close to one hour in the future.
// Yes, we want this to happen when wit hit the 'Expire' link, too.
var expires = Date.parse("%(expires)s") / 1000;
var onehour = (60 * 60);
if (Math.abs(expires - (bunixtime + onehour)) > fudge_seconds) {
diff = Math.floor(expires - bunixtime);
if (expires > (bunixtime + onehour)) {
var msg = "Response cookie 'expires' date is " + interval(diff) + " in the future.";
} else {
var msg = "Response cookie 'expires' date is " + interval(0 - diff) + " in the past.";
}
document.getElementById('respcookiewarn').innerHTML = msg;
}
}
//-->
</script>
</head>
<body onload='init()'>
<h2>Session Demo</h2>
<p>Reload this page. The session ID should not change from one reload to the next</p>
<p><a href='../'>Index</a> | <a href='expire'>Expire</a> | <a href='regen'>Regenerate</a></p>
<table>
<tr><th>Session ID:</th><td>%(sessionid)s<p class='warn'>%(changemsg)s</p></td></tr>
<tr><th>Request Cookie</th><td>%(reqcookie)s</td></tr>
<tr><th>Response Cookie</th><td>%(respcookie)s<p id='respcookiewarn' class='warn'></p></td></tr>
<tr><th>Session Data</th><td>%(sessiondata)s</td></tr>
<tr><th>Server Time</th><td id='stime'>%(servertime)s (Unix time: %(serverunixtime)s)</td></tr>
<tr><th>Browser Time</th><td id='btime'>&nbsp;</td></tr>
<tr><th>Cherrypy Version:</th><td>%(cpversion)s</td></tr>
<tr><th>Python Version:</th><td>%(pyversion)s</td></tr>
</table>
</body></html>
"""
class Root(object):
def page(self):
changemsg = []
if cherrypy.session.id != cherrypy.session.originalid:
if cherrypy.session.originalid is None:
changemsg.append('Created new session because no session id was given.')
if cherrypy.session.missing:
changemsg.append('Created new session due to missing (expired or malicious) session.')
if cherrypy.session.regenerated:
changemsg.append('Application generated a new session.')
try:
expires = cherrypy.response.cookie['session_id']['expires']
except KeyError:
expires = ''
return page % {
'sessionid': cherrypy.session.id,
'changemsg': '<br>'.join(changemsg),
'respcookie': cherrypy.response.cookie.output(),
'reqcookie': cherrypy.request.cookie.output(),
'sessiondata': copyitems(cherrypy.session),
'servertime': datetime.utcnow().strftime("%Y/%m/%d %H:%M") + " UTC",
'serverunixtime': calendar.timegm(datetime.utcnow().timetuple()),
'cpversion': cherrypy.__version__,
'pyversion': sys.version,
'expires': expires,
}
def index(self):
# Must modify data or the session will not be saved.
cherrypy.session['color'] = 'green'
return self.page()
index.exposed = True
def expire(self):
sessions.expire()
return self.page()
expire.exposed = True
def regen(self):
cherrypy.session.regenerate()
# Must modify data or the session will not be saved.
cherrypy.session['color'] = 'yellow'
return self.page()
regen.exposed = True
if __name__ == '__main__':
cherrypy.config.update({
#'environment': 'production',
'log.screen': True,
'tools.sessions.on': True,
})
cherrypy.quickstart(Root())

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1 +0,0 @@
Hello, world

View File

@ -1 +0,0 @@
Dummy stylesheet

View File

@ -1,38 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDBKo554mzIMY+AByUNpaUOP9bJnQ7ZLQe9XgHwoLJR4VzpyZZZ
R9L4WtImEew05FY3Izerfm3MN3+MC0tJ6yQU9sOiU3vBW6RrLIMlfKsnRwBRZ0Kn
da+O6xldVSosu8Ev3z9VZ94iC/ZgKzrH7Mjj/U8/MQO7RBS/LAqee8bFNQIDAQAB
AoGAWOCF0ZrWxn3XMucWq2LNwPKqlvVGwbIwX3cDmX22zmnM4Fy6arXbYh4XlyCj
9+ofqRrxIFz5k/7tFriTmZ0xag5+Jdx+Kwg0/twiP7XCNKipFogwe1Hznw8OFAoT
enKBdj2+/n2o0Bvo/tDB59m9L/538d46JGQUmJlzMyqYikECQQDyoq+8CtMNvE18
8VgHcR/KtApxWAjj4HpaHYL637ATjThetUZkW92mgDgowyplthusxdNqhHWyv7E8
tWNdYErZAkEAy85ShTR0M5aWmrE7o0r0SpWInAkNBH9aXQRRARFYsdBtNfRu6I0i
0lvU9wiu3eF57FMEC86yViZ5UBnQfTu7vQJAVesj/Zt7pwaCDfdMa740OsxMUlyR
MVhhGx4OLpYdPJ8qUecxGQKq13XZ7R1HGyNEY4bd2X80Smq08UFuATfC6QJAH8UB
yBHtKz2GLIcELOg6PIYizW/7v3+6rlVF60yw7sb2vzpjL40QqIn4IKoR2DSVtOkb
8FtAIX3N21aq0VrGYQJBAIPiaEc2AZ8Bq2GC4F3wOz/BxJ/izvnkiotR12QK4fh5
yjZMhTjWCas5zwHR5PDjlD88AWGDMsZ1PicD4348xJQ=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAy6gAwIBAgIJAI18BD7eQxlGMA0GCSqGSIb3DQEBBAUAMIGeMQswCQYD
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU2FuIERpZWdv
MRkwFwYDVQQKExBDaGVycnlQeSBQcm9qZWN0MREwDwYDVQQLEwhkZXYtdGVzdDEW
MBQGA1UEAxMNQ2hlcnJ5UHkgVGVhbTEgMB4GCSqGSIb3DQEJARYRcmVtaUBjaGVy
cnlweS5vcmcwHhcNMDYwOTA5MTkyMDIwWhcNMzQwMTI0MTkyMDIwWjCBnjELMAkG
A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVNhbiBEaWVn
bzEZMBcGA1UEChMQQ2hlcnJ5UHkgUHJvamVjdDERMA8GA1UECxMIZGV2LXRlc3Qx
FjAUBgNVBAMTDUNoZXJyeVB5IFRlYW0xIDAeBgkqhkiG9w0BCQEWEXJlbWlAY2hl
cnJ5cHkub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBKo554mzIMY+A
ByUNpaUOP9bJnQ7ZLQe9XgHwoLJR4VzpyZZZR9L4WtImEew05FY3Izerfm3MN3+M
C0tJ6yQU9sOiU3vBW6RrLIMlfKsnRwBRZ0Knda+O6xldVSosu8Ev3z9VZ94iC/Zg
KzrH7Mjj/U8/MQO7RBS/LAqee8bFNQIDAQABo4IBBzCCAQMwHQYDVR0OBBYEFDIQ
2feb71tVZCWpU0qJ/Tw+wdtoMIHTBgNVHSMEgcswgciAFDIQ2feb71tVZCWpU0qJ
/Tw+wdtooYGkpIGhMIGeMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p
YTESMBAGA1UEBxMJU2FuIERpZWdvMRkwFwYDVQQKExBDaGVycnlQeSBQcm9qZWN0
MREwDwYDVQQLEwhkZXYtdGVzdDEWMBQGA1UEAxMNQ2hlcnJ5UHkgVGVhbTEgMB4G
CSqGSIb3DQEJARYRcmVtaUBjaGVycnlweS5vcmeCCQCNfAQ+3kMZRjAMBgNVHRME
BTADAQH/MA0GCSqGSIb3DQEBBAUAA4GBAL7AAQz7IePV48ZTAFHKr88ntPALsL5S
8vHCZPNMevNkLTj3DYUw2BcnENxMjm1kou2F2BkvheBPNZKIhc6z4hAml3ed1xa2
D7w6e6OTcstdK/+KrPDDHeOP1dhMWNs2JE1bNlfF1LiXzYKSXpe88eCKjCXsCT/T
NluCaWQys3MS
-----END CERTIFICATE-----

View File

@ -1,79 +0,0 @@
# This file is part of CherryPy <http://www.cherrypy.org/>
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
import cherrypy
from cherrypy._cpcompat import md5, ntob
from cherrypy.lib import auth_basic
from cherrypy.test import helper
class BasicAuthTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "This is public."
index.exposed = True
class BasicProtected:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
class BasicProtected2:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
userpassdict = {'xuser' : 'xpassword'}
userhashdict = {'xuser' : md5(ntob('xpassword')).hexdigest()}
def checkpasshash(realm, user, password):
p = userhashdict.get(user)
return p and p == md5(ntob(password)).hexdigest() or False
conf = {'/basic': {'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'wonderland',
'tools.auth_basic.checkpassword': auth_basic.checkpassword_dict(userpassdict)},
'/basic2': {'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'wonderland',
'tools.auth_basic.checkpassword': checkpasshash},
}
root = Root()
root.basic = BasicProtected()
root.basic2 = BasicProtected2()
cherrypy.tree.mount(root, config=conf)
setup_server = staticmethod(setup_server)
def testPublic(self):
self.getPage("/")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.assertBody('This is public.')
def testBasic(self):
self.getPage("/basic/")
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="wonderland"')
self.getPage('/basic/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
self.assertStatus(401)
self.getPage('/basic/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
self.assertStatus('200 OK')
self.assertBody("Hello xuser, you've been authorized.")
def testBasic2(self):
self.getPage("/basic2/")
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="wonderland"')
self.getPage('/basic2/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')])
self.assertStatus(401)
self.getPage('/basic2/', [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')])
self.assertStatus('200 OK')
self.assertBody("Hello xuser, you've been authorized.")

View File

@ -1,115 +0,0 @@
# This file is part of CherryPy <http://www.cherrypy.org/>
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:expandtab:fileencoding=utf-8
import cherrypy
from cherrypy.lib import auth_digest
from cherrypy.test import helper
class DigestAuthTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "This is public."
index.exposed = True
class DigestProtected:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
def fetch_users():
return {'test': 'test'}
get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(fetch_users())
conf = {'/digest': {'tools.auth_digest.on': True,
'tools.auth_digest.realm': 'localhost',
'tools.auth_digest.get_ha1': get_ha1,
'tools.auth_digest.key': 'a565c27146791cfb',
'tools.auth_digest.debug': 'True'}}
root = Root()
root.digest = DigestProtected()
cherrypy.tree.mount(root, config=conf)
setup_server = staticmethod(setup_server)
def testPublic(self):
self.getPage("/")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.assertBody('This is public.')
def testDigest(self):
self.getPage("/digest/")
self.assertStatus(401)
value = None
for k, v in self.headers:
if k.lower() == "www-authenticate":
if v.startswith("Digest"):
value = v
break
if value is None:
self._handlewebError("Digest authentification scheme was not found")
value = value[7:]
items = value.split(', ')
tokens = {}
for item in items:
key, value = item.split('=')
tokens[key.lower()] = value
missing_msg = "%s is missing"
bad_value_msg = "'%s' was expecting '%s' but found '%s'"
nonce = None
if 'realm' not in tokens:
self._handlewebError(missing_msg % 'realm')
elif tokens['realm'] != '"localhost"':
self._handlewebError(bad_value_msg % ('realm', '"localhost"', tokens['realm']))
if 'nonce' not in tokens:
self._handlewebError(missing_msg % 'nonce')
else:
nonce = tokens['nonce'].strip('"')
if 'algorithm' not in tokens:
self._handlewebError(missing_msg % 'algorithm')
elif tokens['algorithm'] != '"MD5"':
self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
if 'qop' not in tokens:
self._handlewebError(missing_msg % 'qop')
elif tokens['qop'] != '"auth"':
self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
get_ha1 = auth_digest.get_ha1_dict_plain({'test' : 'test'})
# Test user agent response with a wrong value for 'realm'
base_auth = 'Digest username="test", realm="wrong realm", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
auth_header = base_auth % (nonce, '11111111111111111111111111111111', '00000001')
auth = auth_digest.HttpDigestAuthorization(auth_header, 'GET')
# calculate the response digest
ha1 = get_ha1(auth.realm, 'test')
response = auth.request_digest(ha1)
# send response with correct response digest, but wrong realm
auth_header = base_auth % (nonce, response, '00000001')
self.getPage('/digest/', [('Authorization', auth_header)])
self.assertStatus(401)
# Test that must pass
base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
auth_header = base_auth % (nonce, '11111111111111111111111111111111', '00000001')
auth = auth_digest.HttpDigestAuthorization(auth_header, 'GET')
# calculate the response digest
ha1 = get_ha1('localhost', 'test')
response = auth.request_digest(ha1)
# send response with correct response digest
auth_header = base_auth % (nonce, response, '00000001')
self.getPage('/digest/', [('Authorization', auth_header)])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")

View File

@ -1,263 +0,0 @@
import threading
import time
import unittest
import cherrypy
from cherrypy._cpcompat import get_daemon, set
from cherrypy.process import wspbus
msg = "Listener %d on channel %s: %s."
class PublishSubscribeTests(unittest.TestCase):
def get_listener(self, channel, index):
def listener(arg=None):
self.responses.append(msg % (index, channel, arg))
return listener
def test_builtin_channels(self):
b = wspbus.Bus()
self.responses, expected = [], []
for channel in b.listeners:
for index, priority in enumerate([100, 50, 0, 51]):
b.subscribe(channel, self.get_listener(channel, index), priority)
for channel in b.listeners:
b.publish(channel)
expected.extend([msg % (i, channel, None) for i in (2, 1, 3, 0)])
b.publish(channel, arg=79347)
expected.extend([msg % (i, channel, 79347) for i in (2, 1, 3, 0)])
self.assertEqual(self.responses, expected)
def test_custom_channels(self):
b = wspbus.Bus()
self.responses, expected = [], []
custom_listeners = ('hugh', 'louis', 'dewey')
for channel in custom_listeners:
for index, priority in enumerate([None, 10, 60, 40]):
b.subscribe(channel, self.get_listener(channel, index), priority)
for channel in custom_listeners:
b.publish(channel, 'ah so')
expected.extend([msg % (i, channel, 'ah so') for i in (1, 3, 0, 2)])
b.publish(channel)
expected.extend([msg % (i, channel, None) for i in (1, 3, 0, 2)])
self.assertEqual(self.responses, expected)
def test_listener_errors(self):
b = wspbus.Bus()
self.responses, expected = [], []
channels = [c for c in b.listeners if c != 'log']
for channel in channels:
b.subscribe(channel, self.get_listener(channel, 1))
# This will break since the lambda takes no args.
b.subscribe(channel, lambda: None, priority=20)
for channel in channels:
self.assertRaises(wspbus.ChannelFailures, b.publish, channel, 123)
expected.append(msg % (1, channel, 123))
self.assertEqual(self.responses, expected)
class BusMethodTests(unittest.TestCase):
def log(self, bus):
self._log_entries = []
def logit(msg, level):
self._log_entries.append(msg)
bus.subscribe('log', logit)
def assertLog(self, entries):
self.assertEqual(self._log_entries, entries)
def get_listener(self, channel, index):
def listener(arg=None):
self.responses.append(msg % (index, channel, arg))
return listener
def test_start(self):
b = wspbus.Bus()
self.log(b)
self.responses = []
num = 3
for index in range(num):
b.subscribe('start', self.get_listener('start', index))
b.start()
try:
# The start method MUST call all 'start' listeners.
self.assertEqual(set(self.responses),
set([msg % (i, 'start', None) for i in range(num)]))
# The start method MUST move the state to STARTED
# (or EXITING, if errors occur)
self.assertEqual(b.state, b.states.STARTED)
# The start method MUST log its states.
self.assertLog(['Bus STARTING', 'Bus STARTED'])
finally:
# Exit so the atexit handler doesn't complain.
b.exit()
def test_stop(self):
b = wspbus.Bus()
self.log(b)
self.responses = []
num = 3
for index in range(num):
b.subscribe('stop', self.get_listener('stop', index))
b.stop()
# The stop method MUST call all 'stop' listeners.
self.assertEqual(set(self.responses),
set([msg % (i, 'stop', None) for i in range(num)]))
# The stop method MUST move the state to STOPPED
self.assertEqual(b.state, b.states.STOPPED)
# The stop method MUST log its states.
self.assertLog(['Bus STOPPING', 'Bus STOPPED'])
def test_graceful(self):
b = wspbus.Bus()
self.log(b)
self.responses = []
num = 3
for index in range(num):
b.subscribe('graceful', self.get_listener('graceful', index))
b.graceful()
# The graceful method MUST call all 'graceful' listeners.
self.assertEqual(set(self.responses),
set([msg % (i, 'graceful', None) for i in range(num)]))
# The graceful method MUST log its states.
self.assertLog(['Bus graceful'])
def test_exit(self):
b = wspbus.Bus()
self.log(b)
self.responses = []
num = 3
for index in range(num):
b.subscribe('stop', self.get_listener('stop', index))
b.subscribe('exit', self.get_listener('exit', index))
b.exit()
# The exit method MUST call all 'stop' listeners,
# and then all 'exit' listeners.
self.assertEqual(set(self.responses),
set([msg % (i, 'stop', None) for i in range(num)] +
[msg % (i, 'exit', None) for i in range(num)]))
# The exit method MUST move the state to EXITING
self.assertEqual(b.state, b.states.EXITING)
# The exit method MUST log its states.
self.assertLog(['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED'])
def test_wait(self):
b = wspbus.Bus()
def f(method):
time.sleep(0.2)
getattr(b, method)()
for method, states in [('start', [b.states.STARTED]),
('stop', [b.states.STOPPED]),
('start', [b.states.STARTING, b.states.STARTED]),
('exit', [b.states.EXITING]),
]:
threading.Thread(target=f, args=(method,)).start()
b.wait(states)
# The wait method MUST wait for the given state(s).
if b.state not in states:
self.fail("State %r not in %r" % (b.state, states))
def test_block(self):
b = wspbus.Bus()
self.log(b)
def f():
time.sleep(0.2)
b.exit()
def g():
time.sleep(0.4)
threading.Thread(target=f).start()
threading.Thread(target=g).start()
threads = [t for t in threading.enumerate() if not get_daemon(t)]
self.assertEqual(len(threads), 3)
b.block()
# The block method MUST wait for the EXITING state.
self.assertEqual(b.state, b.states.EXITING)
# The block method MUST wait for ALL non-main, non-daemon threads to finish.
threads = [t for t in threading.enumerate() if not get_daemon(t)]
self.assertEqual(len(threads), 1)
# The last message will mention an indeterminable thread name; ignore it
self.assertEqual(self._log_entries[:-1],
['Bus STOPPING', 'Bus STOPPED',
'Bus EXITING', 'Bus EXITED',
'Waiting for child threads to terminate...'])
def test_start_with_callback(self):
b = wspbus.Bus()
self.log(b)
try:
events = []
def f(*args, **kwargs):
events.append(("f", args, kwargs))
def g():
events.append("g")
b.subscribe("start", g)
b.start_with_callback(f, (1, 3, 5), {"foo": "bar"})
# Give wait() time to run f()
time.sleep(0.2)
# The callback method MUST wait for the STARTED state.
self.assertEqual(b.state, b.states.STARTED)
# The callback method MUST run after all start methods.
self.assertEqual(events, ["g", ("f", (1, 3, 5), {"foo": "bar"})])
finally:
b.exit()
def test_log(self):
b = wspbus.Bus()
self.log(b)
self.assertLog([])
# Try a normal message.
expected = []
for msg in ["O mah darlin'"] * 3 + ["Clementiiiiiiiine"]:
b.log(msg)
expected.append(msg)
self.assertLog(expected)
# Try an error message
try:
foo
except NameError:
b.log("You are lost and gone forever", traceback=True)
lastmsg = self._log_entries[-1]
if "Traceback" not in lastmsg or "NameError" not in lastmsg:
self.fail("Last log message %r did not contain "
"the expected traceback." % lastmsg)
else:
self.fail("NameError was not raised as expected.")
if __name__ == "__main__":
unittest.main()

View File

@ -1,328 +0,0 @@
import datetime
import gzip
from itertools import count
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import sys
import threading
import time
import urllib
import cherrypy
from cherrypy._cpcompat import next, ntob, quote, xrange
from cherrypy.lib import httputil
gif_bytes = ntob('GIF89a\x01\x00\x01\x00\x82\x00\x01\x99"\x1e\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x02\x03\x02\x08\t\x00;')
from cherrypy.test import helper
class CacheTest(helper.CPWebCase):
def setup_server():
class Root:
_cp_config = {'tools.caching.on': True}
def __init__(self):
self.counter = 0
self.control_counter = 0
self.longlock = threading.Lock()
def index(self):
self.counter += 1
msg = "visit #%s" % self.counter
return msg
index.exposed = True
def control(self):
self.control_counter += 1
return "visit #%s" % self.control_counter
control.exposed = True
def a_gif(self):
cherrypy.response.headers['Last-Modified'] = httputil.HTTPDate()
return gif_bytes
a_gif.exposed = True
def long_process(self, seconds='1'):
try:
self.longlock.acquire()
time.sleep(float(seconds))
finally:
self.longlock.release()
return 'success!'
long_process.exposed = True
def clear_cache(self, path):
cherrypy._cache.store[cherrypy.request.base + path].clear()
clear_cache.exposed = True
class VaryHeaderCachingServer(object):
_cp_config = {'tools.caching.on': True,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Vary', 'Our-Varying-Header')],
}
def __init__(self):
self.counter = count(1)
def index(self):
return "visit #%s" % next(self.counter)
index.exposed = True
class UnCached(object):
_cp_config = {'tools.expires.on': True,
'tools.expires.secs': 60,
'tools.staticdir.on': True,
'tools.staticdir.dir': 'static',
'tools.staticdir.root': curdir,
}
def force(self):
cherrypy.response.headers['Etag'] = 'bibbitybobbityboo'
self._cp_config['tools.expires.force'] = True
self._cp_config['tools.expires.secs'] = 0
return "being forceful"
force.exposed = True
force._cp_config = {'tools.expires.secs': 0}
def dynamic(self):
cherrypy.response.headers['Etag'] = 'bibbitybobbityboo'
cherrypy.response.headers['Cache-Control'] = 'private'
return "D-d-d-dynamic!"
dynamic.exposed = True
def cacheable(self):
cherrypy.response.headers['Etag'] = 'bibbitybobbityboo'
return "Hi, I'm cacheable."
cacheable.exposed = True
def specific(self):
cherrypy.response.headers['Etag'] = 'need_this_to_make_me_cacheable'
return "I am being specific"
specific.exposed = True
specific._cp_config = {'tools.expires.secs': 86400}
class Foo(object):pass
def wrongtype(self):
cherrypy.response.headers['Etag'] = 'need_this_to_make_me_cacheable'
return "Woops"
wrongtype.exposed = True
wrongtype._cp_config = {'tools.expires.secs': Foo()}
cherrypy.tree.mount(Root())
cherrypy.tree.mount(UnCached(), "/expires")
cherrypy.tree.mount(VaryHeaderCachingServer(), "/varying_headers")
cherrypy.config.update({'tools.gzip.on': True})
setup_server = staticmethod(setup_server)
def testCaching(self):
elapsed = 0.0
for trial in range(10):
self.getPage("/")
# The response should be the same every time,
# except for the Age response header.
self.assertBody('visit #1')
if trial != 0:
age = int(self.assertHeader("Age"))
self.assert_(age >= elapsed)
elapsed = age
# POST, PUT, DELETE should not be cached.
self.getPage("/", method="POST")
self.assertBody('visit #2')
# Because gzip is turned on, the Vary header should always Vary for content-encoding
self.assertHeader('Vary', 'Accept-Encoding')
# The previous request should have invalidated the cache,
# so this request will recalc the response.
self.getPage("/", method="GET")
self.assertBody('visit #3')
# ...but this request should get the cached copy.
self.getPage("/", method="GET")
self.assertBody('visit #3')
self.getPage("/", method="DELETE")
self.assertBody('visit #4')
# The previous request should have invalidated the cache,
# so this request will recalc the response.
self.getPage("/", method="GET", headers=[('Accept-Encoding', 'gzip')])
self.assertHeader('Content-Encoding', 'gzip')
self.assertHeader('Vary')
self.assertEqual(cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
# Now check that a second request gets the gzip header and gzipped body
# This also tests a bug in 3.0 to 3.0.2 whereby the cached, gzipped
# response body was being gzipped a second time.
self.getPage("/", method="GET", headers=[('Accept-Encoding', 'gzip')])
self.assertHeader('Content-Encoding', 'gzip')
self.assertEqual(cherrypy.lib.encoding.decompress(self.body), ntob("visit #5"))
# Now check that a third request that doesn't accept gzip
# skips the cache (because the 'Vary' header denies it).
self.getPage("/", method="GET")
self.assertNoHeader('Content-Encoding')
self.assertBody('visit #6')
def testVaryHeader(self):
self.getPage("/varying_headers/")
self.assertStatus("200 OK")
self.assertHeaderItemValue('Vary', 'Our-Varying-Header')
self.assertBody('visit #1')
# Now check that different 'Vary'-fields don't evict each other.
# This test creates 2 requests with different 'Our-Varying-Header'
# and then tests if the first one still exists.
self.getPage("/varying_headers/", headers=[('Our-Varying-Header', 'request 2')])
self.assertStatus("200 OK")
self.assertBody('visit #2')
self.getPage("/varying_headers/", headers=[('Our-Varying-Header', 'request 2')])
self.assertStatus("200 OK")
self.assertBody('visit #2')
self.getPage("/varying_headers/")
self.assertStatus("200 OK")
self.assertBody('visit #1')
def testExpiresTool(self):
# test setting an expires header
self.getPage("/expires/specific")
self.assertStatus("200 OK")
self.assertHeader("Expires")
# test exceptions for bad time values
self.getPage("/expires/wrongtype")
self.assertStatus(500)
self.assertInBody("TypeError")
# static content should not have "cache prevention" headers
self.getPage("/expires/index.html")
self.assertStatus("200 OK")
self.assertNoHeader("Pragma")
self.assertNoHeader("Cache-Control")
self.assertHeader("Expires")
# dynamic content that sets indicators should not have
# "cache prevention" headers
self.getPage("/expires/cacheable")
self.assertStatus("200 OK")
self.assertNoHeader("Pragma")
self.assertNoHeader("Cache-Control")
self.assertHeader("Expires")
self.getPage('/expires/dynamic')
self.assertBody("D-d-d-dynamic!")
# the Cache-Control header should be untouched
self.assertHeader("Cache-Control", "private")
self.assertHeader("Expires")
# configure the tool to ignore indicators and replace existing headers
self.getPage("/expires/force")
self.assertStatus("200 OK")
# This also gives us a chance to test 0 expiry with no other headers
self.assertHeader("Pragma", "no-cache")
if cherrypy.server.protocol_version == "HTTP/1.1":
self.assertHeader("Cache-Control", "no-cache, must-revalidate")
self.assertHeader("Expires", "Sun, 28 Jan 2007 00:00:00 GMT")
# static content should now have "cache prevention" headers
self.getPage("/expires/index.html")
self.assertStatus("200 OK")
self.assertHeader("Pragma", "no-cache")
if cherrypy.server.protocol_version == "HTTP/1.1":
self.assertHeader("Cache-Control", "no-cache, must-revalidate")
self.assertHeader("Expires", "Sun, 28 Jan 2007 00:00:00 GMT")
# the cacheable handler should now have "cache prevention" headers
self.getPage("/expires/cacheable")
self.assertStatus("200 OK")
self.assertHeader("Pragma", "no-cache")
if cherrypy.server.protocol_version == "HTTP/1.1":
self.assertHeader("Cache-Control", "no-cache, must-revalidate")
self.assertHeader("Expires", "Sun, 28 Jan 2007 00:00:00 GMT")
self.getPage('/expires/dynamic')
self.assertBody("D-d-d-dynamic!")
# dynamic sets Cache-Control to private but it should be
# overwritten here ...
self.assertHeader("Pragma", "no-cache")
if cherrypy.server.protocol_version == "HTTP/1.1":
self.assertHeader("Cache-Control", "no-cache, must-revalidate")
self.assertHeader("Expires", "Sun, 28 Jan 2007 00:00:00 GMT")
def testLastModified(self):
self.getPage("/a.gif")
self.assertStatus(200)
self.assertBody(gif_bytes)
lm1 = self.assertHeader("Last-Modified")
# this request should get the cached copy.
self.getPage("/a.gif")
self.assertStatus(200)
self.assertBody(gif_bytes)
self.assertHeader("Age")
lm2 = self.assertHeader("Last-Modified")
self.assertEqual(lm1, lm2)
# this request should match the cached copy, but raise 304.
self.getPage("/a.gif", [('If-Modified-Since', lm1)])
self.assertStatus(304)
self.assertNoHeader("Last-Modified")
if not getattr(cherrypy.server, "using_apache", False):
self.assertHeader("Age")
def test_antistampede(self):
SECONDS = 4
# We MUST make an initial synchronous request in order to create the
# AntiStampedeCache object, and populate its selecting_headers,
# before the actual stampede.
self.getPage("/long_process?seconds=%d" % SECONDS)
self.assertBody('success!')
self.getPage("/clear_cache?path=" +
quote('/long_process?seconds=%d' % SECONDS, safe=''))
self.assertStatus(200)
start = datetime.datetime.now()
def run():
self.getPage("/long_process?seconds=%d" % SECONDS)
# The response should be the same every time
self.assertBody('success!')
ts = [threading.Thread(target=run) for i in xrange(100)]
for t in ts:
t.start()
for t in ts:
t.join()
self.assertEqualDates(start, datetime.datetime.now(),
# Allow a second (two, for slow hosts)
# for our thread/TCP overhead etc.
seconds=SECONDS + 2)
def test_cache_control(self):
self.getPage("/control")
self.assertBody('visit #1')
self.getPage("/control")
self.assertBody('visit #1')
self.getPage("/control", headers=[('Cache-Control', 'no-cache')])
self.assertBody('visit #2')
self.getPage("/control")
self.assertBody('visit #2')
self.getPage("/control", headers=[('Pragma', 'no-cache')])
self.assertBody('visit #3')
self.getPage("/control")
self.assertBody('visit #3')
time.sleep(1)
self.getPage("/control", headers=[('Cache-Control', 'max-age=0')])
self.assertBody('visit #4')
self.getPage("/control")
self.assertBody('visit #4')

View File

@ -1,256 +0,0 @@
"""Tests for the CherryPy configuration system."""
import os, sys
localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
from cherrypy._cpcompat import ntob, StringIO
import unittest
import cherrypy
def setup_server():
class Root:
_cp_config = {'foo': 'this',
'bar': 'that'}
def __init__(self):
cherrypy.config.namespaces['db'] = self.db_namespace
def db_namespace(self, k, v):
if k == "scheme":
self.db = v
# @cherrypy.expose(alias=('global_', 'xyz'))
def index(self, key):
return cherrypy.request.config.get(key, "None")
index = cherrypy.expose(index, alias=('global_', 'xyz'))
def repr(self, key):
return repr(cherrypy.request.config.get(key, None))
repr.exposed = True
def dbscheme(self):
return self.db
dbscheme.exposed = True
def plain(self, x):
return x
plain.exposed = True
plain._cp_config = {'request.body.attempt_charsets': ['utf-16']}
favicon_ico = cherrypy.tools.staticfile.handler(
filename=os.path.join(localDir, '../favicon.ico'))
class Foo:
_cp_config = {'foo': 'this2',
'baz': 'that2'}
def index(self, key):
return cherrypy.request.config.get(key, "None")
index.exposed = True
nex = index
def silly(self):
return 'Hello world'
silly.exposed = True
silly._cp_config = {'response.headers.X-silly': 'sillyval'}
# Test the expose and config decorators
#@cherrypy.expose
#@cherrypy.config(foo='this3', **{'bax': 'this4'})
def bar(self, key):
return repr(cherrypy.request.config.get(key, None))
bar.exposed = True
bar._cp_config = {'foo': 'this3', 'bax': 'this4'}
class Another:
def index(self, key):
return str(cherrypy.request.config.get(key, "None"))
index.exposed = True
def raw_namespace(key, value):
if key == 'input.map':
handler = cherrypy.request.handler
def wrapper():
params = cherrypy.request.params
for name, coercer in list(value.items()):
try:
params[name] = coercer(params[name])
except KeyError:
pass
return handler()
cherrypy.request.handler = wrapper
elif key == 'output':
handler = cherrypy.request.handler
def wrapper():
# 'value' is a type (like int or str).
return value(handler())
cherrypy.request.handler = wrapper
class Raw:
_cp_config = {'raw.output': repr}
def incr(self, num):
return num + 1
incr.exposed = True
incr._cp_config = {'raw.input.map': {'num': int}}
ioconf = StringIO("""
[/]
neg: -1234
filename: os.path.join(sys.prefix, "hello.py")
thing1: cherrypy.lib.httputil.response_codes[404]
thing2: __import__('cherrypy.tutorial', globals(), locals(), ['']).thing2
complex: 3+2j
mul: 6*3
ones: "11"
twos: "22"
stradd: %%(ones)s + %%(twos)s + "33"
[/favicon.ico]
tools.staticfile.filename = %r
""" % os.path.join(localDir, 'static/dirback.jpg'))
root = Root()
root.foo = Foo()
root.raw = Raw()
app = cherrypy.tree.mount(root, config=ioconf)
app.request_class.namespaces['raw'] = raw_namespace
cherrypy.tree.mount(Another(), "/another")
cherrypy.config.update({'luxuryyacht': 'throatwobblermangrove',
'db.scheme': r"sqlite///memory",
})
# Client-side code #
from cherrypy.test import helper
class ConfigTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testConfig(self):
tests = [
('/', 'nex', 'None'),
('/', 'foo', 'this'),
('/', 'bar', 'that'),
('/xyz', 'foo', 'this'),
('/foo/', 'foo', 'this2'),
('/foo/', 'bar', 'that'),
('/foo/', 'bax', 'None'),
('/foo/bar', 'baz', "'that2'"),
('/foo/nex', 'baz', 'that2'),
# If 'foo' == 'this', then the mount point '/another' leaks into '/'.
('/another/','foo', 'None'),
]
for path, key, expected in tests:
self.getPage(path + "?key=" + key)
self.assertBody(expected)
expectedconf = {
# From CP defaults
'tools.log_headers.on': False,
'tools.log_tracebacks.on': True,
'request.show_tracebacks': True,
'log.screen': False,
'environment': 'test_suite',
'engine.autoreload_on': False,
# From global config
'luxuryyacht': 'throatwobblermangrove',
# From Root._cp_config
'bar': 'that',
# From Foo._cp_config
'baz': 'that2',
# From Foo.bar._cp_config
'foo': 'this3',
'bax': 'this4',
}
for key, expected in expectedconf.items():
self.getPage("/foo/bar?key=" + key)
self.assertBody(repr(expected))
def testUnrepr(self):
self.getPage("/repr?key=neg")
self.assertBody("-1234")
self.getPage("/repr?key=filename")
self.assertBody(repr(os.path.join(sys.prefix, "hello.py")))
self.getPage("/repr?key=thing1")
self.assertBody(repr(cherrypy.lib.httputil.response_codes[404]))
if not getattr(cherrypy.server, "using_apache", False):
# The object ID's won't match up when using Apache, since the
# server and client are running in different processes.
self.getPage("/repr?key=thing2")
from cherrypy.tutorial import thing2
self.assertBody(repr(thing2))
self.getPage("/repr?key=complex")
self.assertBody("(3+2j)")
self.getPage("/repr?key=mul")
self.assertBody("18")
self.getPage("/repr?key=stradd")
self.assertBody(repr("112233"))
def testRespNamespaces(self):
self.getPage("/foo/silly")
self.assertHeader('X-silly', 'sillyval')
self.assertBody('Hello world')
def testCustomNamespaces(self):
self.getPage("/raw/incr?num=12")
self.assertBody("13")
self.getPage("/dbscheme")
self.assertBody(r"sqlite///memory")
def testHandlerToolConfigOverride(self):
# Assert that config overrides tool constructor args. Above, we set
# the favicon in the page handler to be '../favicon.ico',
# but then overrode it in config to be './static/dirback.jpg'.
self.getPage("/favicon.ico")
self.assertBody(open(os.path.join(localDir, "static/dirback.jpg"),
"rb").read())
def test_request_body_namespace(self):
self.getPage("/plain", method='POST', headers=[
('Content-Type', 'application/x-www-form-urlencoded'),
('Content-Length', '13')],
body=ntob('\xff\xfex\x00=\xff\xfea\x00b\x00c\x00'))
self.assertBody("abc")
class VariableSubstitutionTests(unittest.TestCase):
setup_server = staticmethod(setup_server)
def test_config(self):
from textwrap import dedent
# variable substitution with [DEFAULT]
conf = dedent("""
[DEFAULT]
dir = "/some/dir"
my.dir = %(dir)s + "/sub"
[my]
my.dir = %(dir)s + "/my/dir"
my.dir2 = %(my.dir)s + '/dir2'
""")
fp = StringIO(conf)
cherrypy.config.update(fp)
self.assertEqual(cherrypy.config["my"]["my.dir"], "/some/dir/my/dir")
self.assertEqual(cherrypy.config["my"]["my.dir2"], "/some/dir/my/dir/dir2")

View File

@ -1,121 +0,0 @@
"""Tests for the CherryPy configuration system."""
import os, sys
localDir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import socket
import time
import cherrypy
# Client-side code #
from cherrypy.test import helper
class ServerConfigTests(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return cherrypy.request.wsgi_environ['SERVER_PORT']
index.exposed = True
def upload(self, file):
return "Size: %s" % len(file.file.read())
upload.exposed = True
def tinyupload(self):
return cherrypy.request.body.read()
tinyupload.exposed = True
tinyupload._cp_config = {'request.body.maxbytes': 100}
cherrypy.tree.mount(Root())
cherrypy.config.update({
'server.socket_host': '0.0.0.0',
'server.socket_port': 9876,
'server.max_request_body_size': 200,
'server.max_request_header_size': 500,
'server.socket_timeout': 0.5,
# Test explicit server.instance
'server.2.instance': 'cherrypy._cpwsgi_server.CPWSGIServer',
'server.2.socket_port': 9877,
# Test non-numeric <servername>
# Also test default server.instance = builtin server
'server.yetanother.socket_port': 9878,
})
setup_server = staticmethod(setup_server)
PORT = 9876
def testBasicConfig(self):
self.getPage("/")
self.assertBody(str(self.PORT))
def testAdditionalServers(self):
if self.scheme == 'https':
return self.skip("not available under ssl")
self.PORT = 9877
self.getPage("/")
self.assertBody(str(self.PORT))
self.PORT = 9878
self.getPage("/")
self.assertBody(str(self.PORT))
def testMaxRequestSizePerHandler(self):
if getattr(cherrypy.server, "using_apache", False):
return self.skip("skipped due to known Apache differences... ")
self.getPage('/tinyupload', method="POST",
headers=[('Content-Type', 'text/plain'),
('Content-Length', '100')],
body="x" * 100)
self.assertStatus(200)
self.assertBody("x" * 100)
self.getPage('/tinyupload', method="POST",
headers=[('Content-Type', 'text/plain'),
('Content-Length', '101')],
body="x" * 101)
self.assertStatus(413)
def testMaxRequestSize(self):
if getattr(cherrypy.server, "using_apache", False):
return self.skip("skipped due to known Apache differences... ")
for size in (500, 5000, 50000):
self.getPage("/", headers=[('From', "x" * 500)])
self.assertStatus(413)
# Test for http://www.cherrypy.org/ticket/421
# (Incorrect border condition in readline of SizeCheckWrapper).
# This hangs in rev 891 and earlier.
lines256 = "x" * 248
self.getPage("/",
headers=[('Host', '%s:%s' % (self.HOST, self.PORT)),
('From', lines256)])
# Test upload
body = '\r\n'.join([
'--x',
'Content-Disposition: form-data; name="file"; filename="hello.txt"',
'Content-Type: text/plain',
'',
'%s',
'--x--'])
partlen = 200 - len(body)
b = body % ("x" * partlen)
h = [("Content-type", "multipart/form-data; boundary=x"),
("Content-Length", "%s" % len(b))]
self.getPage('/upload', h, "POST", b)
self.assertBody('Size: %d' % partlen)
b = body % ("x" * 200)
h = [("Content-type", "multipart/form-data; boundary=x"),
("Content-Length", "%s" % len(b))]
self.getPage('/upload', h, "POST", b)
self.assertStatus(413)

View File

@ -1,734 +0,0 @@
"""Tests for TCP connection handling, including proper and timely close."""
import socket
import sys
import time
timeout = 1
import cherrypy
from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, NotConnected, BadStatusLine
from cherrypy._cpcompat import ntob, urlopen, unicodestr
from cherrypy.test import webtest
from cherrypy import _cperror
pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN'
def setup_server():
def raise500():
raise cherrypy.HTTPError(500)
class Root:
def index(self):
return pov
index.exposed = True
page1 = index
page2 = index
page3 = index
def hello(self):
return "Hello, world!"
hello.exposed = True
def timeout(self, t):
return str(cherrypy.server.httpserver.timeout)
timeout.exposed = True
def stream(self, set_cl=False):
if set_cl:
cherrypy.response.headers['Content-Length'] = 10
def content():
for x in range(10):
yield str(x)
return content()
stream.exposed = True
stream._cp_config = {'response.stream': True}
def error(self, code=500):
raise cherrypy.HTTPError(code)
error.exposed = True
def upload(self):
if not cherrypy.request.method == 'POST':
raise AssertionError("'POST' != request.method %r" %
cherrypy.request.method)
return "thanks for '%s'" % cherrypy.request.body.read()
upload.exposed = True
def custom(self, response_code):
cherrypy.response.status = response_code
return "Code = %s" % response_code
custom.exposed = True
def err_before_read(self):
return "ok"
err_before_read.exposed = True
err_before_read._cp_config = {'hooks.on_start_resource': raise500}
def one_megabyte_of_a(self):
return ["a" * 1024] * 1024
one_megabyte_of_a.exposed = True
def custom_cl(self, body, cl):
cherrypy.response.headers['Content-Length'] = cl
if not isinstance(body, list):
body = [body]
newbody = []
for chunk in body:
if isinstance(chunk, unicodestr):
chunk = chunk.encode('ISO-8859-1')
newbody.append(chunk)
return newbody
custom_cl.exposed = True
# Turn off the encoding tool so it doens't collapse
# our response body and reclaculate the Content-Length.
custom_cl._cp_config = {'tools.encode.on': False}
cherrypy.tree.mount(Root())
cherrypy.config.update({
'server.max_request_body_size': 1001,
'server.socket_timeout': timeout,
})
from cherrypy.test import helper
class ConnectionCloseTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_HTTP11(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
self.persistent = True
# Make the first request and assert there's no "Connection: close".
self.getPage("/")
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertNoHeader("Connection")
# Make another request on the same connection.
self.getPage("/page1")
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertNoHeader("Connection")
# Test client-side close.
self.getPage("/page2", headers=[("Connection", "close")])
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertHeader("Connection", "close")
# Make another request on the same connection, which should error.
self.assertRaises(NotConnected, self.getPage, "/")
def test_Streaming_no_len(self):
self._streaming(set_cl=False)
def test_Streaming_with_len(self):
self._streaming(set_cl=True)
def _streaming(self, set_cl):
if cherrypy.server.protocol_version == "HTTP/1.1":
self.PROTOCOL = "HTTP/1.1"
self.persistent = True
# Make the first request and assert there's no "Connection: close".
self.getPage("/")
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertNoHeader("Connection")
# Make another, streamed request on the same connection.
if set_cl:
# When a Content-Length is provided, the content should stream
# without closing the connection.
self.getPage("/stream?set_cl=Yes")
self.assertHeader("Content-Length")
self.assertNoHeader("Connection", "close")
self.assertNoHeader("Transfer-Encoding")
self.assertStatus('200 OK')
self.assertBody('0123456789')
else:
# When no Content-Length response header is provided,
# streamed output will either close the connection, or use
# chunked encoding, to determine transfer-length.
self.getPage("/stream")
self.assertNoHeader("Content-Length")
self.assertStatus('200 OK')
self.assertBody('0123456789')
chunked_response = False
for k, v in self.headers:
if k.lower() == "transfer-encoding":
if str(v) == "chunked":
chunked_response = True
if chunked_response:
self.assertNoHeader("Connection", "close")
else:
self.assertHeader("Connection", "close")
# Make another request on the same connection, which should error.
self.assertRaises(NotConnected, self.getPage, "/")
# Try HEAD. See http://www.cherrypy.org/ticket/864.
self.getPage("/stream", method='HEAD')
self.assertStatus('200 OK')
self.assertBody('')
self.assertNoHeader("Transfer-Encoding")
else:
self.PROTOCOL = "HTTP/1.0"
self.persistent = True
# Make the first request and assert Keep-Alive.
self.getPage("/", headers=[("Connection", "Keep-Alive")])
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertHeader("Connection", "Keep-Alive")
# Make another, streamed request on the same connection.
if set_cl:
# When a Content-Length is provided, the content should
# stream without closing the connection.
self.getPage("/stream?set_cl=Yes",
headers=[("Connection", "Keep-Alive")])
self.assertHeader("Content-Length")
self.assertHeader("Connection", "Keep-Alive")
self.assertNoHeader("Transfer-Encoding")
self.assertStatus('200 OK')
self.assertBody('0123456789')
else:
# When a Content-Length is not provided,
# the server should close the connection.
self.getPage("/stream", headers=[("Connection", "Keep-Alive")])
self.assertStatus('200 OK')
self.assertBody('0123456789')
self.assertNoHeader("Content-Length")
self.assertNoHeader("Connection", "Keep-Alive")
self.assertNoHeader("Transfer-Encoding")
# Make another request on the same connection, which should error.
self.assertRaises(NotConnected, self.getPage, "/")
def test_HTTP10_KeepAlive(self):
self.PROTOCOL = "HTTP/1.0"
if self.scheme == "https":
self.HTTP_CONN = HTTPSConnection
else:
self.HTTP_CONN = HTTPConnection
# Test a normal HTTP/1.0 request.
self.getPage("/page2")
self.assertStatus('200 OK')
self.assertBody(pov)
# Apache, for example, may emit a Connection header even for HTTP/1.0
## self.assertNoHeader("Connection")
# Test a keep-alive HTTP/1.0 request.
self.persistent = True
self.getPage("/page3", headers=[("Connection", "Keep-Alive")])
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertHeader("Connection", "Keep-Alive")
# Remove the keep-alive header again.
self.getPage("/page3")
self.assertStatus('200 OK')
self.assertBody(pov)
# Apache, for example, may emit a Connection header even for HTTP/1.0
## self.assertNoHeader("Connection")
class PipelineTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_HTTP11_Timeout(self):
# If we timeout without sending any data,
# the server will close the conn with a 408.
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Connect but send nothing.
self.persistent = True
conn = self.HTTP_CONN
conn.auto_open = False
conn.connect()
# Wait for our socket timeout
time.sleep(timeout * 2)
# The request should have returned 408 already.
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 408)
conn.close()
# Connect but send half the headers only.
self.persistent = True
conn = self.HTTP_CONN
conn.auto_open = False
conn.connect()
conn.send(ntob('GET /hello HTTP/1.1'))
conn.send(("Host: %s" % self.HOST).encode('ascii'))
# Wait for our socket timeout
time.sleep(timeout * 2)
# The conn should have already sent 408.
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 408)
conn.close()
def test_HTTP11_Timeout_after_request(self):
# If we timeout after at least one request has succeeded,
# the server will close the conn without 408.
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Make an initial request
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/timeout?t=%s" % timeout, skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
self.body = response.read()
self.assertBody(str(timeout))
# Make a second request on the same socket
conn._output(ntob('GET /hello HTTP/1.1'))
conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
conn._send_output()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
self.body = response.read()
self.assertBody("Hello, world!")
# Wait for our socket timeout
time.sleep(timeout * 2)
# Make another request on the same socket, which should error
conn._output(ntob('GET /hello HTTP/1.1'))
conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
conn._send_output()
response = conn.response_class(conn.sock, method="GET")
try:
response.begin()
except:
if not isinstance(sys.exc_info()[1],
(socket.error, BadStatusLine)):
self.fail("Writing to timed out socket didn't fail"
" as it should have: %s" % sys.exc_info()[1])
else:
if response.status != 408:
self.fail("Writing to timed out socket didn't fail"
" as it should have: %s" %
response.read())
conn.close()
# Make another request on a new socket, which should work
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
self.body = response.read()
self.assertBody(pov)
# Make another request on the same socket,
# but timeout on the headers
conn.send(ntob('GET /hello HTTP/1.1'))
# Wait for our socket timeout
time.sleep(timeout * 2)
response = conn.response_class(conn.sock, method="GET")
try:
response.begin()
except:
if not isinstance(sys.exc_info()[1],
(socket.error, BadStatusLine)):
self.fail("Writing to timed out socket didn't fail"
" as it should have: %s" % sys.exc_info()[1])
else:
self.fail("Writing to timed out socket didn't fail"
" as it should have: %s" %
response.read())
conn.close()
# Retry the request on a new connection, which should work
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
self.body = response.read()
self.assertBody(pov)
conn.close()
def test_HTTP11_pipelining(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Test pipelining. httplib doesn't support this directly.
self.persistent = True
conn = self.HTTP_CONN
# Put request 1
conn.putrequest("GET", "/hello", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
for trial in range(5):
# Put next request
conn._output(ntob('GET /hello HTTP/1.1'))
conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
conn._send_output()
# Retrieve previous response
response = conn.response_class(conn.sock, method="GET")
response.begin()
body = response.read(13)
self.assertEqual(response.status, 200)
self.assertEqual(body, ntob("Hello, world!"))
# Retrieve final response
response = conn.response_class(conn.sock, method="GET")
response.begin()
body = response.read()
self.assertEqual(response.status, 200)
self.assertEqual(body, ntob("Hello, world!"))
conn.close()
def test_100_Continue(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
self.persistent = True
conn = self.HTTP_CONN
# Try a page without an Expect request header first.
# Note that httplib's response.begin automatically ignores
# 100 Continue responses, so we must manually check for it.
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Content-Type", "text/plain")
conn.putheader("Content-Length", "4")
conn.endheaders()
conn.send(ntob("d'oh"))
response = conn.response_class(conn.sock, method="POST")
version, status, reason = response._read_status()
self.assertNotEqual(status, 100)
conn.close()
# Now try a page with an Expect header...
conn.connect()
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Content-Type", "text/plain")
conn.putheader("Content-Length", "17")
conn.putheader("Expect", "100-continue")
conn.endheaders()
response = conn.response_class(conn.sock, method="POST")
# ...assert and then skip the 100 response
version, status, reason = response._read_status()
self.assertEqual(status, 100)
while True:
line = response.fp.readline().strip()
if line:
self.fail("100 Continue should not output any headers. Got %r" % line)
else:
break
# ...send the body
body = ntob("I am a small file")
conn.send(body)
# ...get the final response
response.begin()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(200)
self.assertBody("thanks for '%s'" % body)
conn.close()
class ConnectionTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_readall_or_close(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
if self.scheme == "https":
self.HTTP_CONN = HTTPSConnection
else:
self.HTTP_CONN = HTTPConnection
# Test a max of 0 (the default) and then reset to what it was above.
old_max = cherrypy.server.max_request_body_size
for new_max in (0, old_max):
cherrypy.server.max_request_body_size = new_max
self.persistent = True
conn = self.HTTP_CONN
# Get a POST page with an error
conn.putrequest("POST", "/err_before_read", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Content-Type", "text/plain")
conn.putheader("Content-Length", "1000")
conn.putheader("Expect", "100-continue")
conn.endheaders()
response = conn.response_class(conn.sock, method="POST")
# ...assert and then skip the 100 response
version, status, reason = response._read_status()
self.assertEqual(status, 100)
while True:
skip = response.fp.readline().strip()
if not skip:
break
# ...send the body
conn.send(ntob("x" * 1000))
# ...get the final response
response.begin()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(500)
# Now try a working page with an Expect header...
conn._output(ntob('POST /upload HTTP/1.1'))
conn._output(ntob("Host: %s" % self.HOST, 'ascii'))
conn._output(ntob("Content-Type: text/plain"))
conn._output(ntob("Content-Length: 17"))
conn._output(ntob("Expect: 100-continue"))
conn._send_output()
response = conn.response_class(conn.sock, method="POST")
# ...assert and then skip the 100 response
version, status, reason = response._read_status()
self.assertEqual(status, 100)
while True:
skip = response.fp.readline().strip()
if not skip:
break
# ...send the body
body = ntob("I am a small file")
conn.send(body)
# ...get the final response
response.begin()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(200)
self.assertBody("thanks for '%s'" % body)
conn.close()
def test_No_Message_Body(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Set our HTTP_CONN to an instance so it persists between requests.
self.persistent = True
# Make the first request and assert there's no "Connection: close".
self.getPage("/")
self.assertStatus('200 OK')
self.assertBody(pov)
self.assertNoHeader("Connection")
# Make a 204 request on the same connection.
self.getPage("/custom/204")
self.assertStatus(204)
self.assertNoHeader("Content-Length")
self.assertBody("")
self.assertNoHeader("Connection")
# Make a 304 request on the same connection.
self.getPage("/custom/304")
self.assertStatus(304)
self.assertNoHeader("Content-Length")
self.assertBody("")
self.assertNoHeader("Connection")
def test_Chunked_Encoding(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
if (hasattr(self, 'harness') and
"modpython" in self.harness.__class__.__name__.lower()):
# mod_python forbids chunked encoding
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Set our HTTP_CONN to an instance so it persists between requests.
self.persistent = True
conn = self.HTTP_CONN
# Try a normal chunked request (with extensions)
body = ntob("8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n"
"Content-Type: application/json\r\n"
"\r\n")
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Transfer-Encoding", "chunked")
conn.putheader("Trailer", "Content-Type")
# Note that this is somewhat malformed:
# we shouldn't be sending Content-Length.
# RFC 2616 says the server should ignore it.
conn.putheader("Content-Length", "3")
conn.endheaders()
conn.send(body)
response = conn.getresponse()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus('200 OK')
self.assertBody("thanks for '%s'" % ntob('xx\r\nxxxxyyyyy'))
# Try a chunked request that exceeds server.max_request_body_size.
# Note that the delimiters and trailer are included.
body = ntob("3e3\r\n" + ("x" * 995) + "\r\n0\r\n\r\n")
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Transfer-Encoding", "chunked")
conn.putheader("Content-Type", "text/plain")
# Chunked requests don't need a content-length
## conn.putheader("Content-Length", len(body))
conn.endheaders()
conn.send(body)
response = conn.getresponse()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(413)
conn.close()
def test_Content_Length_in(self):
# Try a non-chunked request where Content-Length exceeds
# server.max_request_body_size. Assert error before body send.
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("POST", "/upload", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader("Content-Type", "text/plain")
conn.putheader("Content-Length", "9999")
conn.endheaders()
response = conn.getresponse()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(413)
self.assertBody("The entity sent with the request exceeds "
"the maximum allowed bytes.")
conn.close()
def test_Content_Length_out_preheaders(self):
# Try a non-chunked response where Content-Length is less than
# the actual bytes in the response body.
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/custom_cl?body=I+have+too+many+bytes&cl=5",
skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.getresponse()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(500)
self.assertBody(
"The requested resource returned more bytes than the "
"declared Content-Length.")
conn.close()
def test_Content_Length_out_postheaders(self):
# Try a non-chunked response where Content-Length is less than
# the actual bytes in the response body.
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/custom_cl?body=I+too&body=+have+too+many&cl=5",
skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.getresponse()
self.status, self.headers, self.body = webtest.shb(response)
self.assertStatus(200)
self.assertBody("I too")
conn.close()
def test_598(self):
remote_data_conn = urlopen('%s://%s:%s/one_megabyte_of_a/' %
(self.scheme, self.HOST, self.PORT,))
buf = remote_data_conn.read(512)
time.sleep(timeout * 0.6)
remaining = (1024 * 1024) - 512
while remaining:
data = remote_data_conn.read(remaining)
if not data:
break
else:
buf += data
remaining -= len(data)
self.assertEqual(len(buf), 1024 * 1024)
self.assertEqual(buf, ntob("a" * 1024 * 1024))
self.assertEqual(remaining, 0)
remote_data_conn.close()
class BadRequestTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_No_CRLF(self):
self.persistent = True
conn = self.HTTP_CONN
conn.send(ntob('GET /hello HTTP/1.1\n\n'))
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.body = response.read()
self.assertBody("HTTP requires CRLF terminators")
conn.close()
conn.connect()
conn.send(ntob('GET /hello HTTP/1.1\r\n\n'))
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.body = response.read()
self.assertBody("HTTP requires CRLF terminators")
conn.close()

View File

@ -1,688 +0,0 @@
"""Basic tests for the CherryPy core: request handling."""
import os
localDir = os.path.dirname(__file__)
import sys
import types
import cherrypy
from cherrypy._cpcompat import IncompleteRead, itervalues, ntob
from cherrypy import _cptools, tools
from cherrypy.lib import httputil, static
favicon_path = os.path.join(os.getcwd(), localDir, "../favicon.ico")
# Client-side code #
from cherrypy.test import helper
class CoreRequestHandlingTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "hello"
index.exposed = True
favicon_ico = tools.staticfile.handler(filename=favicon_path)
def defct(self, newct):
newct = "text/%s" % newct
cherrypy.config.update({'tools.response_headers.on': True,
'tools.response_headers.headers':
[('Content-Type', newct)]})
defct.exposed = True
def baseurl(self, path_info, relative=None):
return cherrypy.url(path_info, relative=bool(relative))
baseurl.exposed = True
root = Root()
if sys.version_info >= (2, 5):
from cherrypy.test._test_decorators import ExposeExamples
root.expose_dec = ExposeExamples()
class TestType(type):
"""Metaclass which automatically exposes all functions in each subclass,
and adds an instance of the subclass as an attribute of root.
"""
def __init__(cls, name, bases, dct):
type.__init__(cls, name, bases, dct)
for value in itervalues(dct):
if isinstance(value, types.FunctionType):
value.exposed = True
setattr(root, name.lower(), cls())
Test = TestType('Test', (object, ), {})
class URL(Test):
_cp_config = {'tools.trailing_slash.on': False}
def index(self, path_info, relative=None):
if relative != 'server':
relative = bool(relative)
return cherrypy.url(path_info, relative=relative)
def leaf(self, path_info, relative=None):
if relative != 'server':
relative = bool(relative)
return cherrypy.url(path_info, relative=relative)
def log_status():
Status.statuses.append(cherrypy.response.status)
cherrypy.tools.log_status = cherrypy.Tool('on_end_resource', log_status)
class Status(Test):
def index(self):
return "normal"
def blank(self):
cherrypy.response.status = ""
# According to RFC 2616, new status codes are OK as long as they
# are between 100 and 599.
# Here is an illegal code...
def illegal(self):
cherrypy.response.status = 781
return "oops"
# ...and here is an unknown but legal code.
def unknown(self):
cherrypy.response.status = "431 My custom error"
return "funky"
# Non-numeric code
def bad(self):
cherrypy.response.status = "error"
return "bad news"
statuses = []
def on_end_resource_stage(self):
return repr(self.statuses)
on_end_resource_stage._cp_config = {'tools.log_status.on': True}
class Redirect(Test):
class Error:
_cp_config = {"tools.err_redirect.on": True,
"tools.err_redirect.url": "/errpage",
"tools.err_redirect.internal": False,
}
def index(self):
raise NameError("redirect_test")
index.exposed = True
error = Error()
def index(self):
return "child"
def custom(self, url, code):
raise cherrypy.HTTPRedirect(url, code)
def by_code(self, code):
raise cherrypy.HTTPRedirect("somewhere%20else", code)
by_code._cp_config = {'tools.trailing_slash.extra': True}
def nomodify(self):
raise cherrypy.HTTPRedirect("", 304)
def proxy(self):
raise cherrypy.HTTPRedirect("proxy", 305)
def stringify(self):
return str(cherrypy.HTTPRedirect("/"))
def fragment(self, frag):
raise cherrypy.HTTPRedirect("/some/url#%s" % frag)
def login_redir():
if not getattr(cherrypy.request, "login", None):
raise cherrypy.InternalRedirect("/internalredirect/login")
tools.login_redir = _cptools.Tool('before_handler', login_redir)
def redir_custom():
raise cherrypy.InternalRedirect("/internalredirect/custom_err")
class InternalRedirect(Test):
def index(self):
raise cherrypy.InternalRedirect("/")
def choke(self):
return 3 / 0
choke.exposed = True
choke._cp_config = {'hooks.before_error_response': redir_custom}
def relative(self, a, b):
raise cherrypy.InternalRedirect("cousin?t=6")
def cousin(self, t):
assert cherrypy.request.prev.closed
return cherrypy.request.prev.query_string
def petshop(self, user_id):
if user_id == "parrot":
# Trade it for a slug when redirecting
raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=slug')
elif user_id == "terrier":
# Trade it for a fish when redirecting
raise cherrypy.InternalRedirect('/image/getImagesByUser?user_id=fish')
else:
# This should pass the user_id through to getImagesByUser
raise cherrypy.InternalRedirect(
'/image/getImagesByUser?user_id=%s' % str(user_id))
# We support Python 2.3, but the @-deco syntax would look like this:
# @tools.login_redir()
def secure(self):
return "Welcome!"
secure = tools.login_redir()(secure)
# Since calling the tool returns the same function you pass in,
# you could skip binding the return value, and just write:
# tools.login_redir()(secure)
def login(self):
return "Please log in"
def custom_err(self):
return "Something went horribly wrong."
def early_ir(self, arg):
return "whatever"
early_ir._cp_config = {'hooks.before_request_body': redir_custom}
class Image(Test):
def getImagesByUser(self, user_id):
return "0 images for %s" % user_id
class Flatten(Test):
def as_string(self):
return "content"
def as_list(self):
return ["con", "tent"]
def as_yield(self):
yield ntob("content")
def as_dblyield(self):
yield self.as_yield()
as_dblyield._cp_config = {'tools.flatten.on': True}
def as_refyield(self):
for chunk in self.as_yield():
yield chunk
class Ranges(Test):
def get_ranges(self, bytes):
return repr(httputil.get_ranges('bytes=%s' % bytes, 8))
def slice_file(self):
path = os.path.join(os.getcwd(), os.path.dirname(__file__))
return static.serve_file(os.path.join(path, "static/index.html"))
class Cookies(Test):
def single(self, name):
cookie = cherrypy.request.cookie[name]
# Python2's SimpleCookie.__setitem__ won't take unicode keys.
cherrypy.response.cookie[str(name)] = cookie.value
def multiple(self, names):
for name in names:
cookie = cherrypy.request.cookie[name]
# Python2's SimpleCookie.__setitem__ won't take unicode keys.
cherrypy.response.cookie[str(name)] = cookie.value
def append_headers(header_list, debug=False):
if debug:
cherrypy.log(
"Extending response headers with %s" % repr(header_list),
"TOOLS.APPEND_HEADERS")
cherrypy.serving.response.header_list.extend(header_list)
cherrypy.tools.append_headers = cherrypy.Tool('on_end_resource', append_headers)
class MultiHeader(Test):
def header_list(self):
pass
header_list = cherrypy.tools.append_headers(header_list=[
(ntob('WWW-Authenticate'), ntob('Negotiate')),
(ntob('WWW-Authenticate'), ntob('Basic realm="foo"')),
])(header_list)
def commas(self):
cherrypy.response.headers['WWW-Authenticate'] = 'Negotiate,Basic realm="foo"'
cherrypy.tree.mount(root)
setup_server = staticmethod(setup_server)
def testStatus(self):
self.getPage("/status/")
self.assertBody('normal')
self.assertStatus(200)
self.getPage("/status/blank")
self.assertBody('')
self.assertStatus(200)
self.getPage("/status/illegal")
self.assertStatus(500)
msg = "Illegal response status from server (781 is out of range)."
self.assertErrorPage(500, msg)
if not getattr(cherrypy.server, 'using_apache', False):
self.getPage("/status/unknown")
self.assertBody('funky')
self.assertStatus(431)
self.getPage("/status/bad")
self.assertStatus(500)
msg = "Illegal response status from server ('error' is non-numeric)."
self.assertErrorPage(500, msg)
def test_on_end_resource_status(self):
self.getPage('/status/on_end_resource_stage')
self.assertBody('[]')
self.getPage('/status/on_end_resource_stage')
self.assertBody(repr(["200 OK"]))
def testSlashes(self):
# Test that requests for index methods without a trailing slash
# get redirected to the same URI path with a trailing slash.
# Make sure GET params are preserved.
self.getPage("/redirect?id=3")
self.assertStatus(301)
self.assertInBody("<a href='%s/redirect/?id=3'>"
"%s/redirect/?id=3</a>" % (self.base(), self.base()))
if self.prefix():
# Corner case: the "trailing slash" redirect could be tricky if
# we're using a virtual root and the URI is "/vroot" (no slash).
self.getPage("")
self.assertStatus(301)
self.assertInBody("<a href='%s/'>%s/</a>" %
(self.base(), self.base()))
# Test that requests for NON-index methods WITH a trailing slash
# get redirected to the same URI path WITHOUT a trailing slash.
# Make sure GET params are preserved.
self.getPage("/redirect/by_code/?code=307")
self.assertStatus(301)
self.assertInBody("<a href='%s/redirect/by_code?code=307'>"
"%s/redirect/by_code?code=307</a>"
% (self.base(), self.base()))
# If the trailing_slash tool is off, CP should just continue
# as if the slashes were correct. But it needs some help
# inside cherrypy.url to form correct output.
self.getPage('/url?path_info=page1')
self.assertBody('%s/url/page1' % self.base())
self.getPage('/url/leaf/?path_info=page1')
self.assertBody('%s/url/page1' % self.base())
def testRedirect(self):
self.getPage("/redirect/")
self.assertBody('child')
self.assertStatus(200)
self.getPage("/redirect/by_code?code=300")
self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
self.assertStatus(300)
self.getPage("/redirect/by_code?code=301")
self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
self.assertStatus(301)
self.getPage("/redirect/by_code?code=302")
self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
self.assertStatus(302)
self.getPage("/redirect/by_code?code=303")
self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
self.assertStatus(303)
self.getPage("/redirect/by_code?code=307")
self.assertMatchesBody(r"<a href='(.*)somewhere%20else'>\1somewhere%20else</a>")
self.assertStatus(307)
self.getPage("/redirect/nomodify")
self.assertBody('')
self.assertStatus(304)
self.getPage("/redirect/proxy")
self.assertBody('')
self.assertStatus(305)
# HTTPRedirect on error
self.getPage("/redirect/error/")
self.assertStatus(('302 Found', '303 See Other'))
self.assertInBody('/errpage')
# Make sure str(HTTPRedirect()) works.
self.getPage("/redirect/stringify", protocol="HTTP/1.0")
self.assertStatus(200)
self.assertBody("(['%s/'], 302)" % self.base())
if cherrypy.server.protocol_version == "HTTP/1.1":
self.getPage("/redirect/stringify", protocol="HTTP/1.1")
self.assertStatus(200)
self.assertBody("(['%s/'], 303)" % self.base())
# check that #fragments are handled properly
# http://skrb.org/ietf/http_errata.html#location-fragments
frag = "foo"
self.getPage("/redirect/fragment/%s" % frag)
self.assertMatchesBody(r"<a href='(.*)\/some\/url\#%s'>\1\/some\/url\#%s</a>" % (frag, frag))
loc = self.assertHeader('Location')
assert loc.endswith("#%s" % frag)
self.assertStatus(('302 Found', '303 See Other'))
# check injection protection
# See http://www.cherrypy.org/ticket/1003
self.getPage("/redirect/custom?code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval")
self.assertStatus(303)
loc = self.assertHeader('Location')
assert 'Set-Cookie' in loc
self.assertNoHeader('Set-Cookie')
def test_InternalRedirect(self):
# InternalRedirect
self.getPage("/internalredirect/")
self.assertBody('hello')
self.assertStatus(200)
# Test passthrough
self.getPage("/internalredirect/petshop?user_id=Sir-not-appearing-in-this-film")
self.assertBody('0 images for Sir-not-appearing-in-this-film')
self.assertStatus(200)
# Test args
self.getPage("/internalredirect/petshop?user_id=parrot")
self.assertBody('0 images for slug')
self.assertStatus(200)
# Test POST
self.getPage("/internalredirect/petshop", method="POST",
body="user_id=terrier")
self.assertBody('0 images for fish')
self.assertStatus(200)
# Test ir before body read
self.getPage("/internalredirect/early_ir", method="POST",
body="arg=aha!")
self.assertBody("Something went horribly wrong.")
self.assertStatus(200)
self.getPage("/internalredirect/secure")
self.assertBody('Please log in')
self.assertStatus(200)
# Relative path in InternalRedirect.
# Also tests request.prev.
self.getPage("/internalredirect/relative?a=3&b=5")
self.assertBody("a=3&b=5")
self.assertStatus(200)
# InternalRedirect on error
self.getPage("/internalredirect/choke")
self.assertStatus(200)
self.assertBody("Something went horribly wrong.")
def testFlatten(self):
for url in ["/flatten/as_string", "/flatten/as_list",
"/flatten/as_yield", "/flatten/as_dblyield",
"/flatten/as_refyield"]:
self.getPage(url)
self.assertBody('content')
def testRanges(self):
self.getPage("/ranges/get_ranges?bytes=3-6")
self.assertBody("[(3, 7)]")
# Test multiple ranges and a suffix-byte-range-spec, for good measure.
self.getPage("/ranges/get_ranges?bytes=2-4,-1")
self.assertBody("[(2, 5), (7, 8)]")
# Get a partial file.
if cherrypy.server.protocol_version == "HTTP/1.1":
self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
self.assertStatus(206)
self.assertHeader("Content-Type", "text/html;charset=utf-8")
self.assertHeader("Content-Range", "bytes 2-5/14")
self.assertBody("llo,")
# What happens with overlapping ranges (and out of order, too)?
self.getPage("/ranges/slice_file", [('Range', 'bytes=4-6,2-5')])
self.assertStatus(206)
ct = self.assertHeader("Content-Type")
expected_type = "multipart/byteranges; boundary="
self.assert_(ct.startswith(expected_type))
boundary = ct[len(expected_type):]
expected_body = ("\r\n--%s\r\n"
"Content-type: text/html\r\n"
"Content-range: bytes 4-6/14\r\n"
"\r\n"
"o, \r\n"
"--%s\r\n"
"Content-type: text/html\r\n"
"Content-range: bytes 2-5/14\r\n"
"\r\n"
"llo,\r\n"
"--%s--\r\n" % (boundary, boundary, boundary))
self.assertBody(expected_body)
self.assertHeader("Content-Length")
# Test "416 Requested Range Not Satisfiable"
self.getPage("/ranges/slice_file", [('Range', 'bytes=2300-2900')])
self.assertStatus(416)
# "When this status code is returned for a byte-range request,
# the response SHOULD include a Content-Range entity-header
# field specifying the current length of the selected resource"
self.assertHeader("Content-Range", "bytes */14")
elif cherrypy.server.protocol_version == "HTTP/1.0":
# Test Range behavior with HTTP/1.0 request
self.getPage("/ranges/slice_file", [('Range', 'bytes=2-5')])
self.assertStatus(200)
self.assertBody("Hello, world\r\n")
def testFavicon(self):
# favicon.ico is served by staticfile.
icofilename = os.path.join(localDir, "../favicon.ico")
icofile = open(icofilename, "rb")
data = icofile.read()
icofile.close()
self.getPage("/favicon.ico")
self.assertBody(data)
def testCookies(self):
if sys.version_info >= (2, 5):
header_value = lambda x: x
else:
header_value = lambda x: x+';'
self.getPage("/cookies/single?name=First",
[('Cookie', 'First=Dinsdale;')])
self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
self.getPage("/cookies/multiple?names=First&names=Last",
[('Cookie', 'First=Dinsdale; Last=Piranha;'),
])
self.assertHeader('Set-Cookie', header_value('First=Dinsdale'))
self.assertHeader('Set-Cookie', header_value('Last=Piranha'))
self.getPage("/cookies/single?name=Something-With:Colon",
[('Cookie', 'Something-With:Colon=some-value')])
self.assertStatus(400)
def testDefaultContentType(self):
self.getPage('/')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.getPage('/defct/plain')
self.getPage('/')
self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
self.getPage('/defct/html')
def test_multiple_headers(self):
self.getPage('/multiheader/header_list')
self.assertEqual([(k, v) for k, v in self.headers if k == 'WWW-Authenticate'],
[('WWW-Authenticate', 'Negotiate'),
('WWW-Authenticate', 'Basic realm="foo"'),
])
self.getPage('/multiheader/commas')
self.assertHeader('WWW-Authenticate', 'Negotiate,Basic realm="foo"')
def test_cherrypy_url(self):
# Input relative to current
self.getPage('/url/leaf?path_info=page1')
self.assertBody('%s/url/page1' % self.base())
self.getPage('/url/?path_info=page1')
self.assertBody('%s/url/page1' % self.base())
# Other host header
host = 'www.mydomain.example'
self.getPage('/url/leaf?path_info=page1',
headers=[('Host', host)])
self.assertBody('%s://%s/url/page1' % (self.scheme, host))
# Input is 'absolute'; that is, relative to script_name
self.getPage('/url/leaf?path_info=/page1')
self.assertBody('%s/page1' % self.base())
self.getPage('/url/?path_info=/page1')
self.assertBody('%s/page1' % self.base())
# Single dots
self.getPage('/url/leaf?path_info=./page1')
self.assertBody('%s/url/page1' % self.base())
self.getPage('/url/leaf?path_info=other/./page1')
self.assertBody('%s/url/other/page1' % self.base())
self.getPage('/url/?path_info=/other/./page1')
self.assertBody('%s/other/page1' % self.base())
# Double dots
self.getPage('/url/leaf?path_info=../page1')
self.assertBody('%s/page1' % self.base())
self.getPage('/url/leaf?path_info=other/../page1')
self.assertBody('%s/url/page1' % self.base())
self.getPage('/url/leaf?path_info=/other/../page1')
self.assertBody('%s/page1' % self.base())
# Output relative to current path or script_name
self.getPage('/url/?path_info=page1&relative=True')
self.assertBody('page1')
self.getPage('/url/leaf?path_info=/page1&relative=True')
self.assertBody('../page1')
self.getPage('/url/leaf?path_info=page1&relative=True')
self.assertBody('page1')
self.getPage('/url/leaf?path_info=leaf/page1&relative=True')
self.assertBody('leaf/page1')
self.getPage('/url/leaf?path_info=../page1&relative=True')
self.assertBody('../page1')
self.getPage('/url/?path_info=other/../page1&relative=True')
self.assertBody('page1')
# Output relative to /
self.getPage('/baseurl?path_info=ab&relative=True')
self.assertBody('ab')
# Output relative to /
self.getPage('/baseurl?path_info=/ab&relative=True')
self.assertBody('ab')
# absolute-path references ("server-relative")
# Input relative to current
self.getPage('/url/leaf?path_info=page1&relative=server')
self.assertBody('/url/page1')
self.getPage('/url/?path_info=page1&relative=server')
self.assertBody('/url/page1')
# Input is 'absolute'; that is, relative to script_name
self.getPage('/url/leaf?path_info=/page1&relative=server')
self.assertBody('/page1')
self.getPage('/url/?path_info=/page1&relative=server')
self.assertBody('/page1')
def test_expose_decorator(self):
if not sys.version_info >= (2, 5):
return self.skip("skipped (Python 2.5+ only) ")
# Test @expose
self.getPage("/expose_dec/no_call")
self.assertStatus(200)
self.assertBody("Mr E. R. Bradshaw")
# Test @expose()
self.getPage("/expose_dec/call_empty")
self.assertStatus(200)
self.assertBody("Mrs. B.J. Smegma")
# Test @expose("alias")
self.getPage("/expose_dec/call_alias")
self.assertStatus(200)
self.assertBody("Mr Nesbitt")
# Does the original name work?
self.getPage("/expose_dec/nesbitt")
self.assertStatus(200)
self.assertBody("Mr Nesbitt")
# Test @expose(["alias1", "alias2"])
self.getPage("/expose_dec/alias1")
self.assertStatus(200)
self.assertBody("Mr Ken Andrews")
self.getPage("/expose_dec/alias2")
self.assertStatus(200)
self.assertBody("Mr Ken Andrews")
# Does the original name work?
self.getPage("/expose_dec/andrews")
self.assertStatus(200)
self.assertBody("Mr Ken Andrews")
# Test @expose(alias="alias")
self.getPage("/expose_dec/alias3")
self.assertStatus(200)
self.assertBody("Mr. and Mrs. Watson")
class ErrorTests(helper.CPWebCase):
def setup_server():
def break_header():
# Add a header after finalize that is invalid
cherrypy.serving.response.header_list.append((2, 3))
cherrypy.tools.break_header = cherrypy.Tool('on_end_resource', break_header)
class Root:
def index(self):
return "hello"
index.exposed = True
def start_response_error(self):
return "salud!"
start_response_error._cp_config = {'tools.break_header.on': True}
root = Root()
cherrypy.tree.mount(root)
setup_server = staticmethod(setup_server)
def test_start_response_error(self):
self.getPage("/start_response_error")
self.assertStatus(500)
self.assertInBody("TypeError: response.header_list key 2 is not a byte string.")

View File

@ -1,404 +0,0 @@
import cherrypy
from cherrypy._cpcompat import sorted, unicodestr
from cherrypy._cptree import Application
from cherrypy.test import helper
script_names = ["", "/foo", "/users/fred/blog", "/corp/blog"]
def setup_server():
class SubSubRoot:
def index(self):
return "SubSubRoot index"
index.exposed = True
def default(self, *args):
return "SubSubRoot default"
default.exposed = True
def handler(self):
return "SubSubRoot handler"
handler.exposed = True
def dispatch(self):
return "SubSubRoot dispatch"
dispatch.exposed = True
subsubnodes = {
'1': SubSubRoot(),
'2': SubSubRoot(),
}
class SubRoot:
def index(self):
return "SubRoot index"
index.exposed = True
def default(self, *args):
return "SubRoot %s" % (args,)
default.exposed = True
def handler(self):
return "SubRoot handler"
handler.exposed = True
def _cp_dispatch(self, vpath):
return subsubnodes.get(vpath[0], None)
subnodes = {
'1': SubRoot(),
'2': SubRoot(),
}
class Root:
def index(self):
return "index"
index.exposed = True
def default(self, *args):
return "default %s" % (args,)
default.exposed = True
def handler(self):
return "handler"
handler.exposed = True
def _cp_dispatch(self, vpath):
return subnodes.get(vpath[0])
#--------------------------------------------------------------------------
# DynamicNodeAndMethodDispatcher example.
# This example exposes a fairly naive HTTP api
class User(object):
def __init__(self, id, name):
self.id = id
self.name = name
def __unicode__(self):
return unicode(self.name)
def __str__(self):
return str(self.name)
user_lookup = {
1: User(1, 'foo'),
2: User(2, 'bar'),
}
def make_user(name, id=None):
if not id:
id = max(*list(user_lookup.keys())) + 1
user_lookup[id] = User(id, name)
return id
class UserContainerNode(object):
exposed = True
def POST(self, name):
"""
Allow the creation of a new Object
"""
return "POST %d" % make_user(name)
def GET(self):
return unicodestr(sorted(user_lookup.keys()))
def dynamic_dispatch(self, vpath):
try:
id = int(vpath[0])
except (ValueError, IndexError):
return None
return UserInstanceNode(id)
class UserInstanceNode(object):
exposed = True
def __init__(self, id):
self.id = id
self.user = user_lookup.get(id, None)
# For all but PUT methods there MUST be a valid user identified
# by self.id
if not self.user and cherrypy.request.method != 'PUT':
raise cherrypy.HTTPError(404)
def GET(self, *args, **kwargs):
"""
Return the appropriate representation of the instance.
"""
return unicodestr(self.user)
def POST(self, name):
"""
Update the fields of the user instance.
"""
self.user.name = name
return "POST %d" % self.user.id
def PUT(self, name):
"""
Create a new user with the specified id, or edit it if it already exists
"""
if self.user:
# Edit the current user
self.user.name = name
return "PUT %d" % self.user.id
else:
# Make a new user with said attributes.
return "PUT %d" % make_user(name, self.id)
def DELETE(self):
"""
Delete the user specified at the id.
"""
id = self.user.id
del user_lookup[self.user.id]
del self.user
return "DELETE %d" % id
class ABHandler:
class CustomDispatch:
def index(self, a, b):
return "custom"
index.exposed = True
def _cp_dispatch(self, vpath):
"""Make sure that if we don't pop anything from vpath,
processing still works.
"""
return self.CustomDispatch()
def index(self, a, b=None):
body = [ 'a:' + str(a) ]
if b is not None:
body.append(',b:' + str(b))
return ''.join(body)
index.exposed = True
def delete(self, a, b):
return 'deleting ' + str(a) + ' and ' + str(b)
delete.exposed = True
class IndexOnly:
def _cp_dispatch(self, vpath):
"""Make sure that popping ALL of vpath still shows the index
handler.
"""
while vpath:
vpath.pop()
return self
def index(self):
return "IndexOnly index"
index.exposed = True
class DecoratedPopArgs:
"""Test _cp_dispatch with @cherrypy.popargs."""
def index(self):
return "no params"
index.exposed = True
def hi(self):
return "hi was not interpreted as 'a' param"
hi.exposed = True
DecoratedPopArgs = cherrypy.popargs('a', 'b', handler=ABHandler())(DecoratedPopArgs)
class NonDecoratedPopArgs:
"""Test _cp_dispatch = cherrypy.popargs()"""
_cp_dispatch = cherrypy.popargs('a')
def index(self, a):
return "index: " + str(a)
index.exposed = True
class ParameterizedHandler:
"""Special handler created for each request"""
def __init__(self, a):
self.a = a
def index(self):
if 'a' in cherrypy.request.params:
raise Exception("Parameterized handler argument ended up in request.params")
return self.a
index.exposed = True
class ParameterizedPopArgs:
"""Test cherrypy.popargs() with a function call handler"""
ParameterizedPopArgs = cherrypy.popargs('a', handler=ParameterizedHandler)(ParameterizedPopArgs)
Root.decorated = DecoratedPopArgs()
Root.undecorated = NonDecoratedPopArgs()
Root.index_only = IndexOnly()
Root.parameter_test = ParameterizedPopArgs()
Root.users = UserContainerNode()
md = cherrypy.dispatch.MethodDispatcher('dynamic_dispatch')
for url in script_names:
conf = {'/': {
'user': (url or "/").split("/")[-2],
},
'/users': {
'request.dispatch': md
},
}
cherrypy.tree.mount(Root(), url, conf)
class DynamicObjectMappingTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testObjectMapping(self):
for url in script_names:
prefix = self.script_name = url
self.getPage('/')
self.assertBody('index')
self.getPage('/handler')
self.assertBody('handler')
# Dynamic dispatch will succeed here for the subnodes
# so the subroot gets called
self.getPage('/1/')
self.assertBody('SubRoot index')
self.getPage('/2/')
self.assertBody('SubRoot index')
self.getPage('/1/handler')
self.assertBody('SubRoot handler')
self.getPage('/2/handler')
self.assertBody('SubRoot handler')
# Dynamic dispatch will fail here for the subnodes
# so the default gets called
self.getPage('/asdf/')
self.assertBody("default ('asdf',)")
self.getPage('/asdf/asdf')
self.assertBody("default ('asdf', 'asdf')")
self.getPage('/asdf/handler')
self.assertBody("default ('asdf', 'handler')")
# Dynamic dispatch will succeed here for the subsubnodes
# so the subsubroot gets called
self.getPage('/1/1/')
self.assertBody('SubSubRoot index')
self.getPage('/2/2/')
self.assertBody('SubSubRoot index')
self.getPage('/1/1/handler')
self.assertBody('SubSubRoot handler')
self.getPage('/2/2/handler')
self.assertBody('SubSubRoot handler')
self.getPage('/2/2/dispatch')
self.assertBody('SubSubRoot dispatch')
# The exposed dispatch will not be called as a dispatch
# method.
self.getPage('/2/2/foo/foo')
self.assertBody("SubSubRoot default")
# Dynamic dispatch will fail here for the subsubnodes
# so the SubRoot gets called
self.getPage('/1/asdf/')
self.assertBody("SubRoot ('asdf',)")
self.getPage('/1/asdf/asdf')
self.assertBody("SubRoot ('asdf', 'asdf')")
self.getPage('/1/asdf/handler')
self.assertBody("SubRoot ('asdf', 'handler')")
def testMethodDispatch(self):
# GET acts like a container
self.getPage("/users")
self.assertBody("[1, 2]")
self.assertHeader('Allow', 'GET, HEAD, POST')
# POST to the container URI allows creation
self.getPage("/users", method="POST", body="name=baz")
self.assertBody("POST 3")
self.assertHeader('Allow', 'GET, HEAD, POST')
# POST to a specific instanct URI results in a 404
# as the resource does not exit.
self.getPage("/users/5", method="POST", body="name=baz")
self.assertStatus(404)
# PUT to a specific instanct URI results in creation
self.getPage("/users/5", method="PUT", body="name=boris")
self.assertBody("PUT 5")
self.assertHeader('Allow', 'DELETE, GET, HEAD, POST, PUT')
# GET acts like a container
self.getPage("/users")
self.assertBody("[1, 2, 3, 5]")
self.assertHeader('Allow', 'GET, HEAD, POST')
test_cases = (
(1, 'foo', 'fooupdated', 'DELETE, GET, HEAD, POST, PUT'),
(2, 'bar', 'barupdated', 'DELETE, GET, HEAD, POST, PUT'),
(3, 'baz', 'bazupdated', 'DELETE, GET, HEAD, POST, PUT'),
(5, 'boris', 'borisupdated', 'DELETE, GET, HEAD, POST, PUT'),
)
for id, name, updatedname, headers in test_cases:
self.getPage("/users/%d" % id)
self.assertBody(name)
self.assertHeader('Allow', headers)
# Make sure POSTs update already existings resources
self.getPage("/users/%d" % id, method='POST', body="name=%s" % updatedname)
self.assertBody("POST %d" % id)
self.assertHeader('Allow', headers)
# Make sure PUTs Update already existing resources.
self.getPage("/users/%d" % id, method='PUT', body="name=%s" % updatedname)
self.assertBody("PUT %d" % id)
self.assertHeader('Allow', headers)
# Make sure DELETES Remove already existing resources.
self.getPage("/users/%d" % id, method='DELETE')
self.assertBody("DELETE %d" % id)
self.assertHeader('Allow', headers)
# GET acts like a container
self.getPage("/users")
self.assertBody("[]")
self.assertHeader('Allow', 'GET, HEAD, POST')
def testVpathDispatch(self):
self.getPage("/decorated/")
self.assertBody("no params")
self.getPage("/decorated/hi")
self.assertBody("hi was not interpreted as 'a' param")
self.getPage("/decorated/yo/")
self.assertBody("a:yo")
self.getPage("/decorated/yo/there/")
self.assertBody("a:yo,b:there")
self.getPage("/decorated/yo/there/delete")
self.assertBody("deleting yo and there")
self.getPage("/decorated/yo/there/handled_by_dispatch/")
self.assertBody("custom")
self.getPage("/undecorated/blah/")
self.assertBody("index: blah")
self.getPage("/index_only/a/b/c/d/e/f/g/")
self.assertBody("IndexOnly index")
self.getPage("/parameter_test/argument2/")
self.assertBody("argument2")

View File

@ -1,363 +0,0 @@
import gzip
import sys
import cherrypy
from cherrypy._cpcompat import BytesIO, IncompleteRead, ntob, ntou
europoundUnicode = ntou('\x80\xa3')
sing = ntou("\u6bdb\u6cfd\u4e1c: Sing, Little Birdie?", 'escape')
sing8 = sing.encode('utf-8')
sing16 = sing.encode('utf-16')
from cherrypy.test import helper
class EncodingTests(helper.CPWebCase):
def setup_server():
class Root:
def index(self, param):
assert param == europoundUnicode, "%r != %r" % (param, europoundUnicode)
yield europoundUnicode
index.exposed = True
def mao_zedong(self):
return sing
mao_zedong.exposed = True
def utf8(self):
return sing8
utf8.exposed = True
utf8._cp_config = {'tools.encode.encoding': 'utf-8'}
def cookies_and_headers(self):
# if the headers have non-ascii characters and a cookie has
# any part which is unicode (even ascii), the response
# should not fail.
cherrypy.response.cookie['candy'] = 'bar'
cherrypy.response.cookie['candy']['domain'] = 'cherrypy.org'
cherrypy.response.headers['Some-Header'] = 'My d\xc3\xb6g has fleas'
return 'Any content'
cookies_and_headers.exposed = True
def reqparams(self, *args, **kwargs):
return ntob(', ').join([": ".join((k, v)).encode('utf8')
for k, v in cherrypy.request.params.items()])
reqparams.exposed = True
def nontext(self, *args, **kwargs):
cherrypy.response.headers['Content-Type'] = 'application/binary'
return '\x00\x01\x02\x03'
nontext.exposed = True
nontext._cp_config = {'tools.encode.text_only': False,
'tools.encode.add_charset': True,
}
class GZIP:
def index(self):
yield "Hello, world"
index.exposed = True
def noshow(self):
# Test for ticket #147, where yield showed no exceptions (content-
# encoding was still gzip even though traceback wasn't zipped).
raise IndexError()
yield "Here be dragons"
noshow.exposed = True
# Turn encoding off so the gzip tool is the one doing the collapse.
noshow._cp_config = {'tools.encode.on': False}
def noshow_stream(self):
# Test for ticket #147, where yield showed no exceptions (content-
# encoding was still gzip even though traceback wasn't zipped).
raise IndexError()
yield "Here be dragons"
noshow_stream.exposed = True
noshow_stream._cp_config = {'response.stream': True}
class Decode:
def extra_charset(self, *args, **kwargs):
return ', '.join([": ".join((k, v))
for k, v in cherrypy.request.params.items()])
extra_charset.exposed = True
extra_charset._cp_config = {
'tools.decode.on': True,
'tools.decode.default_encoding': ['utf-16'],
}
def force_charset(self, *args, **kwargs):
return ', '.join([": ".join((k, v))
for k, v in cherrypy.request.params.items()])
force_charset.exposed = True
force_charset._cp_config = {
'tools.decode.on': True,
'tools.decode.encoding': 'utf-16',
}
root = Root()
root.gzip = GZIP()
root.decode = Decode()
cherrypy.tree.mount(root, config={'/gzip': {'tools.gzip.on': True}})
setup_server = staticmethod(setup_server)
def test_query_string_decoding(self):
europoundUtf8 = europoundUnicode.encode('utf-8')
self.getPage(ntob('/?param=') + europoundUtf8)
self.assertBody(europoundUtf8)
# Encoded utf8 query strings MUST be parsed correctly.
# Here, q is the POUND SIGN U+00A3 encoded in utf8 and then %HEX
self.getPage("/reqparams?q=%C2%A3")
# The return value will be encoded as utf8.
self.assertBody(ntob("q: \xc2\xa3"))
# Query strings that are incorrectly encoded MUST raise 404.
# Here, q is the POUND SIGN U+00A3 encoded in latin1 and then %HEX
self.getPage("/reqparams?q=%A3")
self.assertStatus(404)
self.assertErrorPage(404,
"The given query string could not be processed. Query "
"strings for this resource must be encoded with 'utf8'.")
def test_urlencoded_decoding(self):
# Test the decoding of an application/x-www-form-urlencoded entity.
europoundUtf8 = europoundUnicode.encode('utf-8')
body=ntob("param=") + europoundUtf8
self.getPage('/', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(europoundUtf8)
# Encoded utf8 entities MUST be parsed and decoded correctly.
# Here, q is the POUND SIGN U+00A3 encoded in utf8
body = ntob("q=\xc2\xa3")
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
# ...and in utf16, which is not in the default attempt_charsets list:
body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00")
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded;charset=utf-16"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
# Entities that are incorrectly encoded MUST raise 400.
# Here, q is the POUND SIGN U+00A3 encoded in utf16, but
# the Content-Type incorrectly labels it utf-8.
body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00")
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"),
("Content-Length", str(len(body))),
],
body=body),
self.assertStatus(400)
self.assertErrorPage(400,
"The request entity could not be decoded. The following charsets "
"were attempted: ['utf-8']")
def test_decode_tool(self):
# An extra charset should be tried first, and succeed if it matches.
# Here, we add utf-16 as a charset and pass a utf-16 body.
body = ntob("\xff\xfeq\x00=\xff\xfe\xa3\x00")
self.getPage('/decode/extra_charset', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
# An extra charset should be tried first, and continue to other default
# charsets if it doesn't match.
# Here, we add utf-16 as a charset but still pass a utf-8 body.
body = ntob("q=\xc2\xa3")
self.getPage('/decode/extra_charset', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("q: \xc2\xa3"))
# An extra charset should error if force is True and it doesn't match.
# Here, we force utf-16 as a charset but still pass a utf-8 body.
body = ntob("q=\xc2\xa3")
self.getPage('/decode/force_charset', method='POST',
headers=[("Content-Type", "application/x-www-form-urlencoded"),
("Content-Length", str(len(body))),
],
body=body),
self.assertErrorPage(400,
"The request entity could not be decoded. The following charsets "
"were attempted: ['utf-16']")
def test_multipart_decoding(self):
# Test the decoding of a multipart entity when the charset (utf16) is
# explicitly given.
body=ntob('\r\n'.join(['--X',
'Content-Type: text/plain;charset=utf-16',
'Content-Disposition: form-data; name="text"',
'',
'\xff\xfea\x00b\x00\x1c c\x00',
'--X',
'Content-Type: text/plain;charset=utf-16',
'Content-Disposition: form-data; name="submit"',
'',
'\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00',
'--X--']))
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "multipart/form-data;boundary=X"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("text: ab\xe2\x80\x9cc, submit: Create"))
def test_multipart_decoding_no_charset(self):
# Test the decoding of a multipart entity when the charset (utf8) is
# NOT explicitly given, but is in the list of charsets to attempt.
body=ntob('\r\n'.join(['--X',
'Content-Disposition: form-data; name="text"',
'',
'\xe2\x80\x9c',
'--X',
'Content-Disposition: form-data; name="submit"',
'',
'Create',
'--X--']))
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "multipart/form-data;boundary=X"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(ntob("text: \xe2\x80\x9c, submit: Create"))
def test_multipart_decoding_no_successful_charset(self):
# Test the decoding of a multipart entity when the charset (utf16) is
# NOT explicitly given, and is NOT in the list of charsets to attempt.
body=ntob('\r\n'.join(['--X',
'Content-Disposition: form-data; name="text"',
'',
'\xff\xfea\x00b\x00\x1c c\x00',
'--X',
'Content-Disposition: form-data; name="submit"',
'',
'\xff\xfeC\x00r\x00e\x00a\x00t\x00e\x00',
'--X--']))
self.getPage('/reqparams', method='POST',
headers=[("Content-Type", "multipart/form-data;boundary=X"),
("Content-Length", str(len(body))),
],
body=body),
self.assertStatus(400)
self.assertErrorPage(400,
"The request entity could not be decoded. The following charsets "
"were attempted: ['us-ascii', 'utf-8']")
def test_nontext(self):
self.getPage('/nontext')
self.assertHeader('Content-Type', 'application/binary;charset=utf-8')
self.assertBody('\x00\x01\x02\x03')
def testEncoding(self):
# Default encoding should be utf-8
self.getPage('/mao_zedong')
self.assertBody(sing8)
# Ask for utf-16.
self.getPage('/mao_zedong', [('Accept-Charset', 'utf-16')])
self.assertHeader('Content-Type', 'text/html;charset=utf-16')
self.assertBody(sing16)
# Ask for multiple encodings. ISO-8859-1 should fail, and utf-16
# should be produced.
self.getPage('/mao_zedong', [('Accept-Charset',
'iso-8859-1;q=1, utf-16;q=0.5')])
self.assertBody(sing16)
# The "*" value should default to our default_encoding, utf-8
self.getPage('/mao_zedong', [('Accept-Charset', '*;q=1, utf-7;q=.2')])
self.assertBody(sing8)
# Only allow iso-8859-1, which should fail and raise 406.
self.getPage('/mao_zedong', [('Accept-Charset', 'iso-8859-1, *;q=0')])
self.assertStatus("406 Not Acceptable")
self.assertInBody("Your client sent this Accept-Charset header: "
"iso-8859-1, *;q=0. We tried these charsets: "
"iso-8859-1.")
# Ask for x-mac-ce, which should be unknown. See ticket #569.
self.getPage('/mao_zedong', [('Accept-Charset',
'us-ascii, ISO-8859-1, x-mac-ce')])
self.assertStatus("406 Not Acceptable")
self.assertInBody("Your client sent this Accept-Charset header: "
"us-ascii, ISO-8859-1, x-mac-ce. We tried these "
"charsets: ISO-8859-1, us-ascii, x-mac-ce.")
# Test the 'encoding' arg to encode.
self.getPage('/utf8')
self.assertBody(sing8)
self.getPage('/utf8', [('Accept-Charset', 'us-ascii, ISO-8859-1')])
self.assertStatus("406 Not Acceptable")
def testGzip(self):
zbuf = BytesIO()
zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
zfile.write(ntob("Hello, world"))
zfile.close()
self.getPage('/gzip/', headers=[("Accept-Encoding", "gzip")])
self.assertInBody(zbuf.getvalue()[:3])
self.assertHeader("Vary", "Accept-Encoding")
self.assertHeader("Content-Encoding", "gzip")
# Test when gzip is denied.
self.getPage('/gzip/', headers=[("Accept-Encoding", "identity")])
self.assertHeader("Vary", "Accept-Encoding")
self.assertNoHeader("Content-Encoding")
self.assertBody("Hello, world")
self.getPage('/gzip/', headers=[("Accept-Encoding", "gzip;q=0")])
self.assertHeader("Vary", "Accept-Encoding")
self.assertNoHeader("Content-Encoding")
self.assertBody("Hello, world")
self.getPage('/gzip/', headers=[("Accept-Encoding", "*;q=0")])
self.assertStatus(406)
self.assertNoHeader("Content-Encoding")
self.assertErrorPage(406, "identity, gzip")
# Test for ticket #147
self.getPage('/gzip/noshow', headers=[("Accept-Encoding", "gzip")])
self.assertNoHeader('Content-Encoding')
self.assertStatus(500)
self.assertErrorPage(500, pattern="IndexError\n")
# In this case, there's nothing we can do to deliver a
# readable page, since 1) the gzip header is already set,
# and 2) we may have already written some of the body.
# The fix is to never stream yields when using gzip.
if (cherrypy.server.protocol_version == "HTTP/1.0" or
getattr(cherrypy.server, "using_apache", False)):
self.getPage('/gzip/noshow_stream',
headers=[("Accept-Encoding", "gzip")])
self.assertHeader('Content-Encoding', 'gzip')
self.assertInBody('\x1f\x8b\x08\x00')
else:
# The wsgiserver will simply stop sending data, and the HTTP client
# will error due to an incomplete chunk-encoded stream.
self.assertRaises((ValueError, IncompleteRead), self.getPage,
'/gzip/noshow_stream',
headers=[("Accept-Encoding", "gzip")])
def test_UnicodeHeaders(self):
self.getPage('/cookies_and_headers')
self.assertBody('Any content')

View File

@ -1,83 +0,0 @@
import cherrypy
from cherrypy._cpcompat import ntou
from cherrypy.test import helper
class ETagTest(helper.CPWebCase):
def setup_server():
class Root:
def resource(self):
return "Oh wah ta goo Siam."
resource.exposed = True
def fail(self, code):
code = int(code)
if 300 <= code <= 399:
raise cherrypy.HTTPRedirect([], code)
else:
raise cherrypy.HTTPError(code)
fail.exposed = True
def unicoded(self):
return ntou('I am a \u1ee4nicode string.', 'escape')
unicoded.exposed = True
# In Python 3, tools.encode is on by default
unicoded._cp_config = {'tools.encode.on': True}
conf = {'/': {'tools.etags.on': True,
'tools.etags.autotags': True,
}}
cherrypy.tree.mount(Root(), config=conf)
setup_server = staticmethod(setup_server)
def test_etags(self):
self.getPage("/resource")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.assertBody('Oh wah ta goo Siam.')
etag = self.assertHeader('ETag')
# Test If-Match (both valid and invalid)
self.getPage("/resource", headers=[('If-Match', etag)])
self.assertStatus("200 OK")
self.getPage("/resource", headers=[('If-Match', "*")])
self.assertStatus("200 OK")
self.getPage("/resource", headers=[('If-Match', "*")], method="POST")
self.assertStatus("200 OK")
self.getPage("/resource", headers=[('If-Match', "a bogus tag")])
self.assertStatus("412 Precondition Failed")
# Test If-None-Match (both valid and invalid)
self.getPage("/resource", headers=[('If-None-Match', etag)])
self.assertStatus(304)
self.getPage("/resource", method='POST', headers=[('If-None-Match', etag)])
self.assertStatus("412 Precondition Failed")
self.getPage("/resource", headers=[('If-None-Match', "*")])
self.assertStatus(304)
self.getPage("/resource", headers=[('If-None-Match', "a bogus tag")])
self.assertStatus("200 OK")
def test_errors(self):
self.getPage("/resource")
self.assertStatus(200)
etag = self.assertHeader('ETag')
# Test raising errors in page handler
self.getPage("/fail/412", headers=[('If-Match', etag)])
self.assertStatus(412)
self.getPage("/fail/304", headers=[('If-Match', etag)])
self.assertStatus(304)
self.getPage("/fail/412", headers=[('If-None-Match', "*")])
self.assertStatus(412)
self.getPage("/fail/304", headers=[('If-None-Match', "*")])
self.assertStatus(304)
def test_unicode_body(self):
self.getPage("/unicoded")
self.assertStatus(200)
etag1 = self.assertHeader('ETag')
self.getPage("/unicoded", headers=[('If-Match', etag1)])
self.assertStatus(200)
self.assertHeader('ETag', etag1)

View File

@ -1,212 +0,0 @@
"""Tests for managing HTTP issues (malformed requests, etc)."""
import errno
import mimetypes
import socket
import sys
import cherrypy
from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob, py3k
def encode_multipart_formdata(files):
"""Return (content_type, body) ready for httplib.HTTP instance.
files: a sequence of (name, filename, value) tuples for multipart uploads.
"""
BOUNDARY = '________ThIs_Is_tHe_bouNdaRY_$'
L = []
for key, filename, value in files:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' %
(key, filename))
ct = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
L.append('Content-Type: %s' % ct)
L.append('')
L.append(value)
L.append('--' + BOUNDARY + '--')
L.append('')
body = '\r\n'.join(L)
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
from cherrypy.test import helper
class HTTPTests(helper.CPWebCase):
def setup_server():
class Root:
def index(self, *args, **kwargs):
return "Hello world!"
index.exposed = True
def no_body(self, *args, **kwargs):
return "Hello world!"
no_body.exposed = True
no_body._cp_config = {'request.process_request_body': False}
def post_multipart(self, file):
"""Return a summary ("a * 65536\nb * 65536") of the uploaded file."""
contents = file.file.read()
summary = []
curchar = None
count = 0
for c in contents:
if c == curchar:
count += 1
else:
if count:
if py3k: curchar = chr(curchar)
summary.append("%s * %d" % (curchar, count))
count = 1
curchar = c
if count:
if py3k: curchar = chr(curchar)
summary.append("%s * %d" % (curchar, count))
return ", ".join(summary)
post_multipart.exposed = True
cherrypy.tree.mount(Root())
cherrypy.config.update({'server.max_request_body_size': 30000000})
setup_server = staticmethod(setup_server)
def test_no_content_length(self):
# "The presence of a message-body in a request is signaled by the
# inclusion of a Content-Length or Transfer-Encoding header field in
# the request's message-headers."
#
# Send a message with neither header and no body. Even though
# the request is of method POST, this should be OK because we set
# request.process_request_body to False for our handler.
if self.scheme == "https":
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c.request("POST", "/no_body")
response = c.getresponse()
self.body = response.fp.read()
self.status = str(response.status)
self.assertStatus(200)
self.assertBody(ntob('Hello world!'))
# Now send a message that has no Content-Length, but does send a body.
# Verify that CP times out the socket and responds
# with 411 Length Required.
if self.scheme == "https":
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c.request("POST", "/")
response = c.getresponse()
self.body = response.fp.read()
self.status = str(response.status)
self.assertStatus(411)
def test_post_multipart(self):
alphabet = "abcdefghijklmnopqrstuvwxyz"
# generate file contents for a large post
contents = "".join([c * 65536 for c in alphabet])
# encode as multipart form data
files=[('file', 'file.txt', contents)]
content_type, body = encode_multipart_formdata(files)
body = body.encode('Latin-1')
# post file
if self.scheme == 'https':
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c.putrequest('POST', '/post_multipart')
c.putheader('Content-Type', content_type)
c.putheader('Content-Length', str(len(body)))
c.endheaders()
c.send(body)
response = c.getresponse()
self.body = response.fp.read()
self.status = str(response.status)
self.assertStatus(200)
self.assertBody(", ".join(["%s * 65536" % c for c in alphabet]))
def test_malformed_request_line(self):
if getattr(cherrypy.server, "using_apache", False):
return self.skip("skipped due to known Apache differences...")
# Test missing version in Request-Line
if self.scheme == 'https':
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c._output(ntob('GET /'))
c._send_output()
if hasattr(c, 'strict'):
response = c.response_class(c.sock, strict=c.strict, method='GET')
else:
# Python 3.2 removed the 'strict' feature, saying:
# "http.client now always assumes HTTP/1.x compliant servers."
response = c.response_class(c.sock, method='GET')
response.begin()
self.assertEqual(response.status, 400)
self.assertEqual(response.fp.read(22), ntob("Malformed Request-Line"))
c.close()
def test_malformed_header(self):
if self.scheme == 'https':
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c.putrequest('GET', '/')
c.putheader('Content-Type', 'text/plain')
# See http://www.cherrypy.org/ticket/941
c._output(ntob('Re, 1.2.3.4#015#012'))
c.endheaders()
response = c.getresponse()
self.status = str(response.status)
self.assertStatus(400)
self.body = response.fp.read(20)
self.assertBody("Illegal header line.")
def test_http_over_https(self):
if self.scheme != 'https':
return self.skip("skipped (not running HTTPS)... ")
# Try connecting without SSL.
conn = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
conn.putrequest("GET", "/", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
try:
response.begin()
self.assertEqual(response.status, 400)
self.body = response.read()
self.assertBody("The client sent a plain HTTP request, but this "
"server only speaks HTTPS on this port.")
except socket.error:
e = sys.exc_info()[1]
# "Connection reset by peer" is also acceptable.
if e.errno != errno.ECONNRESET:
raise
def test_garbage_in(self):
# Connect without SSL regardless of server.scheme
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
c._output(ntob('gjkgjklsgjklsgjkljklsg'))
c._send_output()
response = c.response_class(c.sock, method="GET")
try:
response.begin()
self.assertEqual(response.status, 400)
self.assertEqual(response.fp.read(22), ntob("Malformed Request-Line"))
c.close()
except socket.error:
e = sys.exc_info()[1]
# "Connection reset by peer" is also acceptable.
if e.errno != errno.ECONNRESET:
raise

View File

@ -1,151 +0,0 @@
import cherrypy
from cherrypy._cpcompat import md5, sha, ntob
from cherrypy.lib import httpauth
from cherrypy.test import helper
class HTTPAuthTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "This is public."
index.exposed = True
class DigestProtected:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
class BasicProtected:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
class BasicProtected2:
def index(self):
return "Hello %s, you've been authorized." % cherrypy.request.login
index.exposed = True
def fetch_users():
return {'test': 'test'}
def sha_password_encrypter(password):
return sha(ntob(password)).hexdigest()
def fetch_password(username):
return sha(ntob('test')).hexdigest()
conf = {'/digest': {'tools.digest_auth.on': True,
'tools.digest_auth.realm': 'localhost',
'tools.digest_auth.users': fetch_users},
'/basic': {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'localhost',
'tools.basic_auth.users': {'test': md5(ntob('test')).hexdigest()}},
'/basic2': {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'localhost',
'tools.basic_auth.users': fetch_password,
'tools.basic_auth.encrypt': sha_password_encrypter}}
root = Root()
root.digest = DigestProtected()
root.basic = BasicProtected()
root.basic2 = BasicProtected2()
cherrypy.tree.mount(root, config=conf)
setup_server = staticmethod(setup_server)
def testPublic(self):
self.getPage("/")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.assertBody('This is public.')
def testBasic(self):
self.getPage("/basic/")
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="localhost"')
self.getPage('/basic/', [('Authorization', 'Basic dGVzdDp0ZX60')])
self.assertStatus(401)
self.getPage('/basic/', [('Authorization', 'Basic dGVzdDp0ZXN0')])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")
def testBasic2(self):
self.getPage("/basic2/")
self.assertStatus(401)
self.assertHeader('WWW-Authenticate', 'Basic realm="localhost"')
self.getPage('/basic2/', [('Authorization', 'Basic dGVzdDp0ZX60')])
self.assertStatus(401)
self.getPage('/basic2/', [('Authorization', 'Basic dGVzdDp0ZXN0')])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")
def testDigest(self):
self.getPage("/digest/")
self.assertStatus(401)
value = None
for k, v in self.headers:
if k.lower() == "www-authenticate":
if v.startswith("Digest"):
value = v
break
if value is None:
self._handlewebError("Digest authentification scheme was not found")
value = value[7:]
items = value.split(', ')
tokens = {}
for item in items:
key, value = item.split('=')
tokens[key.lower()] = value
missing_msg = "%s is missing"
bad_value_msg = "'%s' was expecting '%s' but found '%s'"
nonce = None
if 'realm' not in tokens:
self._handlewebError(missing_msg % 'realm')
elif tokens['realm'] != '"localhost"':
self._handlewebError(bad_value_msg % ('realm', '"localhost"', tokens['realm']))
if 'nonce' not in tokens:
self._handlewebError(missing_msg % 'nonce')
else:
nonce = tokens['nonce'].strip('"')
if 'algorithm' not in tokens:
self._handlewebError(missing_msg % 'algorithm')
elif tokens['algorithm'] != '"MD5"':
self._handlewebError(bad_value_msg % ('algorithm', '"MD5"', tokens['algorithm']))
if 'qop' not in tokens:
self._handlewebError(missing_msg % 'qop')
elif tokens['qop'] != '"auth"':
self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
# Test a wrong 'realm' value
base_auth = 'Digest username="test", realm="wrong realm", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
auth = base_auth % (nonce, '', '00000001')
params = httpauth.parseAuthorization(auth)
response = httpauth._computeDigestResponse(params, 'test')
auth = base_auth % (nonce, response, '00000001')
self.getPage('/digest/', [('Authorization', auth)])
self.assertStatus(401)
# Test that must pass
base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
auth = base_auth % (nonce, '', '00000001')
params = httpauth.parseAuthorization(auth)
response = httpauth._computeDigestResponse(params, 'test')
auth = base_auth % (nonce, response, '00000001')
self.getPage('/digest/', [('Authorization', auth)])
self.assertStatus('200 OK')
self.assertBody("Hello test, you've been authorized.")

View File

@ -1,29 +0,0 @@
"""Tests for cherrypy/lib/httputil.py."""
import unittest
from cherrypy.lib import httputil
class UtilityTests(unittest.TestCase):
def test_urljoin(self):
# Test all slash+atom combinations for SCRIPT_NAME and PATH_INFO
self.assertEqual(httputil.urljoin("/sn/", "/pi/"), "/sn/pi/")
self.assertEqual(httputil.urljoin("/sn/", "/pi"), "/sn/pi")
self.assertEqual(httputil.urljoin("/sn/", "/"), "/sn/")
self.assertEqual(httputil.urljoin("/sn/", ""), "/sn/")
self.assertEqual(httputil.urljoin("/sn", "/pi/"), "/sn/pi/")
self.assertEqual(httputil.urljoin("/sn", "/pi"), "/sn/pi")
self.assertEqual(httputil.urljoin("/sn", "/"), "/sn/")
self.assertEqual(httputil.urljoin("/sn", ""), "/sn")
self.assertEqual(httputil.urljoin("/", "/pi/"), "/pi/")
self.assertEqual(httputil.urljoin("/", "/pi"), "/pi")
self.assertEqual(httputil.urljoin("/", "/"), "/")
self.assertEqual(httputil.urljoin("/", ""), "/")
self.assertEqual(httputil.urljoin("", "/pi/"), "/pi/")
self.assertEqual(httputil.urljoin("", "/pi"), "/pi")
self.assertEqual(httputil.urljoin("", "/"), "/")
self.assertEqual(httputil.urljoin("", ""), "/")
if __name__ == '__main__':
unittest.main()

View File

@ -1,79 +0,0 @@
import cherrypy
from cherrypy.test import helper
from cherrypy._cpcompat import json
class JsonTest(helper.CPWebCase):
def setup_server():
class Root(object):
def plain(self):
return 'hello'
plain.exposed = True
def json_string(self):
return 'hello'
json_string.exposed = True
json_string._cp_config = {'tools.json_out.on': True}
def json_list(self):
return ['a', 'b', 42]
json_list.exposed = True
json_list._cp_config = {'tools.json_out.on': True}
def json_dict(self):
return {'answer': 42}
json_dict.exposed = True
json_dict._cp_config = {'tools.json_out.on': True}
def json_post(self):
if cherrypy.request.json == [13, 'c']:
return 'ok'
else:
return 'nok'
json_post.exposed = True
json_post._cp_config = {'tools.json_in.on': True}
root = Root()
cherrypy.tree.mount(root)
setup_server = staticmethod(setup_server)
def test_json_output(self):
if json is None:
self.skip("json not found ")
return
self.getPage("/plain")
self.assertBody("hello")
self.getPage("/json_string")
self.assertBody('"hello"')
self.getPage("/json_list")
self.assertBody('["a", "b", 42]')
self.getPage("/json_dict")
self.assertBody('{"answer": 42}')
def test_json_input(self):
if json is None:
self.skip("json not found ")
return
body = '[13, "c"]'
headers = [('Content-Type', 'application/json'),
('Content-Length', str(len(body)))]
self.getPage("/json_post", method="POST", headers=headers, body=body)
self.assertBody('ok')
body = '[13, "c"]'
headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(body)))]
self.getPage("/json_post", method="POST", headers=headers, body=body)
self.assertStatus(415, 'Expected an application/json content type')
body = '[13, -]'
headers = [('Content-Type', 'application/json'),
('Content-Length', str(len(body)))]
self.getPage("/json_post", method="POST", headers=headers, body=body)
self.assertStatus(400, 'Invalid JSON document')

View File

@ -1,157 +0,0 @@
"""Basic tests for the CherryPy core: request handling."""
import os
localDir = os.path.dirname(__file__)
import cherrypy
from cherrypy._cpcompat import ntob, ntou, py3k
access_log = os.path.join(localDir, "access.log")
error_log = os.path.join(localDir, "error.log")
# Some unicode strings.
tartaros = ntou('\u03a4\u1f71\u03c1\u03c4\u03b1\u03c1\u03bf\u03c2', 'escape')
erebos = ntou('\u0388\u03c1\u03b5\u03b2\u03bf\u03c2.com', 'escape')
def setup_server():
class Root:
def index(self):
return "hello"
index.exposed = True
def uni_code(self):
cherrypy.request.login = tartaros
cherrypy.request.remote.name = erebos
uni_code.exposed = True
def slashes(self):
cherrypy.request.request_line = r'GET /slashed\path HTTP/1.1'
slashes.exposed = True
def whitespace(self):
# User-Agent = "User-Agent" ":" 1*( product | comment )
# comment = "(" *( ctext | quoted-pair | comment ) ")"
# ctext = <any TEXT excluding "(" and ")">
# TEXT = <any OCTET except CTLs, but including LWS>
# LWS = [CRLF] 1*( SP | HT )
cherrypy.request.headers['User-Agent'] = 'Browzuh (1.0\r\n\t\t.3)'
whitespace.exposed = True
def as_string(self):
return "content"
as_string.exposed = True
def as_yield(self):
yield "content"
as_yield.exposed = True
def error(self):
raise ValueError()
error.exposed = True
error._cp_config = {'tools.log_tracebacks.on': True}
root = Root()
cherrypy.config.update({'log.error_file': error_log,
'log.access_file': access_log,
})
cherrypy.tree.mount(root)
from cherrypy.test import helper, logtest
class AccessLogTests(helper.CPWebCase, logtest.LogCase):
setup_server = staticmethod(setup_server)
logfile = access_log
def testNormalReturn(self):
self.markLog()
self.getPage("/as_string",
headers=[('Referer', 'http://www.cherrypy.org/'),
('User-Agent', 'Mozilla/5.0')])
self.assertBody('content')
self.assertStatus(200)
intro = '%s - - [' % self.interface()
self.assertLog(-1, intro)
if [k for k, v in self.headers if k.lower() == 'content-length']:
self.assertLog(-1, '] "GET %s/as_string HTTP/1.1" 200 7 '
'"http://www.cherrypy.org/" "Mozilla/5.0"'
% self.prefix())
else:
self.assertLog(-1, '] "GET %s/as_string HTTP/1.1" 200 - '
'"http://www.cherrypy.org/" "Mozilla/5.0"'
% self.prefix())
def testNormalYield(self):
self.markLog()
self.getPage("/as_yield")
self.assertBody('content')
self.assertStatus(200)
intro = '%s - - [' % self.interface()
self.assertLog(-1, intro)
if [k for k, v in self.headers if k.lower() == 'content-length']:
self.assertLog(-1, '] "GET %s/as_yield HTTP/1.1" 200 7 "" ""' %
self.prefix())
else:
self.assertLog(-1, '] "GET %s/as_yield HTTP/1.1" 200 - "" ""'
% self.prefix())
def testEscapedOutput(self):
# Test unicode in access log pieces.
self.markLog()
self.getPage("/uni_code")
self.assertStatus(200)
if py3k:
# The repr of a bytestring in py3k includes a b'' prefix
self.assertLog(-1, repr(tartaros.encode('utf8'))[2:-1])
else:
self.assertLog(-1, repr(tartaros.encode('utf8'))[1:-1])
# Test the erebos value. Included inline for your enlightenment.
# Note the 'r' prefix--those backslashes are literals.
self.assertLog(-1, r'\xce\x88\xcf\x81\xce\xb5\xce\xb2\xce\xbf\xcf\x82')
# Test backslashes in output.
self.markLog()
self.getPage("/slashes")
self.assertStatus(200)
if py3k:
self.assertLog(-1, ntob('"GET /slashed\\path HTTP/1.1"'))
else:
self.assertLog(-1, r'"GET /slashed\\path HTTP/1.1"')
# Test whitespace in output.
self.markLog()
self.getPage("/whitespace")
self.assertStatus(200)
# Again, note the 'r' prefix.
self.assertLog(-1, r'"Browzuh (1.0\r\n\t\t.3)"')
class ErrorLogTests(helper.CPWebCase, logtest.LogCase):
setup_server = staticmethod(setup_server)
logfile = error_log
def testTracebacks(self):
# Test that tracebacks get written to the error log.
self.markLog()
ignore = helper.webtest.ignored_exceptions
ignore.append(ValueError)
try:
self.getPage("/error")
self.assertInBody("raise ValueError()")
self.assertLog(0, 'HTTP Traceback (most recent call last):')
self.assertLog(-3, 'raise ValueError()')
finally:
ignore.pop()

View File

@ -1,128 +0,0 @@
"""Tests for various MIME issues, including the safe_multipart Tool."""
import cherrypy
from cherrypy._cpcompat import ntob, ntou, sorted
def setup_server():
class Root:
def multipart(self, parts):
return repr(parts)
multipart.exposed = True
def multipart_form_data(self, **kwargs):
return repr(list(sorted(kwargs.items())))
multipart_form_data.exposed = True
def flashupload(self, Filedata, Upload, Filename):
return ("Upload: %s, Filename: %s, Filedata: %r" %
(Upload, Filename, Filedata.file.read()))
flashupload.exposed = True
cherrypy.config.update({'server.max_request_body_size': 0})
cherrypy.tree.mount(Root())
# Client-side code #
from cherrypy.test import helper
class MultipartTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_multipart(self):
text_part = ntou("This is the text version")
html_part = ntou("""<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html;charset=ISO-8859-1" http-equiv="Content-Type">
</head>
<body bgcolor="#ffffff" text="#000000">
This is the <strong>HTML</strong> version
</body>
</html>
""")
body = '\r\n'.join([
"--123456789",
"Content-Type: text/plain; charset='ISO-8859-1'",
"Content-Transfer-Encoding: 7bit",
"",
text_part,
"--123456789",
"Content-Type: text/html; charset='ISO-8859-1'",
"",
html_part,
"--123456789--"])
headers = [
('Content-Type', 'multipart/mixed; boundary=123456789'),
('Content-Length', str(len(body))),
]
self.getPage('/multipart', headers, "POST", body)
self.assertBody(repr([text_part, html_part]))
def test_multipart_form_data(self):
body='\r\n'.join(['--X',
'Content-Disposition: form-data; name="foo"',
'',
'bar',
'--X',
# Test a param with more than one value.
# See http://www.cherrypy.org/ticket/1028
'Content-Disposition: form-data; name="baz"',
'',
'111',
'--X',
'Content-Disposition: form-data; name="baz"',
'',
'333',
'--X--'])
self.getPage('/multipart_form_data', method='POST',
headers=[("Content-Type", "multipart/form-data;boundary=X"),
("Content-Length", str(len(body))),
],
body=body),
self.assertBody(repr([('baz', [ntou('111'), ntou('333')]), ('foo', ntou('bar'))]))
class SafeMultipartHandlingTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_Flash_Upload(self):
headers = [
('Accept', 'text/*'),
('Content-Type', 'multipart/form-data; '
'boundary=----------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6'),
('User-Agent', 'Shockwave Flash'),
('Host', 'www.example.com:54583'),
('Content-Length', '499'),
('Connection', 'Keep-Alive'),
('Cache-Control', 'no-cache'),
]
filedata = ntob('<?xml version="1.0" encoding="UTF-8"?>\r\n'
'<projectDescription>\r\n'
'</projectDescription>\r\n')
body = (ntob(
'------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6\r\n'
'Content-Disposition: form-data; name="Filename"\r\n'
'\r\n'
'.project\r\n'
'------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6\r\n'
'Content-Disposition: form-data; '
'name="Filedata"; filename=".project"\r\n'
'Content-Type: application/octet-stream\r\n'
'\r\n')
+ filedata +
ntob('\r\n'
'------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6\r\n'
'Content-Disposition: form-data; name="Upload"\r\n'
'\r\n'
'Submit Query\r\n'
# Flash apps omit the trailing \r\n on the last line:
'------------KM7Ij5cH2KM7Ef1gL6ae0ae0cH2gL6--'
))
self.getPage('/flashupload', headers, "POST", body)
self.assertBody("Upload: Submit Query, Filename: .project, "
"Filedata: %r" % filedata)

View File

@ -1,207 +0,0 @@
import os
localDir = os.path.dirname(__file__)
logfile = os.path.join(localDir, "test_misc_tools.log")
import cherrypy
from cherrypy import tools
def setup_server():
class Root:
def index(self):
yield "Hello, world"
index.exposed = True
h = [("Content-Language", "en-GB"), ('Content-Type', 'text/plain')]
tools.response_headers(headers=h)(index)
def other(self):
return "salut"
other.exposed = True
other._cp_config = {
'tools.response_headers.on': True,
'tools.response_headers.headers': [("Content-Language", "fr"),
('Content-Type', 'text/plain')],
'tools.log_hooks.on': True,
}
class Accept:
_cp_config = {'tools.accept.on': True}
def index(self):
return '<a href="feed">Atom feed</a>'
index.exposed = True
# In Python 2.4+, we could use a decorator instead:
# @tools.accept('application/atom+xml')
def feed(self):
return """<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Unknown Blog</title>
</feed>"""
feed.exposed = True
feed._cp_config = {'tools.accept.media': 'application/atom+xml'}
def select(self):
# We could also write this: mtype = cherrypy.lib.accept.accept(...)
mtype = tools.accept.callable(['text/html', 'text/plain'])
if mtype == 'text/html':
return "<h2>Page Title</h2>"
else:
return "PAGE TITLE"
select.exposed = True
class Referer:
def accept(self):
return "Accepted!"
accept.exposed = True
reject = accept
class AutoVary:
def index(self):
# Read a header directly with 'get'
ae = cherrypy.request.headers.get('Accept-Encoding')
# Read a header directly with '__getitem__'
cl = cherrypy.request.headers['Host']
# Read a header directly with '__contains__'
hasif = 'If-Modified-Since' in cherrypy.request.headers
# Read a header directly with 'has_key'
if hasattr(dict, 'has_key'):
# Python 2
has = cherrypy.request.headers.has_key('Range')
else:
# Python 3
has = 'Range' in cherrypy.request.headers
# Call a lib function
mtype = tools.accept.callable(['text/html', 'text/plain'])
return "Hello, world!"
index.exposed = True
conf = {'/referer': {'tools.referer.on': True,
'tools.referer.pattern': r'http://[^/]*example\.com',
},
'/referer/reject': {'tools.referer.accept': False,
'tools.referer.accept_missing': True,
},
'/autovary': {'tools.autovary.on': True},
}
root = Root()
root.referer = Referer()
root.accept = Accept()
root.autovary = AutoVary()
cherrypy.tree.mount(root, config=conf)
cherrypy.config.update({'log.error_file': logfile})
from cherrypy.test import helper
class ResponseHeadersTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testResponseHeadersDecorator(self):
self.getPage('/')
self.assertHeader("Content-Language", "en-GB")
self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
def testResponseHeaders(self):
self.getPage('/other')
self.assertHeader("Content-Language", "fr")
self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
class RefererTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testReferer(self):
self.getPage('/referer/accept')
self.assertErrorPage(403, 'Forbidden Referer header.')
self.getPage('/referer/accept',
headers=[('Referer', 'http://www.example.com/')])
self.assertStatus(200)
self.assertBody('Accepted!')
# Reject
self.getPage('/referer/reject')
self.assertStatus(200)
self.assertBody('Accepted!')
self.getPage('/referer/reject',
headers=[('Referer', 'http://www.example.com/')])
self.assertErrorPage(403, 'Forbidden Referer header.')
class AcceptTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_Accept_Tool(self):
# Test with no header provided
self.getPage('/accept/feed')
self.assertStatus(200)
self.assertInBody('<title>Unknown Blog</title>')
# Specify exact media type
self.getPage('/accept/feed', headers=[('Accept', 'application/atom+xml')])
self.assertStatus(200)
self.assertInBody('<title>Unknown Blog</title>')
# Specify matching media range
self.getPage('/accept/feed', headers=[('Accept', 'application/*')])
self.assertStatus(200)
self.assertInBody('<title>Unknown Blog</title>')
# Specify all media ranges
self.getPage('/accept/feed', headers=[('Accept', '*/*')])
self.assertStatus(200)
self.assertInBody('<title>Unknown Blog</title>')
# Specify unacceptable media types
self.getPage('/accept/feed', headers=[('Accept', 'text/html')])
self.assertErrorPage(406,
"Your client sent this Accept header: text/html. "
"But this resource only emits these media types: "
"application/atom+xml.")
# Test resource where tool is 'on' but media is None (not set).
self.getPage('/accept/')
self.assertStatus(200)
self.assertBody('<a href="feed">Atom feed</a>')
def test_accept_selection(self):
# Try both our expected media types
self.getPage('/accept/select', [('Accept', 'text/html')])
self.assertStatus(200)
self.assertBody('<h2>Page Title</h2>')
self.getPage('/accept/select', [('Accept', 'text/plain')])
self.assertStatus(200)
self.assertBody('PAGE TITLE')
self.getPage('/accept/select', [('Accept', 'text/plain, text/*;q=0.5')])
self.assertStatus(200)
self.assertBody('PAGE TITLE')
# text/* and */* should prefer text/html since it comes first
# in our 'media' argument to tools.accept
self.getPage('/accept/select', [('Accept', 'text/*')])
self.assertStatus(200)
self.assertBody('<h2>Page Title</h2>')
self.getPage('/accept/select', [('Accept', '*/*')])
self.assertStatus(200)
self.assertBody('<h2>Page Title</h2>')
# Try unacceptable media types
self.getPage('/accept/select', [('Accept', 'application/xml')])
self.assertErrorPage(406,
"Your client sent this Accept header: application/xml. "
"But this resource only emits these media types: "
"text/html, text/plain.")
class AutoVaryTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testAutoVary(self):
self.getPage('/autovary/')
self.assertHeader(
"Vary", 'Accept, Accept-Charset, Accept-Encoding, Host, If-Modified-Since, Range')

View File

@ -1,404 +0,0 @@
import cherrypy
from cherrypy._cpcompat import ntou
from cherrypy._cptree import Application
from cherrypy.test import helper
script_names = ["", "/foo", "/users/fred/blog", "/corp/blog"]
class ObjectMappingTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self, name="world"):
return name
index.exposed = True
def foobar(self):
return "bar"
foobar.exposed = True
def default(self, *params, **kwargs):
return "default:" + repr(params)
default.exposed = True
def other(self):
return "other"
other.exposed = True
def extra(self, *p):
return repr(p)
extra.exposed = True
def redirect(self):
raise cherrypy.HTTPRedirect('dir1/', 302)
redirect.exposed = True
def notExposed(self):
return "not exposed"
def confvalue(self):
return cherrypy.request.config.get("user")
confvalue.exposed = True
def redirect_via_url(self, path):
raise cherrypy.HTTPRedirect(cherrypy.url(path))
redirect_via_url.exposed = True
def translate_html(self):
return "OK"
translate_html.exposed = True
def mapped_func(self, ID=None):
return "ID is %s" % ID
mapped_func.exposed = True
setattr(Root, "Von B\xfclow", mapped_func)
class Exposing:
def base(self):
return "expose works!"
cherrypy.expose(base)
cherrypy.expose(base, "1")
cherrypy.expose(base, "2")
class ExposingNewStyle(object):
def base(self):
return "expose works!"
cherrypy.expose(base)
cherrypy.expose(base, "1")
cherrypy.expose(base, "2")
class Dir1:
def index(self):
return "index for dir1"
index.exposed = True
def myMethod(self):
return "myMethod from dir1, path_info is:" + repr(cherrypy.request.path_info)
myMethod.exposed = True
myMethod._cp_config = {'tools.trailing_slash.extra': True}
def default(self, *params):
return "default for dir1, param is:" + repr(params)
default.exposed = True
class Dir2:
def index(self):
return "index for dir2, path is:" + cherrypy.request.path_info
index.exposed = True
def script_name(self):
return cherrypy.tree.script_name()
script_name.exposed = True
def cherrypy_url(self):
return cherrypy.url("/extra")
cherrypy_url.exposed = True
def posparam(self, *vpath):
return "/".join(vpath)
posparam.exposed = True
class Dir3:
def default(self):
return "default for dir3, not exposed"
class Dir4:
def index(self):
return "index for dir4, not exposed"
class DefNoIndex:
def default(self, *args):
raise cherrypy.HTTPRedirect("contact")
default.exposed = True
# MethodDispatcher code
class ByMethod:
exposed = True
def __init__(self, *things):
self.things = list(things)
def GET(self):
return repr(self.things)
def POST(self, thing):
self.things.append(thing)
class Collection:
default = ByMethod('a', 'bit')
Root.exposing = Exposing()
Root.exposingnew = ExposingNewStyle()
Root.dir1 = Dir1()
Root.dir1.dir2 = Dir2()
Root.dir1.dir2.dir3 = Dir3()
Root.dir1.dir2.dir3.dir4 = Dir4()
Root.defnoindex = DefNoIndex()
Root.bymethod = ByMethod('another')
Root.collection = Collection()
d = cherrypy.dispatch.MethodDispatcher()
for url in script_names:
conf = {'/': {'user': (url or "/").split("/")[-2]},
'/bymethod': {'request.dispatch': d},
'/collection': {'request.dispatch': d},
}
cherrypy.tree.mount(Root(), url, conf)
class Isolated:
def index(self):
return "made it!"
index.exposed = True
cherrypy.tree.mount(Isolated(), "/isolated")
class AnotherApp:
exposed = True
def GET(self):
return "milk"
cherrypy.tree.mount(AnotherApp(), "/app", {'/': {'request.dispatch': d}})
setup_server = staticmethod(setup_server)
def testObjectMapping(self):
for url in script_names:
prefix = self.script_name = url
self.getPage('/')
self.assertBody('world')
self.getPage("/dir1/myMethod")
self.assertBody("myMethod from dir1, path_info is:'/dir1/myMethod'")
self.getPage("/this/method/does/not/exist")
self.assertBody("default:('this', 'method', 'does', 'not', 'exist')")
self.getPage("/extra/too/much")
self.assertBody("('too', 'much')")
self.getPage("/other")
self.assertBody('other')
self.getPage("/notExposed")
self.assertBody("default:('notExposed',)")
self.getPage("/dir1/dir2/")
self.assertBody('index for dir2, path is:/dir1/dir2/')
# Test omitted trailing slash (should be redirected by default).
self.getPage("/dir1/dir2")
self.assertStatus(301)
self.assertHeader('Location', '%s/dir1/dir2/' % self.base())
# Test extra trailing slash (should be redirected if configured).
self.getPage("/dir1/myMethod/")
self.assertStatus(301)
self.assertHeader('Location', '%s/dir1/myMethod' % self.base())
# Test that default method must be exposed in order to match.
self.getPage("/dir1/dir2/dir3/dir4/index")
self.assertBody("default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")
# Test *vpath when default() is defined but not index()
# This also tests HTTPRedirect with default.
self.getPage("/defnoindex")
self.assertStatus((302, 303))
self.assertHeader('Location', '%s/contact' % self.base())
self.getPage("/defnoindex/")
self.assertStatus((302, 303))
self.assertHeader('Location', '%s/defnoindex/contact' % self.base())
self.getPage("/defnoindex/page")
self.assertStatus((302, 303))
self.assertHeader('Location', '%s/defnoindex/contact' % self.base())
self.getPage("/redirect")
self.assertStatus('302 Found')
self.assertHeader('Location', '%s/dir1/' % self.base())
if not getattr(cherrypy.server, "using_apache", False):
# Test that we can use URL's which aren't all valid Python identifiers
# This should also test the %XX-unquoting of URL's.
self.getPage("/Von%20B%fclow?ID=14")
self.assertBody("ID is 14")
# Test that %2F in the path doesn't get unquoted too early;
# that is, it should not be used to separate path components.
# See ticket #393.
self.getPage("/page%2Fname")
self.assertBody("default:('page/name',)")
self.getPage("/dir1/dir2/script_name")
self.assertBody(url)
self.getPage("/dir1/dir2/cherrypy_url")
self.assertBody("%s/extra" % self.base())
# Test that configs don't overwrite each other from diferent apps
self.getPage("/confvalue")
self.assertBody((url or "/").split("/")[-2])
self.script_name = ""
# Test absoluteURI's in the Request-Line
self.getPage('http://%s:%s/' % (self.interface(), self.PORT))
self.assertBody('world')
self.getPage('http://%s:%s/abs/?service=http://192.168.0.1/x/y/z' %
(self.interface(), self.PORT))
self.assertBody("default:('abs',)")
self.getPage('/rel/?service=http://192.168.120.121:8000/x/y/z')
self.assertBody("default:('rel',)")
# Test that the "isolated" app doesn't leak url's into the root app.
# If it did leak, Root.default() would answer with
# "default:('isolated', 'doesnt', 'exist')".
self.getPage("/isolated/")
self.assertStatus("200 OK")
self.assertBody("made it!")
self.getPage("/isolated/doesnt/exist")
self.assertStatus("404 Not Found")
# Make sure /foobar maps to Root.foobar and not to the app
# mounted at /foo. See http://www.cherrypy.org/ticket/573
self.getPage("/foobar")
self.assertBody("bar")
def test_translate(self):
self.getPage("/translate_html")
self.assertStatus("200 OK")
self.assertBody("OK")
self.getPage("/translate.html")
self.assertStatus("200 OK")
self.assertBody("OK")
self.getPage("/translate-html")
self.assertStatus("200 OK")
self.assertBody("OK")
def test_redir_using_url(self):
for url in script_names:
prefix = self.script_name = url
# Test the absolute path to the parent (leading slash)
self.getPage('/redirect_via_url?path=./')
self.assertStatus(('302 Found', '303 See Other'))
self.assertHeader('Location', '%s/' % self.base())
# Test the relative path to the parent (no leading slash)
self.getPage('/redirect_via_url?path=./')
self.assertStatus(('302 Found', '303 See Other'))
self.assertHeader('Location', '%s/' % self.base())
# Test the absolute path to the parent (leading slash)
self.getPage('/redirect_via_url/?path=./')
self.assertStatus(('302 Found', '303 See Other'))
self.assertHeader('Location', '%s/' % self.base())
# Test the relative path to the parent (no leading slash)
self.getPage('/redirect_via_url/?path=./')
self.assertStatus(('302 Found', '303 See Other'))
self.assertHeader('Location', '%s/' % self.base())
def testPositionalParams(self):
self.getPage("/dir1/dir2/posparam/18/24/hut/hike")
self.assertBody("18/24/hut/hike")
# intermediate index methods should not receive posparams;
# only the "final" index method should do so.
self.getPage("/dir1/dir2/5/3/sir")
self.assertBody("default for dir1, param is:('dir2', '5', '3', 'sir')")
# test that extra positional args raises an 404 Not Found
# See http://www.cherrypy.org/ticket/733.
self.getPage("/dir1/dir2/script_name/extra/stuff")
self.assertStatus(404)
def testExpose(self):
# Test the cherrypy.expose function/decorator
self.getPage("/exposing/base")
self.assertBody("expose works!")
self.getPage("/exposing/1")
self.assertBody("expose works!")
self.getPage("/exposing/2")
self.assertBody("expose works!")
self.getPage("/exposingnew/base")
self.assertBody("expose works!")
self.getPage("/exposingnew/1")
self.assertBody("expose works!")
self.getPage("/exposingnew/2")
self.assertBody("expose works!")
def testMethodDispatch(self):
self.getPage("/bymethod")
self.assertBody("['another']")
self.assertHeader('Allow', 'GET, HEAD, POST')
self.getPage("/bymethod", method="HEAD")
self.assertBody("")
self.assertHeader('Allow', 'GET, HEAD, POST')
self.getPage("/bymethod", method="POST", body="thing=one")
self.assertBody("")
self.assertHeader('Allow', 'GET, HEAD, POST')
self.getPage("/bymethod")
self.assertBody(repr(['another', ntou('one')]))
self.assertHeader('Allow', 'GET, HEAD, POST')
self.getPage("/bymethod", method="PUT")
self.assertErrorPage(405)
self.assertHeader('Allow', 'GET, HEAD, POST')
# Test default with posparams
self.getPage("/collection/silly", method="POST")
self.getPage("/collection", method="GET")
self.assertBody("['a', 'bit', 'silly']")
# Test custom dispatcher set on app root (see #737).
self.getPage("/app")
self.assertBody("milk")
def testTreeMounting(self):
class Root(object):
def hello(self):
return "Hello world!"
hello.exposed = True
# When mounting an application instance,
# we can't specify a different script name in the call to mount.
a = Application(Root(), '/somewhere')
self.assertRaises(ValueError, cherrypy.tree.mount, a, '/somewhereelse')
# When mounting an application instance...
a = Application(Root(), '/somewhere')
# ...we MUST allow in identical script name in the call to mount...
cherrypy.tree.mount(a, '/somewhere')
self.getPage('/somewhere/hello')
self.assertStatus(200)
# ...and MUST allow a missing script_name.
del cherrypy.tree.apps['/somewhere']
cherrypy.tree.mount(a)
self.getPage('/somewhere/hello')
self.assertStatus(200)
# In addition, we MUST be able to create an Application using
# script_name == None for access to the wsgi_environ.
a = Application(Root(), script_name=None)
# However, this does not apply to tree.mount
self.assertRaises(TypeError, cherrypy.tree.mount, a, None)

View File

@ -1,129 +0,0 @@
import cherrypy
from cherrypy.test import helper
script_names = ["", "/path/to/myapp"]
class ProxyTest(helper.CPWebCase):
def setup_server():
# Set up site
cherrypy.config.update({
'tools.proxy.on': True,
'tools.proxy.base': 'www.mydomain.test',
})
# Set up application
class Root:
def __init__(self, sn):
# Calculate a URL outside of any requests.
self.thisnewpage = cherrypy.url("/this/new/page", script_name=sn)
def pageurl(self):
return self.thisnewpage
pageurl.exposed = True
def index(self):
raise cherrypy.HTTPRedirect('dummy')
index.exposed = True
def remoteip(self):
return cherrypy.request.remote.ip
remoteip.exposed = True
def xhost(self):
raise cherrypy.HTTPRedirect('blah')
xhost.exposed = True
xhost._cp_config = {'tools.proxy.local': 'X-Host',
'tools.trailing_slash.extra': True,
}
def base(self):
return cherrypy.request.base
base.exposed = True
def ssl(self):
return cherrypy.request.base
ssl.exposed = True
ssl._cp_config = {'tools.proxy.scheme': 'X-Forwarded-Ssl'}
def newurl(self):
return ("Browse to <a href='%s'>this page</a>."
% cherrypy.url("/this/new/page"))
newurl.exposed = True
for sn in script_names:
cherrypy.tree.mount(Root(sn), sn)
setup_server = staticmethod(setup_server)
def testProxy(self):
self.getPage("/")
self.assertHeader('Location',
"%s://www.mydomain.test%s/dummy" %
(self.scheme, self.prefix()))
# Test X-Forwarded-Host (Apache 1.3.33+ and Apache 2)
self.getPage("/", headers=[('X-Forwarded-Host', 'http://www.example.test')])
self.assertHeader('Location', "http://www.example.test/dummy")
self.getPage("/", headers=[('X-Forwarded-Host', 'www.example.test')])
self.assertHeader('Location', "%s://www.example.test/dummy" % self.scheme)
# Test multiple X-Forwarded-Host headers
self.getPage("/", headers=[
('X-Forwarded-Host', 'http://www.example.test, www.cherrypy.test'),
])
self.assertHeader('Location', "http://www.example.test/dummy")
# Test X-Forwarded-For (Apache2)
self.getPage("/remoteip",
headers=[('X-Forwarded-For', '192.168.0.20')])
self.assertBody("192.168.0.20")
self.getPage("/remoteip",
headers=[('X-Forwarded-For', '67.15.36.43, 192.168.0.20')])
self.assertBody("192.168.0.20")
# Test X-Host (lighttpd; see https://trac.lighttpd.net/trac/ticket/418)
self.getPage("/xhost", headers=[('X-Host', 'www.example.test')])
self.assertHeader('Location', "%s://www.example.test/blah" % self.scheme)
# Test X-Forwarded-Proto (lighttpd)
self.getPage("/base", headers=[('X-Forwarded-Proto', 'https')])
self.assertBody("https://www.mydomain.test")
# Test X-Forwarded-Ssl (webfaction?)
self.getPage("/ssl", headers=[('X-Forwarded-Ssl', 'on')])
self.assertBody("https://www.mydomain.test")
# Test cherrypy.url()
for sn in script_names:
# Test the value inside requests
self.getPage(sn + "/newurl")
self.assertBody("Browse to <a href='%s://www.mydomain.test" % self.scheme
+ sn + "/this/new/page'>this page</a>.")
self.getPage(sn + "/newurl", headers=[('X-Forwarded-Host',
'http://www.example.test')])
self.assertBody("Browse to <a href='http://www.example.test"
+ sn + "/this/new/page'>this page</a>.")
# Test the value outside requests
port = ""
if self.scheme == "http" and self.PORT != 80:
port = ":%s" % self.PORT
elif self.scheme == "https" and self.PORT != 443:
port = ":%s" % self.PORT
host = self.HOST
if host in ('0.0.0.0', '::'):
import socket
host = socket.gethostname()
expected = ("%s://%s%s%s/this/new/page"
% (self.scheme, host, port, sn))
self.getPage(sn + "/pageurl")
self.assertBody(expected)
# Test trailing slash (see http://www.cherrypy.org/ticket/562).
self.getPage("/xhost/", headers=[('X-Host', 'www.example.test')])
self.assertHeader('Location', "%s://www.example.test/xhost"
% self.scheme)

View File

@ -1,59 +0,0 @@
"""Tests for refleaks."""
from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob
import threading
import cherrypy
data = object()
from cherrypy.test import helper
class ReferenceTests(helper.CPWebCase):
def setup_server():
class Root:
def index(self, *args, **kwargs):
cherrypy.request.thing = data
return "Hello world!"
index.exposed = True
cherrypy.tree.mount(Root())
setup_server = staticmethod(setup_server)
def test_threadlocal_garbage(self):
success = []
def getpage():
host = '%s:%s' % (self.interface(), self.PORT)
if self.scheme == 'https':
c = HTTPSConnection(host)
else:
c = HTTPConnection(host)
try:
c.putrequest('GET', '/')
c.endheaders()
response = c.getresponse()
body = response.read()
self.assertEqual(response.status, 200)
self.assertEqual(body, ntob("Hello world!"))
finally:
c.close()
success.append(True)
ITERATIONS = 25
ts = []
for _ in range(ITERATIONS):
t = threading.Thread(target=getpage)
ts.append(t)
t.start()
for t in ts:
t.join()
self.assertEqual(len(success), ITERATIONS)

View File

@ -1,737 +0,0 @@
"""Basic tests for the cherrypy.Request object."""
import os
localDir = os.path.dirname(__file__)
import sys
import types
from cherrypy._cpcompat import IncompleteRead, ntob, ntou, unicodestr
import cherrypy
from cherrypy import _cptools, tools
from cherrypy.lib import httputil
defined_http_methods = ("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
"TRACE", "PROPFIND")
# Client-side code #
from cherrypy.test import helper
class RequestObjectTests(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "hello"
index.exposed = True
def scheme(self):
return cherrypy.request.scheme
scheme.exposed = True
root = Root()
class TestType(type):
"""Metaclass which automatically exposes all functions in each subclass,
and adds an instance of the subclass as an attribute of root.
"""
def __init__(cls, name, bases, dct):
type.__init__(cls, name, bases, dct)
for value in dct.values():
if isinstance(value, types.FunctionType):
value.exposed = True
setattr(root, name.lower(), cls())
Test = TestType('Test', (object,), {})
class PathInfo(Test):
def default(self, *args):
return cherrypy.request.path_info
class Params(Test):
def index(self, thing):
return repr(thing)
def ismap(self, x, y):
return "Coordinates: %s, %s" % (x, y)
def default(self, *args, **kwargs):
return "args: %s kwargs: %s" % (args, kwargs)
default._cp_config = {'request.query_string_encoding': 'latin1'}
class ParamErrorsCallable(object):
exposed = True
def __call__(self):
return "data"
class ParamErrors(Test):
def one_positional(self, param1):
return "data"
one_positional.exposed = True
def one_positional_args(self, param1, *args):
return "data"
one_positional_args.exposed = True
def one_positional_args_kwargs(self, param1, *args, **kwargs):
return "data"
one_positional_args_kwargs.exposed = True
def one_positional_kwargs(self, param1, **kwargs):
return "data"
one_positional_kwargs.exposed = True
def no_positional(self):
return "data"
no_positional.exposed = True
def no_positional_args(self, *args):
return "data"
no_positional_args.exposed = True
def no_positional_args_kwargs(self, *args, **kwargs):
return "data"
no_positional_args_kwargs.exposed = True
def no_positional_kwargs(self, **kwargs):
return "data"
no_positional_kwargs.exposed = True
callable_object = ParamErrorsCallable()
def raise_type_error(self, **kwargs):
raise TypeError("Client Error")
raise_type_error.exposed = True
def raise_type_error_with_default_param(self, x, y=None):
return '%d' % 'a' # throw an exception
raise_type_error_with_default_param.exposed = True
def callable_error_page(status, **kwargs):
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
class Error(Test):
_cp_config = {'tools.log_tracebacks.on': True,
}
def reason_phrase(self):
raise cherrypy.HTTPError("410 Gone fishin'")
def custom(self, err='404'):
raise cherrypy.HTTPError(int(err), "No, <b>really</b>, not found!")
custom._cp_config = {'error_page.404': os.path.join(localDir, "static/index.html"),
'error_page.401': callable_error_page,
}
def custom_default(self):
return 1 + 'a' # raise an unexpected error
custom_default._cp_config = {'error_page.default': callable_error_page}
def noexist(self):
raise cherrypy.HTTPError(404, "No, <b>really</b>, not found!")
noexist._cp_config = {'error_page.404': "nonexistent.html"}
def page_method(self):
raise ValueError()
def page_yield(self):
yield "howdy"
raise ValueError()
def page_streamed(self):
yield "word up"
raise ValueError()
yield "very oops"
page_streamed._cp_config = {"response.stream": True}
def cause_err_in_finalize(self):
# Since status must start with an int, this should error.
cherrypy.response.status = "ZOO OK"
cause_err_in_finalize._cp_config = {'request.show_tracebacks': False}
def rethrow(self):
"""Test that an error raised here will be thrown out to the server."""
raise ValueError()
rethrow._cp_config = {'request.throw_errors': True}
class Expect(Test):
def expectation_failed(self):
expect = cherrypy.request.headers.elements("Expect")
if expect and expect[0].value != '100-continue':
raise cherrypy.HTTPError(400)
raise cherrypy.HTTPError(417, 'Expectation Failed')
class Headers(Test):
def default(self, headername):
"""Spit back out the value for the requested header."""
return cherrypy.request.headers[headername]
def doubledheaders(self):
# From http://www.cherrypy.org/ticket/165:
# "header field names should not be case sensitive sayes the rfc.
# if i set a headerfield in complete lowercase i end up with two
# header fields, one in lowercase, the other in mixed-case."
# Set the most common headers
hMap = cherrypy.response.headers
hMap['content-type'] = "text/html"
hMap['content-length'] = 18
hMap['server'] = 'CherryPy headertest'
hMap['location'] = ('%s://%s:%s/headers/'
% (cherrypy.request.local.ip,
cherrypy.request.local.port,
cherrypy.request.scheme))
# Set a rare header for fun
hMap['Expires'] = 'Thu, 01 Dec 2194 16:00:00 GMT'
return "double header test"
def ifmatch(self):
val = cherrypy.request.headers['If-Match']
assert isinstance(val, unicodestr)
cherrypy.response.headers['ETag'] = val
return val
class HeaderElements(Test):
def get_elements(self, headername):
e = cherrypy.request.headers.elements(headername)
return "\n".join([unicodestr(x) for x in e])
class Method(Test):
def index(self):
m = cherrypy.request.method
if m in defined_http_methods or m == "CONNECT":
return m
if m == "LINK":
raise cherrypy.HTTPError(405)
else:
raise cherrypy.HTTPError(501)
def parameterized(self, data):
return data
def request_body(self):
# This should be a file object (temp file),
# which CP will just pipe back out if we tell it to.
return cherrypy.request.body
def reachable(self):
return "success"
class Divorce:
"""HTTP Method handlers shouldn't collide with normal method names.
For example, a GET-handler shouldn't collide with a method named 'get'.
If you build HTTP method dispatching into CherryPy, rewrite this class
to use your new dispatch mechanism and make sure that:
"GET /divorce HTTP/1.1" maps to divorce.index() and
"GET /divorce/get?ID=13 HTTP/1.1" maps to divorce.get()
"""
documents = {}
def index(self):
yield "<h1>Choose your document</h1>\n"
yield "<ul>\n"
for id, contents in self.documents.items():
yield (" <li><a href='/divorce/get?ID=%s'>%s</a>: %s</li>\n"
% (id, id, contents))
yield "</ul>"
index.exposed = True
def get(self, ID):
return ("Divorce document %s: %s" %
(ID, self.documents.get(ID, "empty")))
get.exposed = True
root.divorce = Divorce()
class ThreadLocal(Test):
def index(self):
existing = repr(getattr(cherrypy.request, "asdf", None))
cherrypy.request.asdf = "rassfrassin"
return existing
appconf = {
'/method': {'request.methods_with_bodies': ("POST", "PUT", "PROPFIND")},
}
cherrypy.tree.mount(root, config=appconf)
setup_server = staticmethod(setup_server)
def test_scheme(self):
self.getPage("/scheme")
self.assertBody(self.scheme)
def testRelativeURIPathInfo(self):
self.getPage("/pathinfo/foo/bar")
self.assertBody("/pathinfo/foo/bar")
def testAbsoluteURIPathInfo(self):
# http://cherrypy.org/ticket/1061
self.getPage("http://localhost/pathinfo/foo/bar")
self.assertBody("/pathinfo/foo/bar")
def testParams(self):
self.getPage("/params/?thing=a")
self.assertBody(repr(ntou("a")))
self.getPage("/params/?thing=a&thing=b&thing=c")
self.assertBody(repr([ntou('a'), ntou('b'), ntou('c')]))
# Test friendly error message when given params are not accepted.
cherrypy.config.update({"request.show_mismatched_params": True})
self.getPage("/params/?notathing=meeting")
self.assertInBody("Missing parameters: thing")
self.getPage("/params/?thing=meeting&notathing=meeting")
self.assertInBody("Unexpected query string parameters: notathing")
# Test ability to turn off friendly error messages
cherrypy.config.update({"request.show_mismatched_params": False})
self.getPage("/params/?notathing=meeting")
self.assertInBody("Not Found")
self.getPage("/params/?thing=meeting&notathing=meeting")
self.assertInBody("Not Found")
# Test "% HEX HEX"-encoded URL, param keys, and values
self.getPage("/params/%d4%20%e3/cheese?Gruy%E8re=Bulgn%e9ville")
self.assertBody("args: %s kwargs: %s" %
(('\xd4 \xe3', 'cheese'),
{'Gruy\xe8re': ntou('Bulgn\xe9ville')}))
# Make sure that encoded = and & get parsed correctly
self.getPage("/params/code?url=http%3A//cherrypy.org/index%3Fa%3D1%26b%3D2")
self.assertBody("args: %s kwargs: %s" %
(('code',),
{'url': ntou('http://cherrypy.org/index?a=1&b=2')}))
# Test coordinates sent by <img ismap>
self.getPage("/params/ismap?223,114")
self.assertBody("Coordinates: 223, 114")
# Test "name[key]" dict-like params
self.getPage("/params/dictlike?a[1]=1&a[2]=2&b=foo&b[bar]=baz")
self.assertBody("args: %s kwargs: %s" %
(('dictlike',),
{'a[1]': ntou('1'), 'b[bar]': ntou('baz'),
'b': ntou('foo'), 'a[2]': ntou('2')}))
def testParamErrors(self):
# test that all of the handlers work when given
# the correct parameters in order to ensure that the
# errors below aren't coming from some other source.
for uri in (
'/paramerrors/one_positional?param1=foo',
'/paramerrors/one_positional_args?param1=foo',
'/paramerrors/one_positional_args/foo',
'/paramerrors/one_positional_args/foo/bar/baz',
'/paramerrors/one_positional_args_kwargs?param1=foo&param2=bar',
'/paramerrors/one_positional_args_kwargs/foo?param2=bar&param3=baz',
'/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz',
'/paramerrors/one_positional_kwargs?param1=foo&param2=bar&param3=baz',
'/paramerrors/one_positional_kwargs/foo?param4=foo&param2=bar&param3=baz',
'/paramerrors/no_positional',
'/paramerrors/no_positional_args/foo',
'/paramerrors/no_positional_args/foo/bar/baz',
'/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar',
'/paramerrors/no_positional_args_kwargs/foo?param2=bar',
'/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz',
'/paramerrors/no_positional_kwargs?param1=foo&param2=bar',
'/paramerrors/callable_object',
):
self.getPage(uri)
self.assertStatus(200)
# query string parameters are part of the URI, so if they are wrong
# for a particular handler, the status MUST be a 404.
error_msgs = [
'Missing parameters',
'Nothing matches the given URI',
'Multiple values for parameters',
'Unexpected query string parameters',
'Unexpected body parameters',
]
for uri, msg in (
('/paramerrors/one_positional', error_msgs[0]),
('/paramerrors/one_positional?foo=foo', error_msgs[0]),
('/paramerrors/one_positional/foo/bar/baz', error_msgs[1]),
('/paramerrors/one_positional/foo?param1=foo', error_msgs[2]),
('/paramerrors/one_positional/foo?param1=foo&param2=foo', error_msgs[2]),
('/paramerrors/one_positional_args/foo?param1=foo&param2=foo', error_msgs[2]),
('/paramerrors/one_positional_args/foo/bar/baz?param2=foo', error_msgs[3]),
('/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar&param3=baz', error_msgs[2]),
('/paramerrors/one_positional_kwargs/foo?param1=foo&param2=bar&param3=baz', error_msgs[2]),
('/paramerrors/no_positional/boo', error_msgs[1]),
('/paramerrors/no_positional?param1=foo', error_msgs[3]),
('/paramerrors/no_positional_args/boo?param1=foo', error_msgs[3]),
('/paramerrors/no_positional_kwargs/boo?param1=foo', error_msgs[1]),
('/paramerrors/callable_object?param1=foo', error_msgs[3]),
('/paramerrors/callable_object/boo', error_msgs[1]),
):
for show_mismatched_params in (True, False):
cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri)
self.assertStatus(404)
if show_mismatched_params:
self.assertInBody(msg)
else:
self.assertInBody("Not Found")
# if body parameters are wrong, a 400 must be returned.
for uri, body, msg in (
('/paramerrors/one_positional/foo', 'param1=foo', error_msgs[2]),
('/paramerrors/one_positional/foo', 'param1=foo&param2=foo', error_msgs[2]),
('/paramerrors/one_positional_args/foo', 'param1=foo&param2=foo', error_msgs[2]),
('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo', error_msgs[4]),
('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar&param3=baz', error_msgs[2]),
('/paramerrors/one_positional_kwargs/foo', 'param1=foo&param2=bar&param3=baz', error_msgs[2]),
('/paramerrors/no_positional', 'param1=foo', error_msgs[4]),
('/paramerrors/no_positional_args/boo', 'param1=foo', error_msgs[4]),
('/paramerrors/callable_object', 'param1=foo', error_msgs[4]),
):
for show_mismatched_params in (True, False):
cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri, method='POST', body=body)
self.assertStatus(400)
if show_mismatched_params:
self.assertInBody(msg)
else:
self.assertInBody("400 Bad")
# even if body parameters are wrong, if we get the uri wrong, then
# it's a 404
for uri, body, msg in (
('/paramerrors/one_positional?param2=foo', 'param1=foo', error_msgs[3]),
('/paramerrors/one_positional/foo/bar', 'param2=foo', error_msgs[1]),
('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo', error_msgs[3]),
('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar&param3=baz', error_msgs[1]),
('/paramerrors/no_positional?param1=foo', 'param2=foo', error_msgs[3]),
('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo', error_msgs[3]),
('/paramerrors/callable_object?param2=bar', 'param1=foo', error_msgs[3]),
):
for show_mismatched_params in (True, False):
cherrypy.config.update({'request.show_mismatched_params': show_mismatched_params})
self.getPage(uri, method='POST', body=body)
self.assertStatus(404)
if show_mismatched_params:
self.assertInBody(msg)
else:
self.assertInBody("Not Found")
# In the case that a handler raises a TypeError we should
# let that type error through.
for uri in (
'/paramerrors/raise_type_error',
'/paramerrors/raise_type_error_with_default_param?x=0',
'/paramerrors/raise_type_error_with_default_param?x=0&y=0',
):
self.getPage(uri, method='GET')
self.assertStatus(500)
self.assertTrue('Client Error', self.body)
def testErrorHandling(self):
self.getPage("/error/missing")
self.assertStatus(404)
self.assertErrorPage(404, "The path '/error/missing' was not found.")
ignore = helper.webtest.ignored_exceptions
ignore.append(ValueError)
try:
valerr = '\n raise ValueError()\nValueError'
self.getPage("/error/page_method")
self.assertErrorPage(500, pattern=valerr)
self.getPage("/error/page_yield")
self.assertErrorPage(500, pattern=valerr)
if (cherrypy.server.protocol_version == "HTTP/1.0" or
getattr(cherrypy.server, "using_apache", False)):
self.getPage("/error/page_streamed")
# Because this error is raised after the response body has
# started, the status should not change to an error status.
self.assertStatus(200)
self.assertBody("word up")
else:
# Under HTTP/1.1, the chunked transfer-coding is used.
# The HTTP client will choke when the output is incomplete.
self.assertRaises((ValueError, IncompleteRead), self.getPage,
"/error/page_streamed")
# No traceback should be present
self.getPage("/error/cause_err_in_finalize")
msg = "Illegal response status from server ('ZOO' is non-numeric)."
self.assertErrorPage(500, msg, None)
finally:
ignore.pop()
# Test HTTPError with a reason-phrase in the status arg.
self.getPage('/error/reason_phrase')
self.assertStatus("410 Gone fishin'")
# Test custom error page for a specific error.
self.getPage("/error/custom")
self.assertStatus(404)
self.assertBody("Hello, world\r\n" + (" " * 499))
# Test custom error page for a specific error.
self.getPage("/error/custom?err=401")
self.assertStatus(401)
self.assertBody("Error 401 Unauthorized - Well, I'm very sorry but you haven't paid!")
# Test default custom error page.
self.getPage("/error/custom_default")
self.assertStatus(500)
self.assertBody("Error 500 Internal Server Error - Well, I'm very sorry but you haven't paid!".ljust(513))
# Test error in custom error page (ticket #305).
# Note that the message is escaped for HTML (ticket #310).
self.getPage("/error/noexist")
self.assertStatus(404)
msg = ("No, &lt;b&gt;really&lt;/b&gt;, not found!<br />"
"In addition, the custom error page failed:\n<br />"
"IOError: [Errno 2] No such file or directory: 'nonexistent.html'")
self.assertInBody(msg)
if getattr(cherrypy.server, "using_apache", False):
pass
else:
# Test throw_errors (ticket #186).
self.getPage("/error/rethrow")
self.assertInBody("raise ValueError()")
def testExpect(self):
e = ('Expect', '100-continue')
self.getPage("/headerelements/get_elements?headername=Expect", [e])
self.assertBody('100-continue')
self.getPage("/expect/expectation_failed", [e])
self.assertStatus(417)
def testHeaderElements(self):
# Accept-* header elements should be sorted, with most preferred first.
h = [('Accept', 'audio/*; q=0.2, audio/basic')]
self.getPage("/headerelements/get_elements?headername=Accept", h)
self.assertStatus(200)
self.assertBody("audio/basic\n"
"audio/*;q=0.2")
h = [('Accept', 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c')]
self.getPage("/headerelements/get_elements?headername=Accept", h)
self.assertStatus(200)
self.assertBody("text/x-c\n"
"text/html\n"
"text/x-dvi;q=0.8\n"
"text/plain;q=0.5")
# Test that more specific media ranges get priority.
h = [('Accept', 'text/*, text/html, text/html;level=1, */*')]
self.getPage("/headerelements/get_elements?headername=Accept", h)
self.assertStatus(200)
self.assertBody("text/html;level=1\n"
"text/html\n"
"text/*\n"
"*/*")
# Test Accept-Charset
h = [('Accept-Charset', 'iso-8859-5, unicode-1-1;q=0.8')]
self.getPage("/headerelements/get_elements?headername=Accept-Charset", h)
self.assertStatus("200 OK")
self.assertBody("iso-8859-5\n"
"unicode-1-1;q=0.8")
# Test Accept-Encoding
h = [('Accept-Encoding', 'gzip;q=1.0, identity; q=0.5, *;q=0')]
self.getPage("/headerelements/get_elements?headername=Accept-Encoding", h)
self.assertStatus("200 OK")
self.assertBody("gzip;q=1.0\n"
"identity;q=0.5\n"
"*;q=0")
# Test Accept-Language
h = [('Accept-Language', 'da, en-gb;q=0.8, en;q=0.7')]
self.getPage("/headerelements/get_elements?headername=Accept-Language", h)
self.assertStatus("200 OK")
self.assertBody("da\n"
"en-gb;q=0.8\n"
"en;q=0.7")
# Test malformed header parsing. See http://www.cherrypy.org/ticket/763.
self.getPage("/headerelements/get_elements?headername=Content-Type",
# Note the illegal trailing ";"
headers=[('Content-Type', 'text/html; charset=utf-8;')])
self.assertStatus(200)
self.assertBody("text/html;charset=utf-8")
def test_repeated_headers(self):
# Test that two request headers are collapsed into one.
# See http://www.cherrypy.org/ticket/542.
self.getPage("/headers/Accept-Charset",
headers=[("Accept-Charset", "iso-8859-5"),
("Accept-Charset", "unicode-1-1;q=0.8")])
self.assertBody("iso-8859-5, unicode-1-1;q=0.8")
# Tests that each header only appears once, regardless of case.
self.getPage("/headers/doubledheaders")
self.assertBody("double header test")
hnames = [name.title() for name, val in self.headers]
for key in ['Content-Length', 'Content-Type', 'Date',
'Expires', 'Location', 'Server']:
self.assertEqual(hnames.count(key), 1, self.headers)
def test_encoded_headers(self):
# First, make sure the innards work like expected.
self.assertEqual(httputil.decode_TEXT(ntou("=?utf-8?q?f=C3=BCr?=")), ntou("f\xfcr"))
if cherrypy.server.protocol_version == "HTTP/1.1":
# Test RFC-2047-encoded request and response header values
u = ntou('\u212bngstr\xf6m', 'escape')
c = ntou("=E2=84=ABngstr=C3=B6m")
self.getPage("/headers/ifmatch", [('If-Match', ntou('=?utf-8?q?%s?=') % c)])
# The body should be utf-8 encoded.
self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m"))
# But the Etag header should be RFC-2047 encoded (binary)
self.assertHeader("ETag", ntou('=?utf-8?b?4oSrbmdzdHLDtm0=?='))
# Test a *LONG* RFC-2047-encoded request and response header value
self.getPage("/headers/ifmatch",
[('If-Match', ntou('=?utf-8?q?%s?=') % (c * 10))])
self.assertBody(ntob("\xe2\x84\xabngstr\xc3\xb6m") * 10)
# Note: this is different output for Python3, but it decodes fine.
etag = self.assertHeader("ETag",
'=?utf-8?b?4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
'4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
'4oSrbmdzdHLDtm3ihKtuZ3N0csO2beKEq25nc3Ryw7Zt'
'4oSrbmdzdHLDtm0=?=')
self.assertEqual(httputil.decode_TEXT(etag), u * 10)
def test_header_presence(self):
# If we don't pass a Content-Type header, it should not be present
# in cherrypy.request.headers
self.getPage("/headers/Content-Type",
headers=[])
self.assertStatus(500)
# If Content-Type is present in the request, it should be present in
# cherrypy.request.headers
self.getPage("/headers/Content-Type",
headers=[("Content-type", "application/json")])
self.assertBody("application/json")
def test_basic_HTTPMethods(self):
helper.webtest.methods_with_bodies = ("POST", "PUT", "PROPFIND")
# Test that all defined HTTP methods work.
for m in defined_http_methods:
self.getPage("/method/", method=m)
# HEAD requests should not return any body.
if m == "HEAD":
self.assertBody("")
elif m == "TRACE":
# Some HTTP servers (like modpy) have their own TRACE support
self.assertEqual(self.body[:5], ntob("TRACE"))
else:
self.assertBody(m)
# Request a PUT method with a form-urlencoded body
self.getPage("/method/parameterized", method="PUT",
body="data=on+top+of+other+things")
self.assertBody("on top of other things")
# Request a PUT method with a file body
b = "one thing on top of another"
h = [("Content-Type", "text/plain"),
("Content-Length", str(len(b)))]
self.getPage("/method/request_body", headers=h, method="PUT", body=b)
self.assertStatus(200)
self.assertBody(b)
# Request a PUT method with a file body but no Content-Type.
# See http://www.cherrypy.org/ticket/790.
b = ntob("one thing on top of another")
self.persistent = True
try:
conn = self.HTTP_CONN
conn.putrequest("PUT", "/method/request_body", skip_host=True)
conn.putheader("Host", self.HOST)
conn.putheader('Content-Length', str(len(b)))
conn.endheaders()
conn.send(b)
response = conn.response_class(conn.sock, method="PUT")
response.begin()
self.assertEqual(response.status, 200)
self.body = response.read()
self.assertBody(b)
finally:
self.persistent = False
# Request a PUT method with no body whatsoever (not an empty one).
# See http://www.cherrypy.org/ticket/650.
# Provide a C-T or webtest will provide one (and a C-L) for us.
h = [("Content-Type", "text/plain")]
self.getPage("/method/reachable", headers=h, method="PUT")
self.assertStatus(411)
# Request a custom method with a request body
b = ('<?xml version="1.0" encoding="utf-8" ?>\n\n'
'<propfind xmlns="DAV:"><prop><getlastmodified/>'
'</prop></propfind>')
h = [('Content-Type', 'text/xml'),
('Content-Length', str(len(b)))]
self.getPage("/method/request_body", headers=h, method="PROPFIND", body=b)
self.assertStatus(200)
self.assertBody(b)
# Request a disallowed method
self.getPage("/method/", method="LINK")
self.assertStatus(405)
# Request an unknown method
self.getPage("/method/", method="SEARCH")
self.assertStatus(501)
# For method dispatchers: make sure that an HTTP method doesn't
# collide with a virtual path atom. If you build HTTP-method
# dispatching into the core, rewrite these handlers to use
# your dispatch idioms.
self.getPage("/divorce/get?ID=13")
self.assertBody('Divorce document 13: empty')
self.assertStatus(200)
self.getPage("/divorce/", method="GET")
self.assertBody('<h1>Choose your document</h1>\n<ul>\n</ul>')
self.assertStatus(200)
def test_CONNECT_method(self):
if getattr(cherrypy.server, "using_apache", False):
return self.skip("skipped due to known Apache differences... ")
self.getPage("/method/", method="CONNECT")
self.assertBody("CONNECT")
def testEmptyThreadlocals(self):
results = []
for x in range(20):
self.getPage("/threadlocal/")
results.append(self.body)
self.assertEqual(results, [ntob("None")] * 20)

View File

@ -1,69 +0,0 @@
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import cherrypy
from cherrypy.test import helper
import nose
class RoutesDispatchTest(helper.CPWebCase):
def setup_server():
try:
import routes
except ImportError:
raise nose.SkipTest('Install routes to test RoutesDispatcher code')
class Dummy:
def index(self):
return "I said good day!"
class City:
def __init__(self, name):
self.name = name
self.population = 10000
def index(self, **kwargs):
return "Welcome to %s, pop. %s" % (self.name, self.population)
index._cp_config = {'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Language', 'en-GB')]}
def update(self, **kwargs):
self.population = kwargs['pop']
return "OK"
d = cherrypy.dispatch.RoutesDispatcher()
d.connect(action='index', name='hounslow', route='/hounslow',
controller=City('Hounslow'))
d.connect(name='surbiton', route='/surbiton', controller=City('Surbiton'),
action='index', conditions=dict(method=['GET']))
d.mapper.connect('/surbiton', controller='surbiton',
action='update', conditions=dict(method=['POST']))
d.connect('main', ':action', controller=Dummy())
conf = {'/': {'request.dispatch': d}}
cherrypy.tree.mount(root=None, config=conf)
setup_server = staticmethod(setup_server)
def test_Routes_Dispatch(self):
self.getPage("/hounslow")
self.assertStatus("200 OK")
self.assertBody("Welcome to Hounslow, pop. 10000")
self.getPage("/foo")
self.assertStatus("404 Not Found")
self.getPage("/surbiton")
self.assertStatus("200 OK")
self.assertBody("Welcome to Surbiton, pop. 10000")
self.getPage("/surbiton", method="POST", body="pop=1327")
self.assertStatus("200 OK")
self.assertBody("OK")
self.getPage("/surbiton")
self.assertStatus("200 OK")
self.assertHeader("Content-Language", "en-GB")
self.assertBody("Welcome to Surbiton, pop. 1327")

View File

@ -1,464 +0,0 @@
import os
localDir = os.path.dirname(__file__)
import sys
import threading
import time
import cherrypy
from cherrypy._cpcompat import copykeys, HTTPConnection, HTTPSConnection
from cherrypy.lib import sessions
from cherrypy.lib.httputil import response_codes
def http_methods_allowed(methods=['GET', 'HEAD']):
method = cherrypy.request.method.upper()
if method not in methods:
cherrypy.response.headers['Allow'] = ", ".join(methods)
raise cherrypy.HTTPError(405)
cherrypy.tools.allow = cherrypy.Tool('on_start_resource', http_methods_allowed)
def setup_server():
class Root:
_cp_config = {'tools.sessions.on': True,
'tools.sessions.storage_type' : 'ram',
'tools.sessions.storage_path' : localDir,
'tools.sessions.timeout': (1.0 / 60),
'tools.sessions.clean_freq': (1.0 / 60),
}
def clear(self):
cherrypy.session.cache.clear()
clear.exposed = True
def data(self):
cherrypy.session['aha'] = 'foo'
return repr(cherrypy.session._data)
data.exposed = True
def testGen(self):
counter = cherrypy.session.get('counter', 0) + 1
cherrypy.session['counter'] = counter
yield str(counter)
testGen.exposed = True
def testStr(self):
counter = cherrypy.session.get('counter', 0) + 1
cherrypy.session['counter'] = counter
return str(counter)
testStr.exposed = True
def setsessiontype(self, newtype):
self.__class__._cp_config.update({'tools.sessions.storage_type': newtype})
if hasattr(cherrypy, "session"):
del cherrypy.session
cls = getattr(sessions, newtype.title() + 'Session')
if cls.clean_thread:
cls.clean_thread.stop()
cls.clean_thread.unsubscribe()
del cls.clean_thread
setsessiontype.exposed = True
setsessiontype._cp_config = {'tools.sessions.on': False}
def index(self):
sess = cherrypy.session
c = sess.get('counter', 0) + 1
time.sleep(0.01)
sess['counter'] = c
return str(c)
index.exposed = True
def keyin(self, key):
return str(key in cherrypy.session)
keyin.exposed = True
def delete(self):
cherrypy.session.delete()
sessions.expire()
return "done"
delete.exposed = True
def delkey(self, key):
del cherrypy.session[key]
return "OK"
delkey.exposed = True
def blah(self):
return self._cp_config['tools.sessions.storage_type']
blah.exposed = True
def iredir(self):
raise cherrypy.InternalRedirect('/blah')
iredir.exposed = True
def restricted(self):
return cherrypy.request.method
restricted.exposed = True
restricted._cp_config = {'tools.allow.on': True,
'tools.allow.methods': ['GET']}
def regen(self):
cherrypy.tools.sessions.regenerate()
return "logged in"
regen.exposed = True
def length(self):
return str(len(cherrypy.session))
length.exposed = True
def session_cookie(self):
# Must load() to start the clean thread.
cherrypy.session.load()
return cherrypy.session.id
session_cookie.exposed = True
session_cookie._cp_config = {
'tools.sessions.path': '/session_cookie',
'tools.sessions.name': 'temp',
'tools.sessions.persistent': False}
cherrypy.tree.mount(Root())
from cherrypy.test import helper
class SessionTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def tearDown(self):
# Clean up sessions.
for fname in os.listdir(localDir):
if fname.startswith(sessions.FileSession.SESSION_PREFIX):
os.unlink(os.path.join(localDir, fname))
def test_0_Session(self):
self.getPage('/setsessiontype/ram')
self.getPage('/clear')
# Test that a normal request gets the same id in the cookies.
# Note: this wouldn't work if /data didn't load the session.
self.getPage('/data')
self.assertBody("{'aha': 'foo'}")
c = self.cookies[0]
self.getPage('/data', self.cookies)
self.assertEqual(self.cookies[0], c)
self.getPage('/testStr')
self.assertBody('1')
cookie_parts = dict([p.strip().split('=')
for p in self.cookies[0][1].split(";")])
# Assert there is an 'expires' param
self.assertEqual(set(cookie_parts.keys()),
set(['session_id', 'expires', 'Path']))
self.getPage('/testGen', self.cookies)
self.assertBody('2')
self.getPage('/testStr', self.cookies)
self.assertBody('3')
self.getPage('/data', self.cookies)
self.assertBody("{'aha': 'foo', 'counter': 3}")
self.getPage('/length', self.cookies)
self.assertBody('2')
self.getPage('/delkey?key=counter', self.cookies)
self.assertStatus(200)
self.getPage('/setsessiontype/file')
self.getPage('/testStr')
self.assertBody('1')
self.getPage('/testGen', self.cookies)
self.assertBody('2')
self.getPage('/testStr', self.cookies)
self.assertBody('3')
self.getPage('/delkey?key=counter', self.cookies)
self.assertStatus(200)
# Wait for the session.timeout (1 second)
time.sleep(2)
self.getPage('/')
self.assertBody('1')
self.getPage('/length', self.cookies)
self.assertBody('1')
# Test session __contains__
self.getPage('/keyin?key=counter', self.cookies)
self.assertBody("True")
cookieset1 = self.cookies
# Make a new session and test __len__ again
self.getPage('/')
self.getPage('/length', self.cookies)
self.assertBody('2')
# Test session delete
self.getPage('/delete', self.cookies)
self.assertBody("done")
self.getPage('/delete', cookieset1)
self.assertBody("done")
f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
self.assertEqual(f(), [])
# Wait for the cleanup thread to delete remaining session files
self.getPage('/')
f = lambda: [x for x in os.listdir(localDir) if x.startswith('session-')]
self.assertNotEqual(f(), [])
time.sleep(2)
self.assertEqual(f(), [])
def test_1_Ram_Concurrency(self):
self.getPage('/setsessiontype/ram')
self._test_Concurrency()
def test_2_File_Concurrency(self):
self.getPage('/setsessiontype/file')
self._test_Concurrency()
def _test_Concurrency(self):
client_thread_count = 5
request_count = 30
# Get initial cookie
self.getPage("/")
self.assertBody("1")
cookies = self.cookies
data_dict = {}
errors = []
def request(index):
if self.scheme == 'https':
c = HTTPSConnection('%s:%s' % (self.interface(), self.PORT))
else:
c = HTTPConnection('%s:%s' % (self.interface(), self.PORT))
for i in range(request_count):
c.putrequest('GET', '/')
for k, v in cookies:
c.putheader(k, v)
c.endheaders()
response = c.getresponse()
body = response.read()
if response.status != 200 or not body.isdigit():
errors.append((response.status, body))
else:
data_dict[index] = max(data_dict[index], int(body))
# Uncomment the following line to prove threads overlap.
## sys.stdout.write("%d " % index)
# Start <request_count> requests from each of
# <client_thread_count> concurrent clients
ts = []
for c in range(client_thread_count):
data_dict[c] = 0
t = threading.Thread(target=request, args=(c,))
ts.append(t)
t.start()
for t in ts:
t.join()
hitcount = max(data_dict.values())
expected = 1 + (client_thread_count * request_count)
for e in errors:
print(e)
self.assertEqual(hitcount, expected)
def test_3_Redirect(self):
# Start a new session
self.getPage('/testStr')
self.getPage('/iredir', self.cookies)
self.assertBody("file")
def test_4_File_deletion(self):
# Start a new session
self.getPage('/testStr')
# Delete the session file manually and retry.
id = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
path = os.path.join(localDir, "session-" + id)
os.unlink(path)
self.getPage('/testStr', self.cookies)
def test_5_Error_paths(self):
self.getPage('/unknown/page')
self.assertErrorPage(404, "The path '/unknown/page' was not found.")
# Note: this path is *not* the same as above. The above
# takes a normal route through the session code; this one
# skips the session code's before_handler and only calls
# before_finalize (save) and on_end (close). So the session
# code has to survive calling save/close without init.
self.getPage('/restricted', self.cookies, method='POST')
self.assertErrorPage(405, response_codes[405][1])
def test_6_regenerate(self):
self.getPage('/testStr')
# grab the cookie ID
id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
self.getPage('/regen')
self.assertBody('logged in')
id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
self.assertNotEqual(id1, id2)
self.getPage('/testStr')
# grab the cookie ID
id1 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
self.getPage('/testStr',
headers=[('Cookie',
'session_id=maliciousid; '
'expires=Sat, 27 Oct 2017 04:18:28 GMT; Path=/;')])
id2 = self.cookies[0][1].split(";", 1)[0].split("=", 1)[1]
self.assertNotEqual(id1, id2)
self.assertNotEqual(id2, 'maliciousid')
def test_7_session_cookies(self):
self.getPage('/setsessiontype/ram')
self.getPage('/clear')
self.getPage('/session_cookie')
# grab the cookie ID
cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
id1 = cookie_parts['temp']
self.assertEqual(copykeys(sessions.RamSession.cache), [id1])
# Send another request in the same "browser session".
self.getPage('/session_cookie', self.cookies)
cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
self.assertBody(id1)
self.assertEqual(copykeys(sessions.RamSession.cache), [id1])
# Simulate a browser close by just not sending the cookies
self.getPage('/session_cookie')
# grab the cookie ID
cookie_parts = dict([p.strip().split('=') for p in self.cookies[0][1].split(";")])
# Assert there is no 'expires' param
self.assertEqual(set(cookie_parts.keys()), set(['temp', 'Path']))
# Assert a new id has been generated...
id2 = cookie_parts['temp']
self.assertNotEqual(id1, id2)
self.assertEqual(set(sessions.RamSession.cache.keys()), set([id1, id2]))
# Wait for the session.timeout on both sessions
time.sleep(2.5)
cache = copykeys(sessions.RamSession.cache)
if cache:
if cache == [id2]:
self.fail("The second session did not time out.")
else:
self.fail("Unknown session id in cache: %r", cache)
import socket
try:
import memcache
host, port = '127.0.0.1', 11211
for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
s = None
try:
s = socket.socket(af, socktype, proto)
# See http://groups.google.com/group/cherrypy-users/
# browse_frm/thread/bbfe5eb39c904fe0
s.settimeout(1.0)
s.connect((host, port))
s.close()
except socket.error:
if s:
s.close()
raise
break
except (ImportError, socket.error):
class MemcachedSessionTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test(self):
return self.skip("memcached not reachable ")
else:
class MemcachedSessionTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def test_0_Session(self):
self.getPage('/setsessiontype/memcached')
self.getPage('/testStr')
self.assertBody('1')
self.getPage('/testGen', self.cookies)
self.assertBody('2')
self.getPage('/testStr', self.cookies)
self.assertBody('3')
self.getPage('/length', self.cookies)
self.assertErrorPage(500)
self.assertInBody("NotImplementedError")
self.getPage('/delkey?key=counter', self.cookies)
self.assertStatus(200)
# Wait for the session.timeout (1 second)
time.sleep(1.25)
self.getPage('/')
self.assertBody('1')
# Test session __contains__
self.getPage('/keyin?key=counter', self.cookies)
self.assertBody("True")
# Test session delete
self.getPage('/delete', self.cookies)
self.assertBody("done")
def test_1_Concurrency(self):
client_thread_count = 5
request_count = 30
# Get initial cookie
self.getPage("/")
self.assertBody("1")
cookies = self.cookies
data_dict = {}
def request(index):
for i in range(request_count):
self.getPage("/", cookies)
# Uncomment the following line to prove threads overlap.
## sys.stdout.write("%d " % index)
if not self.body.isdigit():
self.fail(self.body)
data_dict[index] = v = int(self.body)
# Start <request_count> concurrent requests from
# each of <client_thread_count> clients
ts = []
for c in range(client_thread_count):
data_dict[c] = 0
t = threading.Thread(target=request, args=(c,))
ts.append(t)
t.start()
for t in ts:
t.join()
hitcount = max(data_dict.values())
expected = 1 + (client_thread_count * request_count)
self.assertEqual(hitcount, expected)
def test_3_Redirect(self):
# Start a new session
self.getPage('/testStr')
self.getPage('/iredir', self.cookies)
self.assertBody("memcached")
def test_5_Error_paths(self):
self.getPage('/unknown/page')
self.assertErrorPage(404, "The path '/unknown/page' was not found.")
# Note: this path is *not* the same as above. The above
# takes a normal route through the session code; this one
# skips the session code's before_handler and only calls
# before_finalize (save) and on_end (close). So the session
# code has to survive calling save/close without init.
self.getPage('/restricted', self.cookies, method='POST')
self.assertErrorPage(405, response_codes[405][1])

View File

@ -1,62 +0,0 @@
import cherrypy
from cherrypy.test import helper
class SessionAuthenticateTest(helper.CPWebCase):
def setup_server():
def check(username, password):
# Dummy check_username_and_password function
if username != 'test' or password != 'password':
return 'Wrong login/password'
def augment_params():
# A simple tool to add some things to request.params
# This is to check to make sure that session_auth can handle request
# params (ticket #780)
cherrypy.request.params["test"] = "test"
cherrypy.tools.augment_params = cherrypy.Tool('before_handler',
augment_params, None, priority=30)
class Test:
_cp_config = {'tools.sessions.on': True,
'tools.session_auth.on': True,
'tools.session_auth.check_username_and_password': check,
'tools.augment_params.on': True,
}
def index(self, **kwargs):
return "Hi %s, you are logged in" % cherrypy.request.login
index.exposed = True
cherrypy.tree.mount(Test())
setup_server = staticmethod(setup_server)
def testSessionAuthenticate(self):
# request a page and check for login form
self.getPage('/')
self.assertInBody('<form method="post" action="do_login">')
# setup credentials
login_body = 'username=test&password=password&from_page=/'
# attempt a login
self.getPage('/do_login', method='POST', body=login_body)
self.assertStatus((302, 303))
# get the page now that we are logged in
self.getPage('/', self.cookies)
self.assertBody('Hi test, you are logged in')
# do a logout
self.getPage('/do_logout', self.cookies, method='POST')
self.assertStatus((302, 303))
# verify we are logged out
self.getPage('/', self.cookies)
self.assertInBody('<form method="post" action="do_login">')

View File

@ -1,439 +0,0 @@
from cherrypy._cpcompat import BadStatusLine, ntob
import os
import sys
import threading
import time
import cherrypy
engine = cherrypy.engine
thisdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
class Dependency:
def __init__(self, bus):
self.bus = bus
self.running = False
self.startcount = 0
self.gracecount = 0
self.threads = {}
def subscribe(self):
self.bus.subscribe('start', self.start)
self.bus.subscribe('stop', self.stop)
self.bus.subscribe('graceful', self.graceful)
self.bus.subscribe('start_thread', self.startthread)
self.bus.subscribe('stop_thread', self.stopthread)
def start(self):
self.running = True
self.startcount += 1
def stop(self):
self.running = False
def graceful(self):
self.gracecount += 1
def startthread(self, thread_id):
self.threads[thread_id] = None
def stopthread(self, thread_id):
del self.threads[thread_id]
db_connection = Dependency(engine)
def setup_server():
class Root:
def index(self):
return "Hello World"
index.exposed = True
def ctrlc(self):
raise KeyboardInterrupt()
ctrlc.exposed = True
def graceful(self):
engine.graceful()
return "app was (gracefully) restarted succesfully"
graceful.exposed = True
def block_explicit(self):
while True:
if cherrypy.response.timed_out:
cherrypy.response.timed_out = False
return "broken!"
time.sleep(0.01)
block_explicit.exposed = True
def block_implicit(self):
time.sleep(0.5)
return "response.timeout = %s" % cherrypy.response.timeout
block_implicit.exposed = True
cherrypy.tree.mount(Root())
cherrypy.config.update({
'environment': 'test_suite',
'engine.deadlock_poll_freq': 0.1,
})
db_connection.subscribe()
# ------------ Enough helpers. Time for real live test cases. ------------ #
from cherrypy.test import helper
class ServerStateTests(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def setUp(self):
cherrypy.server.socket_timeout = 0.1
self.do_gc_test = False
def test_0_NormalStateFlow(self):
engine.stop()
# Our db_connection should not be running
self.assertEqual(db_connection.running, False)
self.assertEqual(db_connection.startcount, 1)
self.assertEqual(len(db_connection.threads), 0)
# Test server start
engine.start()
self.assertEqual(engine.state, engine.states.STARTED)
host = cherrypy.server.socket_host
port = cherrypy.server.socket_port
self.assertRaises(IOError, cherrypy._cpserver.check_port, host, port)
# The db_connection should be running now
self.assertEqual(db_connection.running, True)
self.assertEqual(db_connection.startcount, 2)
self.assertEqual(len(db_connection.threads), 0)
self.getPage("/")
self.assertBody("Hello World")
self.assertEqual(len(db_connection.threads), 1)
# Test engine stop. This will also stop the HTTP server.
engine.stop()
self.assertEqual(engine.state, engine.states.STOPPED)
# Verify that our custom stop function was called
self.assertEqual(db_connection.running, False)
self.assertEqual(len(db_connection.threads), 0)
# Block the main thread now and verify that exit() works.
def exittest():
self.getPage("/")
self.assertBody("Hello World")
engine.exit()
cherrypy.server.start()
engine.start_with_callback(exittest)
engine.block()
self.assertEqual(engine.state, engine.states.EXITING)
def test_1_Restart(self):
cherrypy.server.start()
engine.start()
# The db_connection should be running now
self.assertEqual(db_connection.running, True)
grace = db_connection.gracecount
self.getPage("/")
self.assertBody("Hello World")
self.assertEqual(len(db_connection.threads), 1)
# Test server restart from this thread
engine.graceful()
self.assertEqual(engine.state, engine.states.STARTED)
self.getPage("/")
self.assertBody("Hello World")
self.assertEqual(db_connection.running, True)
self.assertEqual(db_connection.gracecount, grace + 1)
self.assertEqual(len(db_connection.threads), 1)
# Test server restart from inside a page handler
self.getPage("/graceful")
self.assertEqual(engine.state, engine.states.STARTED)
self.assertBody("app was (gracefully) restarted succesfully")
self.assertEqual(db_connection.running, True)
self.assertEqual(db_connection.gracecount, grace + 2)
# Since we are requesting synchronously, is only one thread used?
# Note that the "/graceful" request has been flushed.
self.assertEqual(len(db_connection.threads), 0)
engine.stop()
self.assertEqual(engine.state, engine.states.STOPPED)
self.assertEqual(db_connection.running, False)
self.assertEqual(len(db_connection.threads), 0)
def test_2_KeyboardInterrupt(self):
# Raise a keyboard interrupt in the HTTP server's main thread.
# We must start the server in this, the main thread
engine.start()
cherrypy.server.start()
self.persistent = True
try:
# Make the first request and assert there's no "Connection: close".
self.getPage("/")
self.assertStatus('200 OK')
self.assertBody("Hello World")
self.assertNoHeader("Connection")
cherrypy.server.httpserver.interrupt = KeyboardInterrupt
engine.block()
self.assertEqual(db_connection.running, False)
self.assertEqual(len(db_connection.threads), 0)
self.assertEqual(engine.state, engine.states.EXITING)
finally:
self.persistent = False
# Raise a keyboard interrupt in a page handler; on multithreaded
# servers, this should occur in one of the worker threads.
# This should raise a BadStatusLine error, since the worker
# thread will just die without writing a response.
engine.start()
cherrypy.server.start()
try:
self.getPage("/ctrlc")
except BadStatusLine:
pass
else:
print(self.body)
self.fail("AssertionError: BadStatusLine not raised")
engine.block()
self.assertEqual(db_connection.running, False)
self.assertEqual(len(db_connection.threads), 0)
def test_3_Deadlocks(self):
cherrypy.config.update({'response.timeout': 0.2})
engine.start()
cherrypy.server.start()
try:
self.assertNotEqual(engine.timeout_monitor.thread, None)
# Request a "normal" page.
self.assertEqual(engine.timeout_monitor.servings, [])
self.getPage("/")
self.assertBody("Hello World")
# request.close is called async.
while engine.timeout_monitor.servings:
sys.stdout.write(".")
time.sleep(0.01)
# Request a page that explicitly checks itself for deadlock.
# The deadlock_timeout should be 2 secs.
self.getPage("/block_explicit")
self.assertBody("broken!")
# Request a page that implicitly breaks deadlock.
# If we deadlock, we want to touch as little code as possible,
# so we won't even call handle_error, just bail ASAP.
self.getPage("/block_implicit")
self.assertStatus(500)
self.assertInBody("raise cherrypy.TimeoutError()")
finally:
engine.exit()
def test_4_Autoreload(self):
# Start the demo script in a new process
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
p.write_conf(
extra='test_case_name: "test_4_Autoreload"')
p.start(imports='cherrypy.test._test_states_demo')
try:
self.getPage("/start")
start = float(self.body)
# Give the autoreloader time to cache the file time.
time.sleep(2)
# Touch the file
os.utime(os.path.join(thisdir, "_test_states_demo.py"), None)
# Give the autoreloader time to re-exec the process
time.sleep(2)
host = cherrypy.server.socket_host
port = cherrypy.server.socket_port
cherrypy._cpserver.wait_for_occupied_port(host, port)
self.getPage("/start")
if not (float(self.body) > start):
raise AssertionError("start time %s not greater than %s" %
(float(self.body), start))
finally:
# Shut down the spawned process
self.getPage("/exit")
p.join()
def test_5_Start_Error(self):
# If a process errors during start, it should stop the engine
# and exit with a non-zero exit code.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
wait=True)
p.write_conf(
extra="""starterror: True
test_case_name: "test_5_Start_Error"
"""
)
p.start(imports='cherrypy.test._test_states_demo')
if p.exit_code == 0:
self.fail("Process failed to return nonzero exit code.")
class PluginTests(helper.CPWebCase):
def test_daemonize(self):
if os.name not in ['posix']:
return self.skip("skipped (not on posix) ")
self.HOST = '127.0.0.1'
self.PORT = 8081
# Spawn the process and wait, when this returns, the original process
# is finished. If it daemonized properly, we should still be able
# to access pages.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
wait=True, daemonize=True,
socket_host='127.0.0.1',
socket_port=8081)
p.write_conf(
extra='test_case_name: "test_daemonize"')
p.start(imports='cherrypy.test._test_states_demo')
try:
# Just get the pid of the daemonization process.
self.getPage("/pid")
self.assertStatus(200)
page_pid = int(self.body)
self.assertEqual(page_pid, p.get_pid())
finally:
# Shut down the spawned process
self.getPage("/exit")
p.join()
# Wait until here to test the exit code because we want to ensure
# that we wait for the daemon to finish running before we fail.
if p.exit_code != 0:
self.fail("Daemonized parent process failed to exit cleanly.")
class SignalHandlingTests(helper.CPWebCase):
def test_SIGHUP_tty(self):
# When not daemonized, SIGHUP should shut down the server.
try:
from signal import SIGHUP
except ImportError:
return self.skip("skipped (no SIGHUP) ")
# Spawn the process.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
p.write_conf(
extra='test_case_name: "test_SIGHUP_tty"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGHUP
os.kill(p.get_pid(), SIGHUP)
# This might hang if things aren't working right, but meh.
p.join()
def test_SIGHUP_daemonized(self):
# When daemonized, SIGHUP should restart the server.
try:
from signal import SIGHUP
except ImportError:
return self.skip("skipped (no SIGHUP) ")
if os.name not in ['posix']:
return self.skip("skipped (not on posix) ")
# Spawn the process and wait, when this returns, the original process
# is finished. If it daemonized properly, we should still be able
# to access pages.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
wait=True, daemonize=True)
p.write_conf(
extra='test_case_name: "test_SIGHUP_daemonized"')
p.start(imports='cherrypy.test._test_states_demo')
pid = p.get_pid()
try:
# Send a SIGHUP
os.kill(pid, SIGHUP)
# Give the server some time to restart
time.sleep(2)
self.getPage("/pid")
self.assertStatus(200)
new_pid = int(self.body)
self.assertNotEqual(new_pid, pid)
finally:
# Shut down the spawned process
self.getPage("/exit")
p.join()
def test_SIGTERM(self):
# SIGTERM should shut down the server whether daemonized or not.
try:
from signal import SIGTERM
except ImportError:
return self.skip("skipped (no SIGTERM) ")
try:
from os import kill
except ImportError:
return self.skip("skipped (no os.kill) ")
# Spawn a normal, undaemonized process.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
p.write_conf(
extra='test_case_name: "test_SIGTERM"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGTERM
os.kill(p.get_pid(), SIGTERM)
# This might hang if things aren't working right, but meh.
p.join()
if os.name in ['posix']:
# Spawn a daemonized process and test again.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'),
wait=True, daemonize=True)
p.write_conf(
extra='test_case_name: "test_SIGTERM_2"')
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGTERM
os.kill(p.get_pid(), SIGTERM)
# This might hang if things aren't working right, but meh.
p.join()
def test_signal_handler_unsubscribe(self):
try:
from signal import SIGTERM
except ImportError:
return self.skip("skipped (no SIGTERM) ")
try:
from os import kill
except ImportError:
return self.skip("skipped (no os.kill) ")
# Spawn a normal, undaemonized process.
p = helper.CPProcess(ssl=(self.scheme.lower()=='https'))
p.write_conf(
extra="""unsubsig: True
test_case_name: "test_signal_handler_unsubscribe"
""")
p.start(imports='cherrypy.test._test_states_demo')
# Send a SIGTERM
os.kill(p.get_pid(), SIGTERM)
# This might hang if things aren't working right, but meh.
p.join()
# Assert the old handler ran.
target_line = open(p.error_log, 'rb').readlines()[-10]
if not ntob("I am an old SIGTERM handler.") in target_line:
self.fail("Old SIGTERM handler did not run.\n%r" % target_line)

View File

@ -1,300 +0,0 @@
from cherrypy._cpcompat import HTTPConnection, HTTPSConnection, ntob
from cherrypy._cpcompat import BytesIO
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
has_space_filepath = os.path.join(curdir, 'static', 'has space.html')
bigfile_filepath = os.path.join(curdir, "static", "bigfile.log")
BIGFILE_SIZE = 1024 * 1024
import threading
import cherrypy
from cherrypy.lib import static
from cherrypy.test import helper
class StaticTest(helper.CPWebCase):
def setup_server():
if not os.path.exists(has_space_filepath):
open(has_space_filepath, 'wb').write(ntob('Hello, world\r\n'))
if not os.path.exists(bigfile_filepath):
open(bigfile_filepath, 'wb').write(ntob("x" * BIGFILE_SIZE))
class Root:
def bigfile(self):
from cherrypy.lib import static
self.f = static.serve_file(bigfile_filepath)
return self.f
bigfile.exposed = True
bigfile._cp_config = {'response.stream': True}
def tell(self):
if self.f.input.closed:
return ''
return repr(self.f.input.tell()).rstrip('L')
tell.exposed = True
def fileobj(self):
f = open(os.path.join(curdir, 'style.css'), 'rb')
return static.serve_fileobj(f, content_type='text/css')
fileobj.exposed = True
def bytesio(self):
f = BytesIO(ntob('Fee\nfie\nfo\nfum'))
return static.serve_fileobj(f, content_type='text/plain')
bytesio.exposed = True
class Static:
def index(self):
return 'You want the Baron? You can have the Baron!'
index.exposed = True
def dynamic(self):
return "This is a DYNAMIC page"
dynamic.exposed = True
root = Root()
root.static = Static()
rootconf = {
'/static': {
'tools.staticdir.on': True,
'tools.staticdir.dir': 'static',
'tools.staticdir.root': curdir,
},
'/style.css': {
'tools.staticfile.on': True,
'tools.staticfile.filename': os.path.join(curdir, 'style.css'),
},
'/docroot': {
'tools.staticdir.on': True,
'tools.staticdir.root': curdir,
'tools.staticdir.dir': 'static',
'tools.staticdir.index': 'index.html',
},
'/error': {
'tools.staticdir.on': True,
'request.show_tracebacks': True,
},
}
rootApp = cherrypy.Application(root)
rootApp.merge(rootconf)
test_app_conf = {
'/test': {
'tools.staticdir.index': 'index.html',
'tools.staticdir.on': True,
'tools.staticdir.root': curdir,
'tools.staticdir.dir': 'static',
},
}
testApp = cherrypy.Application(Static())
testApp.merge(test_app_conf)
vhost = cherrypy._cpwsgi.VirtualHost(rootApp, {'virt.net': testApp})
cherrypy.tree.graft(vhost)
setup_server = staticmethod(setup_server)
def teardown_server():
for f in (has_space_filepath, bigfile_filepath):
if os.path.exists(f):
try:
os.unlink(f)
except:
pass
teardown_server = staticmethod(teardown_server)
def testStatic(self):
self.getPage("/static/index.html")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html')
self.assertBody('Hello, world\r\n')
# Using a staticdir.root value in a subdir...
self.getPage("/docroot/index.html")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html')
self.assertBody('Hello, world\r\n')
# Check a filename with spaces in it
self.getPage("/static/has%20space.html")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html')
self.assertBody('Hello, world\r\n')
self.getPage("/style.css")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/css')
# Note: The body should be exactly 'Dummy stylesheet\n', but
# unfortunately some tools such as WinZip sometimes turn \n
# into \r\n on Windows when extracting the CherryPy tarball so
# we just check the content
self.assertMatchesBody('^Dummy stylesheet')
def test_fallthrough(self):
# Test that NotFound will then try dynamic handlers (see [878]).
self.getPage("/static/dynamic")
self.assertBody("This is a DYNAMIC page")
# Check a directory via fall-through to dynamic handler.
self.getPage("/static/")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html;charset=utf-8')
self.assertBody('You want the Baron? You can have the Baron!')
def test_index(self):
# Check a directory via "staticdir.index".
self.getPage("/docroot/")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/html')
self.assertBody('Hello, world\r\n')
# The same page should be returned even if redirected.
self.getPage("/docroot")
self.assertStatus(301)
self.assertHeader('Location', '%s/docroot/' % self.base())
self.assertMatchesBody("This resource .* <a href='%s/docroot/'>"
"%s/docroot/</a>." % (self.base(), self.base()))
def test_config_errors(self):
# Check that we get an error if no .file or .dir
self.getPage("/error/thing.html")
self.assertErrorPage(500)
self.assertMatchesBody(ntob("TypeError: staticdir\(\) takes at least 2 "
"(positional )?arguments \(0 given\)"))
def test_security(self):
# Test up-level security
self.getPage("/static/../../test/style.css")
self.assertStatus((400, 403))
def test_modif(self):
# Test modified-since on a reasonably-large file
self.getPage("/static/dirback.jpg")
self.assertStatus("200 OK")
lastmod = ""
for k, v in self.headers:
if k == 'Last-Modified':
lastmod = v
ims = ("If-Modified-Since", lastmod)
self.getPage("/static/dirback.jpg", headers=[ims])
self.assertStatus(304)
self.assertNoHeader("Content-Type")
self.assertNoHeader("Content-Length")
self.assertNoHeader("Content-Disposition")
self.assertBody("")
def test_755_vhost(self):
self.getPage("/test/", [('Host', 'virt.net')])
self.assertStatus(200)
self.getPage("/test", [('Host', 'virt.net')])
self.assertStatus(301)
self.assertHeader('Location', self.scheme + '://virt.net/test/')
def test_serve_fileobj(self):
self.getPage("/fileobj")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/css;charset=utf-8')
self.assertMatchesBody('^Dummy stylesheet')
def test_serve_bytesio(self):
self.getPage("/bytesio")
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/plain;charset=utf-8')
self.assertHeader('Content-Length', 14)
self.assertMatchesBody('Fee\nfie\nfo\nfum')
def test_file_stream(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Make an initial request
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/bigfile", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
body = ntob('')
remaining = BIGFILE_SIZE
while remaining > 0:
data = response.fp.read(65536)
if not data:
break
body += data
remaining -= len(data)
if self.scheme == "https":
newconn = HTTPSConnection
else:
newconn = HTTPConnection
s, h, b = helper.webtest.openURL(
ntob("/tell"), headers=[], host=self.HOST, port=self.PORT,
http_conn=newconn)
if not b:
# The file was closed on the server.
tell_position = BIGFILE_SIZE
else:
tell_position = int(b)
expected = len(body)
if tell_position >= BIGFILE_SIZE:
# We can't exactly control how much content the server asks for.
# Fudge it by only checking the first half of the reads.
if expected < (BIGFILE_SIZE / 2):
self.fail(
"The file should have advanced to position %r, but has "
"already advanced to the end of the file. It may not be "
"streamed as intended, or at the wrong chunk size (64k)" %
expected)
elif tell_position < expected:
self.fail(
"The file should have advanced to position %r, but has "
"only advanced to position %r. It may not be streamed "
"as intended, or at the wrong chunk size (65536)" %
(expected, tell_position))
if body != ntob("x" * BIGFILE_SIZE):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
(BIGFILE_SIZE, body[:50], len(body)))
conn.close()
def test_file_stream_deadlock(self):
if cherrypy.server.protocol_version != "HTTP/1.1":
return self.skip()
self.PROTOCOL = "HTTP/1.1"
# Make an initial request but abort early.
self.persistent = True
conn = self.HTTP_CONN
conn.putrequest("GET", "/bigfile", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
response = conn.response_class(conn.sock, method="GET")
response.begin()
self.assertEqual(response.status, 200)
body = response.fp.read(65536)
if body != ntob("x" * len(body)):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
(65536, body[:50], len(body)))
response.close()
conn.close()
# Make a second request, which should fetch the whole file.
self.persistent = False
self.getPage("/bigfile")
if self.body != ntob("x" * BIGFILE_SIZE):
self.fail("Body != 'x' * %d. Got %r instead (%d bytes)." %
(BIGFILE_SIZE, self.body[:50], len(body)))

View File

@ -1,399 +0,0 @@
"""Test the various means of instantiating and invoking tools."""
import gzip
import sys
from cherrypy._cpcompat import BytesIO, copyitems, itervalues
from cherrypy._cpcompat import IncompleteRead, ntob, ntou, py3k, xrange
import time
timeout = 0.2
import types
import cherrypy
from cherrypy import tools
europoundUnicode = ntou('\x80\xa3')
# Client-side code #
from cherrypy.test import helper
class ToolTests(helper.CPWebCase):
def setup_server():
# Put check_access in a custom toolbox with its own namespace
myauthtools = cherrypy._cptools.Toolbox("myauth")
def check_access(default=False):
if not getattr(cherrypy.request, "userid", default):
raise cherrypy.HTTPError(401)
myauthtools.check_access = cherrypy.Tool('before_request_body', check_access)
def numerify():
def number_it(body):
for chunk in body:
for k, v in cherrypy.request.numerify_map:
chunk = chunk.replace(k, v)
yield chunk
cherrypy.response.body = number_it(cherrypy.response.body)
class NumTool(cherrypy.Tool):
def _setup(self):
def makemap():
m = self._merged_args().get("map", {})
cherrypy.request.numerify_map = copyitems(m)
cherrypy.request.hooks.attach('on_start_resource', makemap)
def critical():
cherrypy.request.error_response = cherrypy.HTTPError(502).set_response
critical.failsafe = True
cherrypy.request.hooks.attach('on_start_resource', critical)
cherrypy.request.hooks.attach(self._point, self.callable)
tools.numerify = NumTool('before_finalize', numerify)
# It's not mandatory to inherit from cherrypy.Tool.
class NadsatTool:
def __init__(self):
self.ended = {}
self._name = "nadsat"
def nadsat(self):
def nadsat_it_up(body):
for chunk in body:
chunk = chunk.replace(ntob("good"), ntob("horrorshow"))
chunk = chunk.replace(ntob("piece"), ntob("lomtick"))
yield chunk
cherrypy.response.body = nadsat_it_up(cherrypy.response.body)
nadsat.priority = 0
def cleanup(self):
# This runs after the request has been completely written out.
cherrypy.response.body = [ntob("razdrez")]
id = cherrypy.request.params.get("id")
if id:
self.ended[id] = True
cleanup.failsafe = True
def _setup(self):
cherrypy.request.hooks.attach('before_finalize', self.nadsat)
cherrypy.request.hooks.attach('on_end_request', self.cleanup)
tools.nadsat = NadsatTool()
def pipe_body():
cherrypy.request.process_request_body = False
clen = int(cherrypy.request.headers['Content-Length'])
cherrypy.request.body = cherrypy.request.rfile.read(clen)
# Assert that we can use a callable object instead of a function.
class Rotator(object):
def __call__(self, scale):
r = cherrypy.response
r.collapse_body()
if py3k:
r.body = [bytes([(x + scale) % 256 for x in r.body[0]])]
else:
r.body = [chr((ord(x) + scale) % 256) for x in r.body[0]]
cherrypy.tools.rotator = cherrypy.Tool('before_finalize', Rotator())
def stream_handler(next_handler, *args, **kwargs):
cherrypy.response.output = o = BytesIO()
try:
response = next_handler(*args, **kwargs)
# Ignore the response and return our accumulated output instead.
return o.getvalue()
finally:
o.close()
cherrypy.tools.streamer = cherrypy._cptools.HandlerWrapperTool(stream_handler)
class Root:
def index(self):
return "Howdy earth!"
index.exposed = True
def tarfile(self):
cherrypy.response.output.write(ntob('I am '))
cherrypy.response.output.write(ntob('a tarfile'))
tarfile.exposed = True
tarfile._cp_config = {'tools.streamer.on': True}
def euro(self):
hooks = list(cherrypy.request.hooks['before_finalize'])
hooks.sort()
cbnames = [x.callback.__name__ for x in hooks]
assert cbnames == ['gzip'], cbnames
priorities = [x.priority for x in hooks]
assert priorities == [80], priorities
yield ntou("Hello,")
yield ntou("world")
yield europoundUnicode
euro.exposed = True
# Bare hooks
def pipe(self):
return cherrypy.request.body
pipe.exposed = True
pipe._cp_config = {'hooks.before_request_body': pipe_body}
# Multiple decorators; include kwargs just for fun.
# Note that rotator must run before gzip.
def decorated_euro(self, *vpath):
yield ntou("Hello,")
yield ntou("world")
yield europoundUnicode
decorated_euro.exposed = True
decorated_euro = tools.gzip(compress_level=6)(decorated_euro)
decorated_euro = tools.rotator(scale=3)(decorated_euro)
root = Root()
class TestType(type):
"""Metaclass which automatically exposes all functions in each subclass,
and adds an instance of the subclass as an attribute of root.
"""
def __init__(cls, name, bases, dct):
type.__init__(cls, name, bases, dct)
for value in itervalues(dct):
if isinstance(value, types.FunctionType):
value.exposed = True
setattr(root, name.lower(), cls())
Test = TestType('Test', (object,), {})
# METHOD ONE:
# Declare Tools in _cp_config
class Demo(Test):
_cp_config = {"tools.nadsat.on": True}
def index(self, id=None):
return "A good piece of cherry pie"
def ended(self, id):
return repr(tools.nadsat.ended[id])
def err(self, id=None):
raise ValueError()
def errinstream(self, id=None):
yield "nonconfidential"
raise ValueError()
yield "confidential"
# METHOD TWO: decorator using Tool()
# We support Python 2.3, but the @-deco syntax would look like this:
# @tools.check_access()
def restricted(self):
return "Welcome!"
restricted = myauthtools.check_access()(restricted)
userid = restricted
def err_in_onstart(self):
return "success!"
def stream(self, id=None):
for x in xrange(100000000):
yield str(x)
stream._cp_config = {'response.stream': True}
conf = {
# METHOD THREE:
# Declare Tools in detached config
'/demo': {
'tools.numerify.on': True,
'tools.numerify.map': {ntob("pie"): ntob("3.14159")},
},
'/demo/restricted': {
'request.show_tracebacks': False,
},
'/demo/userid': {
'request.show_tracebacks': False,
'myauth.check_access.default': True,
},
'/demo/errinstream': {
'response.stream': True,
},
'/demo/err_in_onstart': {
# Because this isn't a dict, on_start_resource will error.
'tools.numerify.map': "pie->3.14159"
},
# Combined tools
'/euro': {
'tools.gzip.on': True,
'tools.encode.on': True,
},
# Priority specified in config
'/decorated_euro/subpath': {
'tools.gzip.priority': 10,
},
# Handler wrappers
'/tarfile': {'tools.streamer.on': True}
}
app = cherrypy.tree.mount(root, config=conf)
app.request_class.namespaces['myauth'] = myauthtools
if sys.version_info >= (2, 5):
from cherrypy.test import _test_decorators
root.tooldecs = _test_decorators.ToolExamples()
setup_server = staticmethod(setup_server)
def testHookErrors(self):
self.getPage("/demo/?id=1")
# If body is "razdrez", then on_end_request is being called too early.
self.assertBody("A horrorshow lomtick of cherry 3.14159")
# If this fails, then on_end_request isn't being called at all.
time.sleep(0.1)
self.getPage("/demo/ended/1")
self.assertBody("True")
valerr = '\n raise ValueError()\nValueError'
self.getPage("/demo/err?id=3")
# If body is "razdrez", then on_end_request is being called too early.
self.assertErrorPage(502, pattern=valerr)
# If this fails, then on_end_request isn't being called at all.
time.sleep(0.1)
self.getPage("/demo/ended/3")
self.assertBody("True")
# If body is "razdrez", then on_end_request is being called too early.
if (cherrypy.server.protocol_version == "HTTP/1.0" or
getattr(cherrypy.server, "using_apache", False)):
self.getPage("/demo/errinstream?id=5")
# Because this error is raised after the response body has
# started, the status should not change to an error status.
self.assertStatus("200 OK")
self.assertBody("nonconfidential")
else:
# Because this error is raised after the response body has
# started, and because it's chunked output, an error is raised by
# the HTTP client when it encounters incomplete output.
self.assertRaises((ValueError, IncompleteRead), self.getPage,
"/demo/errinstream?id=5")
# If this fails, then on_end_request isn't being called at all.
time.sleep(0.1)
self.getPage("/demo/ended/5")
self.assertBody("True")
# Test the "__call__" technique (compile-time decorator).
self.getPage("/demo/restricted")
self.assertErrorPage(401)
# Test compile-time decorator with kwargs from config.
self.getPage("/demo/userid")
self.assertBody("Welcome!")
def testEndRequestOnDrop(self):
old_timeout = None
try:
httpserver = cherrypy.server.httpserver
old_timeout = httpserver.timeout
except (AttributeError, IndexError):
return self.skip()
try:
httpserver.timeout = timeout
# Test that on_end_request is called even if the client drops.
self.persistent = True
try:
conn = self.HTTP_CONN
conn.putrequest("GET", "/demo/stream?id=9", skip_host=True)
conn.putheader("Host", self.HOST)
conn.endheaders()
# Skip the rest of the request and close the conn. This will
# cause the server's active socket to error, which *should*
# result in the request being aborted, and request.close being
# called all the way up the stack (including WSGI middleware),
# eventually calling our on_end_request hook.
finally:
self.persistent = False
time.sleep(timeout * 2)
# Test that the on_end_request hook was called.
self.getPage("/demo/ended/9")
self.assertBody("True")
finally:
if old_timeout is not None:
httpserver.timeout = old_timeout
def testGuaranteedHooks(self):
# The 'critical' on_start_resource hook is 'failsafe' (guaranteed
# to run even if there are failures in other on_start methods).
# This is NOT true of the other hooks.
# Here, we have set up a failure in NumerifyTool.numerify_map,
# but our 'critical' hook should run and set the error to 502.
self.getPage("/demo/err_in_onstart")
self.assertErrorPage(502)
self.assertInBody("AttributeError: 'str' object has no attribute 'items'")
def testCombinedTools(self):
expectedResult = (ntou("Hello,world") + europoundUnicode).encode('utf-8')
zbuf = BytesIO()
zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
zfile.write(expectedResult)
zfile.close()
self.getPage("/euro", headers=[("Accept-Encoding", "gzip"),
("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7")])
self.assertInBody(zbuf.getvalue()[:3])
zbuf = BytesIO()
zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=6)
zfile.write(expectedResult)
zfile.close()
self.getPage("/decorated_euro", headers=[("Accept-Encoding", "gzip")])
self.assertInBody(zbuf.getvalue()[:3])
# This returns a different value because gzip's priority was
# lowered in conf, allowing the rotator to run after gzip.
# Of course, we don't want breakage in production apps,
# but it proves the priority was changed.
self.getPage("/decorated_euro/subpath",
headers=[("Accept-Encoding", "gzip")])
if py3k:
self.assertInBody(bytes([(x + 3) % 256 for x in zbuf.getvalue()]))
else:
self.assertInBody(''.join([chr((ord(x) + 3) % 256) for x in zbuf.getvalue()]))
def testBareHooks(self):
content = "bit of a pain in me gulliver"
self.getPage("/pipe",
headers=[("Content-Length", str(len(content))),
("Content-Type", "text/plain")],
method="POST", body=content)
self.assertBody(content)
def testHandlerWrapperTool(self):
self.getPage("/tarfile")
self.assertBody("I am a tarfile")
def testToolWithConfig(self):
if not sys.version_info >= (2, 5):
return self.skip("skipped (Python 2.5+ only)")
self.getPage('/tooldecs/blah')
self.assertHeader('Content-Type', 'application/data')
def testWarnToolOn(self):
# get
try:
numon = cherrypy.tools.numerify.on
except AttributeError:
pass
else:
raise AssertionError("Tool.on did not error as it should have.")
# set
try:
cherrypy.tools.numerify.on = True
except AttributeError:
pass
else:
raise AssertionError("Tool.on did not error as it should have.")

View File

@ -1,201 +0,0 @@
import sys
import cherrypy
from cherrypy.test import helper
class TutorialTest(helper.CPWebCase):
def setup_server(cls):
conf = cherrypy.config.copy()
def load_tut_module(name):
"""Import or reload tutorial module as needed."""
cherrypy.config.reset()
cherrypy.config.update(conf)
target = "cherrypy.tutorial." + name
if target in sys.modules:
module = reload(sys.modules[target])
else:
module = __import__(target, globals(), locals(), [''])
# The above import will probably mount a new app at "".
app = cherrypy.tree.apps[""]
app.root.load_tut_module = load_tut_module
app.root.sessions = sessions
app.root.traceback_setting = traceback_setting
cls.supervisor.sync_apps()
load_tut_module.exposed = True
def sessions():
cherrypy.config.update({"tools.sessions.on": True})
sessions.exposed = True
def traceback_setting():
return repr(cherrypy.request.show_tracebacks)
traceback_setting.exposed = True
class Dummy:
pass
root = Dummy()
root.load_tut_module = load_tut_module
cherrypy.tree.mount(root)
setup_server = classmethod(setup_server)
def test01HelloWorld(self):
self.getPage("/load_tut_module/tut01_helloworld")
self.getPage("/")
self.assertBody('Hello world!')
def test02ExposeMethods(self):
self.getPage("/load_tut_module/tut02_expose_methods")
self.getPage("/showMessage")
self.assertBody('Hello world!')
def test03GetAndPost(self):
self.getPage("/load_tut_module/tut03_get_and_post")
# Try different GET queries
self.getPage("/greetUser?name=Bob")
self.assertBody("Hey Bob, what's up?")
self.getPage("/greetUser")
self.assertBody('Please enter your name <a href="./">here</a>.')
self.getPage("/greetUser?name=")
self.assertBody('No, really, enter your name <a href="./">here</a>.')
# Try the same with POST
self.getPage("/greetUser", method="POST", body="name=Bob")
self.assertBody("Hey Bob, what's up?")
self.getPage("/greetUser", method="POST", body="name=")
self.assertBody('No, really, enter your name <a href="./">here</a>.')
def test04ComplexSite(self):
self.getPage("/load_tut_module/tut04_complex_site")
msg = '''
<p>Here are some extra useful links:</p>
<ul>
<li><a href="http://del.icio.us">del.icio.us</a></li>
<li><a href="http://www.mornography.de">Hendrik's weblog</a></li>
</ul>
<p>[<a href="../">Return to links page</a>]</p>'''
self.getPage("/links/extra/")
self.assertBody(msg)
def test05DerivedObjects(self):
self.getPage("/load_tut_module/tut05_derived_objects")
msg = '''
<html>
<head>
<title>Another Page</title>
<head>
<body>
<h2>Another Page</h2>
<p>
And this is the amazing second page!
</p>
</body>
</html>
'''
self.getPage("/another/")
self.assertBody(msg)
def test06DefaultMethod(self):
self.getPage("/load_tut_module/tut06_default_method")
self.getPage('/hendrik')
self.assertBody('Hendrik Mans, CherryPy co-developer & crazy German '
'(<a href="./">back</a>)')
def test07Sessions(self):
self.getPage("/load_tut_module/tut07_sessions")
self.getPage("/sessions")
self.getPage('/')
self.assertBody("\n During your current session, you've viewed this"
"\n page 1 times! Your life is a patio of fun!"
"\n ")
self.getPage('/', self.cookies)
self.assertBody("\n During your current session, you've viewed this"
"\n page 2 times! Your life is a patio of fun!"
"\n ")
def test08GeneratorsAndYield(self):
self.getPage("/load_tut_module/tut08_generators_and_yield")
self.getPage('/')
self.assertBody('<html><body><h2>Generators rule!</h2>'
'<h3>List of users:</h3>'
'Remi<br/>Carlos<br/>Hendrik<br/>Lorenzo Lamas<br/>'
'</body></html>')
def test09Files(self):
self.getPage("/load_tut_module/tut09_files")
# Test upload
filesize = 5
h = [("Content-type", "multipart/form-data; boundary=x"),
("Content-Length", str(105 + filesize))]
b = '--x\n' + \
'Content-Disposition: form-data; name="myFile"; filename="hello.txt"\r\n' + \
'Content-Type: text/plain\r\n' + \
'\r\n' + \
'a' * filesize + '\n' + \
'--x--\n'
self.getPage('/upload', h, "POST", b)
self.assertBody('''<html>
<body>
myFile length: %d<br />
myFile filename: hello.txt<br />
myFile mime-type: text/plain
</body>
</html>''' % filesize)
# Test download
self.getPage('/download')
self.assertStatus("200 OK")
self.assertHeader("Content-Type", "application/x-download")
self.assertHeader("Content-Disposition",
# Make sure the filename is quoted.
'attachment; filename="pdf_file.pdf"')
self.assertEqual(len(self.body), 85698)
def test10HTTPErrors(self):
self.getPage("/load_tut_module/tut10_http_errors")
self.getPage("/")
self.assertInBody("""<a href="toggleTracebacks">""")
self.assertInBody("""<a href="/doesNotExist">""")
self.assertInBody("""<a href="/error?code=403">""")
self.assertInBody("""<a href="/error?code=500">""")
self.assertInBody("""<a href="/messageArg">""")
self.getPage("/traceback_setting")
setting = self.body
self.getPage("/toggleTracebacks")
self.assertStatus((302, 303))
self.getPage("/traceback_setting")
self.assertBody(str(not eval(setting)))
self.getPage("/error?code=500")
self.assertStatus(500)
self.assertInBody("The server encountered an unexpected condition "
"which prevented it from fulfilling the request.")
self.getPage("/error?code=403")
self.assertStatus(403)
self.assertInBody("<h2>You can't do that!</h2>")
self.getPage("/messageArg")
self.assertStatus(500)
self.assertInBody("If you construct an HTTPError with a 'message'")

View File

@ -1,107 +0,0 @@
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import cherrypy
from cherrypy.test import helper
class VirtualHostTest(helper.CPWebCase):
def setup_server():
class Root:
def index(self):
return "Hello, world"
index.exposed = True
def dom4(self):
return "Under construction"
dom4.exposed = True
def method(self, value):
return "You sent %s" % value
method.exposed = True
class VHost:
def __init__(self, sitename):
self.sitename = sitename
def index(self):
return "Welcome to %s" % self.sitename
index.exposed = True
def vmethod(self, value):
return "You sent %s" % value
vmethod.exposed = True
def url(self):
return cherrypy.url("nextpage")
url.exposed = True
# Test static as a handler (section must NOT include vhost prefix)
static = cherrypy.tools.staticdir.handler(section='/static', dir=curdir)
root = Root()
root.mydom2 = VHost("Domain 2")
root.mydom3 = VHost("Domain 3")
hostmap = {'www.mydom2.com': '/mydom2',
'www.mydom3.com': '/mydom3',
'www.mydom4.com': '/dom4',
}
cherrypy.tree.mount(root, config={
'/': {'request.dispatch': cherrypy.dispatch.VirtualHost(**hostmap)},
# Test static in config (section must include vhost prefix)
'/mydom2/static2': {'tools.staticdir.on': True,
'tools.staticdir.root': curdir,
'tools.staticdir.dir': 'static',
'tools.staticdir.index': 'index.html',
},
})
setup_server = staticmethod(setup_server)
def testVirtualHost(self):
self.getPage("/", [('Host', 'www.mydom1.com')])
self.assertBody('Hello, world')
self.getPage("/mydom2/", [('Host', 'www.mydom1.com')])
self.assertBody('Welcome to Domain 2')
self.getPage("/", [('Host', 'www.mydom2.com')])
self.assertBody('Welcome to Domain 2')
self.getPage("/", [('Host', 'www.mydom3.com')])
self.assertBody('Welcome to Domain 3')
self.getPage("/", [('Host', 'www.mydom4.com')])
self.assertBody('Under construction')
# Test GET, POST, and positional params
self.getPage("/method?value=root")
self.assertBody("You sent root")
self.getPage("/vmethod?value=dom2+GET", [('Host', 'www.mydom2.com')])
self.assertBody("You sent dom2 GET")
self.getPage("/vmethod", [('Host', 'www.mydom3.com')], method="POST",
body="value=dom3+POST")
self.assertBody("You sent dom3 POST")
self.getPage("/vmethod/pos", [('Host', 'www.mydom3.com')])
self.assertBody("You sent pos")
# Test that cherrypy.url uses the browser url, not the virtual url
self.getPage("/url", [('Host', 'www.mydom2.com')])
self.assertBody("%s://www.mydom2.com/nextpage" % self.scheme)
def test_VHost_plus_Static(self):
# Test static as a handler
self.getPage("/static/style.css", [('Host', 'www.mydom2.com')])
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'text/css;charset=utf-8')
# Test static in config
self.getPage("/static2/dirback.jpg", [('Host', 'www.mydom2.com')])
self.assertStatus('200 OK')
self.assertHeader('Content-Type', 'image/jpeg')
# Test static config with "index" arg
self.getPage("/static2/", [('Host', 'www.mydom2.com')])
self.assertStatus('200 OK')
self.assertBody('Hello, world\r\n')
# Since tools.trailing_slash is on by default, this should redirect
self.getPage("/static2", [('Host', 'www.mydom2.com')])
self.assertStatus(301)

View File

@ -1,91 +0,0 @@
import cherrypy
from cherrypy._cpcompat import ntob
from cherrypy.test import helper
class WSGI_Namespace_Test(helper.CPWebCase):
def setup_server():
class WSGIResponse(object):
def __init__(self, appresults):
self.appresults = appresults
self.iter = iter(appresults)
def __iter__(self):
return self
def next(self):
return self.iter.next()
def __next__(self):
return next(self.iter)
def close(self):
if hasattr(self.appresults, "close"):
self.appresults.close()
class ChangeCase(object):
def __init__(self, app, to=None):
self.app = app
self.to = to
def __call__(self, environ, start_response):
res = self.app(environ, start_response)
class CaseResults(WSGIResponse):
def next(this):
return getattr(this.iter.next(), self.to)()
def __next__(this):
return getattr(next(this.iter), self.to)()
return CaseResults(res)
class Replacer(object):
def __init__(self, app, map={}):
self.app = app
self.map = map
def __call__(self, environ, start_response):
res = self.app(environ, start_response)
class ReplaceResults(WSGIResponse):
def next(this):
line = this.iter.next()
for k, v in self.map.iteritems():
line = line.replace(k, v)
return line
def __next__(this):
line = next(this.iter)
for k, v in self.map.items():
line = line.replace(k, v)
return line
return ReplaceResults(res)
class Root(object):
def index(self):
return "HellO WoRlD!"
index.exposed = True
root_conf = {'wsgi.pipeline': [('replace', Replacer)],
'wsgi.replace.map': {ntob('L'): ntob('X'),
ntob('l'): ntob('r')},
}
app = cherrypy.Application(Root())
app.wsgiapp.pipeline.append(('changecase', ChangeCase))
app.wsgiapp.config['changecase'] = {'to': 'upper'}
cherrypy.tree.mount(app, config={'/': root_conf})
setup_server = staticmethod(setup_server)
def test_pipeline(self):
if not cherrypy.server.httpserver:
return self.skip()
self.getPage("/")
# If body is "HEXXO WORXD!", the middleware was applied out of order.
self.assertBody("HERRO WORRD!")

View File

@ -1,36 +0,0 @@
import cherrypy
from cherrypy.test import helper
class WSGI_VirtualHost_Test(helper.CPWebCase):
def setup_server():
class ClassOfRoot(object):
def __init__(self, name):
self.name = name
def index(self):
return "Welcome to the %s website!" % self.name
index.exposed = True
default = cherrypy.Application(None)
domains = {}
for year in range(1997, 2008):
app = cherrypy.Application(ClassOfRoot('Class of %s' % year))
domains['www.classof%s.example' % year] = app
cherrypy.tree.graft(cherrypy._cpwsgi.VirtualHost(default, domains))
setup_server = staticmethod(setup_server)
def test_welcome(self):
if not cherrypy.server.using_wsgi:
return self.skip("skipped (not using WSGI)... ")
for year in range(1997, 2008):
self.getPage("/", headers=[('Host', 'www.classof%s.example' % year)])
self.assertBody("Welcome to the Class of %s website!" % year)

View File

@ -1,118 +0,0 @@
from cherrypy._cpcompat import ntob
from cherrypy.test import helper
class WSGIGraftTests(helper.CPWebCase):
def setup_server():
import os
curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))
import cherrypy
def test_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
output = ['Hello, world!\n',
'This is a wsgi app running within CherryPy!\n\n']
keys = list(environ.keys())
keys.sort()
for k in keys:
output.append('%s: %s\n' % (k,environ[k]))
return [ntob(x, 'utf-8') for x in output]
def test_empty_string_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [ntob('Hello'), ntob(''), ntob(' '), ntob(''), ntob('world')]
class WSGIResponse(object):
def __init__(self, appresults):
self.appresults = appresults
self.iter = iter(appresults)
def __iter__(self):
return self
def next(self):
return self.iter.next()
def __next__(self):
return next(self.iter)
def close(self):
if hasattr(self.appresults, "close"):
self.appresults.close()
class ReversingMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
results = app(environ, start_response)
class Reverser(WSGIResponse):
def next(this):
line = list(this.iter.next())
line.reverse()
return "".join(line)
def __next__(this):
line = list(next(this.iter))
line.reverse()
return bytes(line)
return Reverser(results)
class Root:
def index(self):
return ntob("I'm a regular CherryPy page handler!")
index.exposed = True
cherrypy.tree.mount(Root())
cherrypy.tree.graft(test_app, '/hosted/app1')
cherrypy.tree.graft(test_empty_string_app, '/hosted/app3')
# Set script_name explicitly to None to signal CP that it should
# be pulled from the WSGI environ each time.
app = cherrypy.Application(Root(), script_name=None)
cherrypy.tree.graft(ReversingMiddleware(app), '/hosted/app2')
setup_server = staticmethod(setup_server)
wsgi_output = '''Hello, world!
This is a wsgi app running within CherryPy!'''
def test_01_standard_app(self):
self.getPage("/")
self.assertBody("I'm a regular CherryPy page handler!")
def test_04_pure_wsgi(self):
import cherrypy
if not cherrypy.server.using_wsgi:
return self.skip("skipped (not using WSGI)... ")
self.getPage("/hosted/app1")
self.assertHeader("Content-Type", "text/plain")
self.assertInBody(self.wsgi_output)
def test_05_wrapped_cp_app(self):
import cherrypy
if not cherrypy.server.using_wsgi:
return self.skip("skipped (not using WSGI)... ")
self.getPage("/hosted/app2/")
body = list("I'm a regular CherryPy page handler!")
body.reverse()
body = "".join(body)
self.assertInBody(body)
def test_06_empty_string_app(self):
import cherrypy
if not cherrypy.server.using_wsgi:
return self.skip("skipped (not using WSGI)... ")
self.getPage("/hosted/app3")
self.assertHeader("Content-Type", "text/plain")
self.assertInBody('Hello world')

View File

@ -1,179 +0,0 @@
import sys
from cherrypy._cpcompat import py3k
try:
from xmlrpclib import DateTime, Fault, ProtocolError, ServerProxy, SafeTransport
except ImportError:
from xmlrpc.client import DateTime, Fault, ProtocolError, ServerProxy, SafeTransport
if py3k:
HTTPSTransport = SafeTransport
# Python 3.0's SafeTransport still mistakenly checks for socket.ssl
import socket
if not hasattr(socket, "ssl"):
socket.ssl = True
else:
class HTTPSTransport(SafeTransport):
"""Subclass of SafeTransport to fix sock.recv errors (by using file)."""
def request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
h = self.make_connection(host)
if verbose:
h.set_debuglevel(1)
self.send_request(h, handler, request_body)
self.send_host(h, host)
self.send_user_agent(h)
self.send_content(h, request_body)
errcode, errmsg, headers = h.getreply()
if errcode != 200:
raise ProtocolError(host + handler, errcode, errmsg, headers)
self.verbose = verbose
# Here's where we differ from the superclass. It says:
# try:
# sock = h._conn.sock
# except AttributeError:
# sock = None
# return self._parse_response(h.getfile(), sock)
return self.parse_response(h.getfile())
import cherrypy
def setup_server():
from cherrypy import _cptools
class Root:
def index(self):
return "I'm a standard index!"
index.exposed = True
class XmlRpc(_cptools.XMLRPCController):
def foo(self):
return "Hello world!"
foo.exposed = True
def return_single_item_list(self):
return [42]
return_single_item_list.exposed = True
def return_string(self):
return "here is a string"
return_string.exposed = True
def return_tuple(self):
return ('here', 'is', 1, 'tuple')
return_tuple.exposed = True
def return_dict(self):
return dict(a=1, b=2, c=3)
return_dict.exposed = True
def return_composite(self):
return dict(a=1,z=26), 'hi', ['welcome', 'friend']
return_composite.exposed = True
def return_int(self):
return 42
return_int.exposed = True
def return_float(self):
return 3.14
return_float.exposed = True
def return_datetime(self):
return DateTime((2003, 10, 7, 8, 1, 0, 1, 280, -1))
return_datetime.exposed = True
def return_boolean(self):
return True
return_boolean.exposed = True
def test_argument_passing(self, num):
return num * 2
test_argument_passing.exposed = True
def test_returning_Fault(self):
return Fault(1, "custom Fault response")
test_returning_Fault.exposed = True
root = Root()
root.xmlrpc = XmlRpc()
cherrypy.tree.mount(root, config={'/': {
'request.dispatch': cherrypy.dispatch.XMLRPCDispatcher(),
'tools.xmlrpc.allow_none': 0,
}})
from cherrypy.test import helper
class XmlRpcTest(helper.CPWebCase):
setup_server = staticmethod(setup_server)
def testXmlRpc(self):
scheme = self.scheme
if scheme == "https":
url = 'https://%s:%s/xmlrpc/' % (self.interface(), self.PORT)
proxy = ServerProxy(url, transport=HTTPSTransport())
else:
url = 'http://%s:%s/xmlrpc/' % (self.interface(), self.PORT)
proxy = ServerProxy(url)
# begin the tests ...
self.getPage("/xmlrpc/foo")
self.assertBody("Hello world!")
self.assertEqual(proxy.return_single_item_list(), [42])
self.assertNotEqual(proxy.return_single_item_list(), 'one bazillion')
self.assertEqual(proxy.return_string(), "here is a string")
self.assertEqual(proxy.return_tuple(), list(('here', 'is', 1, 'tuple')))
self.assertEqual(proxy.return_dict(), {'a': 1, 'c': 3, 'b': 2})
self.assertEqual(proxy.return_composite(),
[{'a': 1, 'z': 26}, 'hi', ['welcome', 'friend']])
self.assertEqual(proxy.return_int(), 42)
self.assertEqual(proxy.return_float(), 3.14)
self.assertEqual(proxy.return_datetime(),
DateTime((2003, 10, 7, 8, 1, 0, 1, 280, -1)))
self.assertEqual(proxy.return_boolean(), True)
self.assertEqual(proxy.test_argument_passing(22), 22 * 2)
# Test an error in the page handler (should raise an xmlrpclib.Fault)
try:
proxy.test_argument_passing({})
except Exception:
x = sys.exc_info()[1]
self.assertEqual(x.__class__, Fault)
self.assertEqual(x.faultString, ("unsupported operand type(s) "
"for *: 'dict' and 'int'"))
else:
self.fail("Expected xmlrpclib.Fault")
# http://www.cherrypy.org/ticket/533
# if a method is not found, an xmlrpclib.Fault should be raised
try:
proxy.non_method()
except Exception:
x = sys.exc_info()[1]
self.assertEqual(x.__class__, Fault)
self.assertEqual(x.faultString, 'method "non_method" is not supported')
else:
self.fail("Expected xmlrpclib.Fault")
# Test returning a Fault from the page handler.
try:
proxy.test_returning_Fault()
except Exception:
x = sys.exc_info()[1]
self.assertEqual(x.__class__, Fault)
self.assertEqual(x.faultString, ("custom Fault response"))
else:
self.fail("Expected xmlrpclib.Fault")

View File

@ -1,575 +0,0 @@
"""Extensions to unittest for web frameworks.
Use the WebCase.getPage method to request a page from your HTTP server.
Framework Integration
=====================
If you have control over your server process, you can handle errors
in the server-side of the HTTP conversation a bit better. You must run
both the client (your WebCase tests) and the server in the same process
(but in separate threads, obviously).
When an error occurs in the framework, call server_error. It will print
the traceback to stdout, and keep any assertions you have from running
(the assumption is that, if the server errors, the page output will not
be of further significance to your tests).
"""
import os
import pprint
import re
import socket
import sys
import time
import traceback
import types
from unittest import *
from unittest import _TextTestResult
from cherrypy._cpcompat import basestring, ntob, py3k, HTTPConnection, HTTPSConnection, unicodestr
def interface(host):
"""Return an IP address for a client connection given the server host.
If the server is listening on '0.0.0.0' (INADDR_ANY)
or '::' (IN6ADDR_ANY), this will return the proper localhost."""
if host == '0.0.0.0':
# INADDR_ANY, which should respond on localhost.
return "127.0.0.1"
if host == '::':
# IN6ADDR_ANY, which should respond on localhost.
return "::1"
return host
class TerseTestResult(_TextTestResult):
def printErrors(self):
# Overridden to avoid unnecessary empty line
if self.errors or self.failures:
if self.dots or self.showAll:
self.stream.writeln()
self.printErrorList('ERROR', self.errors)
self.printErrorList('FAIL', self.failures)
class TerseTestRunner(TextTestRunner):
"""A test runner class that displays results in textual form."""
def _makeResult(self):
return TerseTestResult(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
# Overridden to remove unnecessary empty lines and separators
result = self._makeResult()
test(result)
result.printErrors()
if not result.wasSuccessful():
self.stream.write("FAILED (")
failed, errored = list(map(len, (result.failures, result.errors)))
if failed:
self.stream.write("failures=%d" % failed)
if errored:
if failed: self.stream.write(", ")
self.stream.write("errors=%d" % errored)
self.stream.writeln(")")
return result
class ReloadingTestLoader(TestLoader):
def loadTestsFromName(self, name, module=None):
"""Return a suite of all tests cases given a string specifier.
The name may resolve either to a module, a test case class, a
test method within a test case class, or a callable object which
returns a TestCase or TestSuite instance.
The method optionally resolves the names relative to a given module.
"""
parts = name.split('.')
unused_parts = []
if module is None:
if not parts:
raise ValueError("incomplete test name: %s" % name)
else:
parts_copy = parts[:]
while parts_copy:
target = ".".join(parts_copy)
if target in sys.modules:
module = reload(sys.modules[target])
parts = unused_parts
break
else:
try:
module = __import__(target)
parts = unused_parts
break
except ImportError:
unused_parts.insert(0,parts_copy[-1])
del parts_copy[-1]
if not parts_copy:
raise
parts = parts[1:]
obj = module
for part in parts:
obj = getattr(obj, part)
if type(obj) == types.ModuleType:
return self.loadTestsFromModule(obj)
elif (((py3k and isinstance(obj, type))
or isinstance(obj, (type, types.ClassType)))
and issubclass(obj, TestCase)):
return self.loadTestsFromTestCase(obj)
elif type(obj) == types.UnboundMethodType:
if py3k:
return obj.__self__.__class__(obj.__name__)
else:
return obj.im_class(obj.__name__)
elif hasattr(obj, '__call__'):
test = obj()
if not isinstance(test, TestCase) and \
not isinstance(test, TestSuite):
raise ValueError("calling %s returned %s, "
"not a test" % (obj,test))
return test
else:
raise ValueError("do not know how to make test from: %s" % obj)
try:
# Jython support
if sys.platform[:4] == 'java':
def getchar():
# Hopefully this is enough
return sys.stdin.read(1)
else:
# On Windows, msvcrt.getch reads a single char without output.
import msvcrt
def getchar():
return msvcrt.getch()
except ImportError:
# Unix getchr
import tty, termios
def getchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class WebCase(TestCase):
HOST = "127.0.0.1"
PORT = 8000
HTTP_CONN = HTTPConnection
PROTOCOL = "HTTP/1.1"
scheme = "http"
url = None
status = None
headers = None
body = None
encoding = 'utf-8'
time = None
def get_conn(self, auto_open=False):
"""Return a connection to our HTTP server."""
if self.scheme == "https":
cls = HTTPSConnection
else:
cls = HTTPConnection
conn = cls(self.interface(), self.PORT)
# Automatically re-connect?
conn.auto_open = auto_open
conn.connect()
return conn
def set_persistent(self, on=True, auto_open=False):
"""Make our HTTP_CONN persistent (or not).
If the 'on' argument is True (the default), then self.HTTP_CONN
will be set to an instance of HTTPConnection (or HTTPS
if self.scheme is "https"). This will then persist across requests.
We only allow for a single open connection, so if you call this
and we currently have an open connection, it will be closed.
"""
try:
self.HTTP_CONN.close()
except (TypeError, AttributeError):
pass
if on:
self.HTTP_CONN = self.get_conn(auto_open=auto_open)
else:
if self.scheme == "https":
self.HTTP_CONN = HTTPSConnection
else:
self.HTTP_CONN = HTTPConnection
def _get_persistent(self):
return hasattr(self.HTTP_CONN, "__class__")
def _set_persistent(self, on):
self.set_persistent(on)
persistent = property(_get_persistent, _set_persistent)
def interface(self):
"""Return an IP address for a client connection.
If the server is listening on '0.0.0.0' (INADDR_ANY)
or '::' (IN6ADDR_ANY), this will return the proper localhost."""
return interface(self.HOST)
def getPage(self, url, headers=None, method="GET", body=None, protocol=None):
"""Open the url with debugging support. Return status, headers, body."""
ServerError.on = False
if isinstance(url, unicodestr):
url = url.encode('utf-8')
if isinstance(body, unicodestr):
body = body.encode('utf-8')
self.url = url
self.time = None
start = time.time()
result = openURL(url, headers, method, body, self.HOST, self.PORT,
self.HTTP_CONN, protocol or self.PROTOCOL)
self.time = time.time() - start
self.status, self.headers, self.body = result
# Build a list of request cookies from the previous response cookies.
self.cookies = [('Cookie', v) for k, v in self.headers
if k.lower() == 'set-cookie']
if ServerError.on:
raise ServerError()
return result
interactive = True
console_height = 30
def _handlewebError(self, msg):
print("")
print(" ERROR: %s" % msg)
if not self.interactive:
raise self.failureException(msg)
p = " Show: [B]ody [H]eaders [S]tatus [U]RL; [I]gnore, [R]aise, or sys.e[X]it >> "
sys.stdout.write(p)
sys.stdout.flush()
while True:
i = getchar().upper()
if not isinstance(i, type("")):
i = i.decode('ascii')
if i not in "BHSUIRX":
continue
print(i.upper()) # Also prints new line
if i == "B":
for x, line in enumerate(self.body.splitlines()):
if (x + 1) % self.console_height == 0:
# The \r and comma should make the next line overwrite
sys.stdout.write("<-- More -->\r")
m = getchar().lower()
# Erase our "More" prompt
sys.stdout.write(" \r")
if m == "q":
break
print(line)
elif i == "H":
pprint.pprint(self.headers)
elif i == "S":
print(self.status)
elif i == "U":
print(self.url)
elif i == "I":
# return without raising the normal exception
return
elif i == "R":
raise self.failureException(msg)
elif i == "X":
self.exit()
sys.stdout.write(p)
sys.stdout.flush()
def exit(self):
sys.exit()
def assertStatus(self, status, msg=None):
"""Fail if self.status != status."""
if isinstance(status, basestring):
if not self.status == status:
if msg is None:
msg = 'Status (%r) != %r' % (self.status, status)
self._handlewebError(msg)
elif isinstance(status, int):
code = int(self.status[:3])
if code != status:
if msg is None:
msg = 'Status (%r) != %r' % (self.status, status)
self._handlewebError(msg)
else:
# status is a tuple or list.
match = False
for s in status:
if isinstance(s, basestring):
if self.status == s:
match = True
break
elif int(self.status[:3]) == s:
match = True
break
if not match:
if msg is None:
msg = 'Status (%r) not in %r' % (self.status, status)
self._handlewebError(msg)
def assertHeader(self, key, value=None, msg=None):
"""Fail if (key, [value]) not in self.headers."""
lowkey = key.lower()
for k, v in self.headers:
if k.lower() == lowkey:
if value is None or str(value) == v:
return v
if msg is None:
if value is None:
msg = '%r not in headers' % key
else:
msg = '%r:%r not in headers' % (key, value)
self._handlewebError(msg)
def assertHeaderItemValue(self, key, value, msg=None):
"""Fail if the header does not contain the specified value"""
actual_value = self.assertHeader(key, msg=msg)
header_values = map(str.strip, actual_value.split(','))
if value in header_values:
return value
if msg is None:
msg = "%r not in %r" % (value, header_values)
self._handlewebError(msg)
def assertNoHeader(self, key, msg=None):
"""Fail if key in self.headers."""
lowkey = key.lower()
matches = [k for k, v in self.headers if k.lower() == lowkey]
if matches:
if msg is None:
msg = '%r in headers' % key
self._handlewebError(msg)
def assertBody(self, value, msg=None):
"""Fail if value != self.body."""
if isinstance(value, unicodestr):
value = value.encode(self.encoding)
if value != self.body:
if msg is None:
msg = 'expected body:\n%r\n\nactual body:\n%r' % (value, self.body)
self._handlewebError(msg)
def assertInBody(self, value, msg=None):
"""Fail if value not in self.body."""
if isinstance(value, unicodestr):
value = value.encode(self.encoding)
if value not in self.body:
if msg is None:
msg = '%r not in body: %s' % (value, self.body)
self._handlewebError(msg)
def assertNotInBody(self, value, msg=None):
"""Fail if value in self.body."""
if isinstance(value, unicodestr):
value = value.encode(self.encoding)
if value in self.body:
if msg is None:
msg = '%r found in body' % value
self._handlewebError(msg)
def assertMatchesBody(self, pattern, msg=None, flags=0):
"""Fail if value (a regex pattern) is not in self.body."""
if isinstance(pattern, unicodestr):
pattern = pattern.encode(self.encoding)
if re.search(pattern, self.body, flags) is None:
if msg is None:
msg = 'No match for %r in body' % pattern
self._handlewebError(msg)
methods_with_bodies = ("POST", "PUT")
def cleanHeaders(headers, method, body, host, port):
"""Return request headers, with required headers added (if missing)."""
if headers is None:
headers = []
# Add the required Host request header if not present.
# [This specifies the host:port of the server, not the client.]
found = False
for k, v in headers:
if k.lower() == 'host':
found = True
break
if not found:
if port == 80:
headers.append(("Host", host))
else:
headers.append(("Host", "%s:%s" % (host, port)))
if method in methods_with_bodies:
# Stick in default type and length headers if not present
found = False
for k, v in headers:
if k.lower() == 'content-type':
found = True
break
if not found:
headers.append(("Content-Type", "application/x-www-form-urlencoded"))
headers.append(("Content-Length", str(len(body or ""))))
return headers
def shb(response):
"""Return status, headers, body the way we like from a response."""
if py3k:
h = response.getheaders()
else:
h = []
key, value = None, None
for line in response.msg.headers:
if line:
if line[0] in " \t":
value += line.strip()
else:
if key and value:
h.append((key, value))
key, value = line.split(":", 1)
key = key.strip()
value = value.strip()
if key and value:
h.append((key, value))
return "%s %s" % (response.status, response.reason), h, response.read()
def openURL(url, headers=None, method="GET", body=None,
host="127.0.0.1", port=8000, http_conn=HTTPConnection,
protocol="HTTP/1.1"):
"""Open the given HTTP resource and return status, headers, and body."""
headers = cleanHeaders(headers, method, body, host, port)
# Trying 10 times is simply in case of socket errors.
# Normal case--it should run once.
for trial in range(10):
try:
# Allow http_conn to be a class or an instance
if hasattr(http_conn, "host"):
conn = http_conn
else:
conn = http_conn(interface(host), port)
conn._http_vsn_str = protocol
conn._http_vsn = int("".join([x for x in protocol if x.isdigit()]))
# skip_accept_encoding argument added in python version 2.4
if sys.version_info < (2, 4):
def putheader(self, header, value):
if header == 'Accept-Encoding' and value == 'identity':
return
self.__class__.putheader(self, header, value)
import new
conn.putheader = new.instancemethod(putheader, conn, conn.__class__)
conn.putrequest(method.upper(), url, skip_host=True)
elif not py3k:
conn.putrequest(method.upper(), url, skip_host=True,
skip_accept_encoding=True)
else:
import http.client
# Replace the stdlib method, which only accepts ASCII url's
def putrequest(self, method, url):
if self._HTTPConnection__response and self._HTTPConnection__response.isclosed():
self._HTTPConnection__response = None
if self._HTTPConnection__state == http.client._CS_IDLE:
self._HTTPConnection__state = http.client._CS_REQ_STARTED
else:
raise http.client.CannotSendRequest()
self._method = method
if not url:
url = ntob('/')
request = ntob(' ').join((method.encode("ASCII"), url,
self._http_vsn_str.encode("ASCII")))
self._output(request)
import types
conn.putrequest = types.MethodType(putrequest, conn)
conn.putrequest(method.upper(), url)
for key, value in headers:
conn.putheader(key, ntob(value, "Latin-1"))
conn.endheaders()
if body is not None:
conn.send(body)
# Handle response
response = conn.getresponse()
s, h, b = shb(response)
if not hasattr(http_conn, "host"):
# We made our own conn instance. Close it.
conn.close()
return s, h, b
except socket.error:
time.sleep(0.5)
if trial == 9:
raise
# Add any exceptions which your web framework handles
# normally (that you don't want server_error to trap).
ignored_exceptions = []
# You'll want set this to True when you can't guarantee
# that each response will immediately follow each request;
# for example, when handling requests via multiple threads.
ignore_all = False
class ServerError(Exception):
on = False
def server_error(exc=None):
"""Server debug hook. Return True if exception handled, False if ignored.
You probably want to wrap this, so you can still handle an error using
your framework when it's ignored.
"""
if exc is None:
exc = sys.exc_info()
if ignore_all or exc[0] in ignored_exceptions:
return False
else:
ServerError.on = True
print("")
print("".join(traceback.format_exception(*exc)))
return True

View File

@ -1,16 +0,0 @@
CherryPy Tutorials
------------------------------------------------------------------------
This is a series of tutorials explaining how to develop dynamic web
applications using CherryPy. A couple of notes:
- Each of these tutorials builds on the ones before it. If you're
new to CherryPy, we recommend you start with 01_helloworld.py and
work your way upwards. :)
- In most of these tutorials, you will notice that all output is done
by returning normal Python strings, often using simple Python
variable substitution. In most real-world applications, you will
probably want to use a separate template package (like Cheetah,
CherryTemplate or XML/XSL).

View File

@ -1,3 +0,0 @@
# This is used in test_config to test unrepr of "from A import B"
thing2 = object()

View File

@ -1,168 +0,0 @@
'''
Bonus Tutorial: Using SQLObject
This is a silly little contacts manager application intended to
demonstrate how to use SQLObject from within a CherryPy2 project. It
also shows how to use inline Cheetah templates.
SQLObject is an Object/Relational Mapper that allows you to access
data stored in an RDBMS in a pythonic fashion. You create data objects
as Python classes and let SQLObject take care of all the nasty details.
This code depends on the latest development version (0.6+) of SQLObject.
You can get it from the SQLObject Subversion server. You can find all
necessary information at <http://www.sqlobject.org>. This code will NOT
work with the 0.5.x version advertised on their website!
This code also depends on a recent version of Cheetah. You can find
Cheetah at <http://www.cheetahtemplate.org>.
After starting this application for the first time, you will need to
access the /reset URI in order to create the database table and some
sample data. Accessing /reset again will drop and re-create the table,
so you may want to be careful. :-)
This application isn't supposed to be fool-proof, it's not even supposed
to be very GOOD. Play around with it some, browse the source code, smile.
:)
-- Hendrik Mans <hendrik@mans.de>
'''
import cherrypy
from Cheetah.Template import Template
from sqlobject import *
# configure your database connection here
__connection__ = 'mysql://root:@localhost/test'
# this is our (only) data class.
class Contact(SQLObject):
lastName = StringCol(length = 50, notNone = True)
firstName = StringCol(length = 50, notNone = True)
phone = StringCol(length = 30, notNone = True, default = '')
email = StringCol(length = 30, notNone = True, default = '')
url = StringCol(length = 100, notNone = True, default = '')
class ContactManager:
def index(self):
# Let's display a list of all stored contacts.
contacts = Contact.select()
template = Template('''
<h2>All Contacts</h2>
#for $contact in $contacts
<a href="mailto:$contact.email">$contact.lastName, $contact.firstName</a>
[<a href="./edit?id=$contact.id">Edit</a>]
[<a href="./delete?id=$contact.id">Delete</a>]
<br/>
#end for
<p>[<a href="./edit">Add new contact</a>]</p>
''', [locals(), globals()])
return template.respond()
index.exposed = True
def edit(self, id = 0):
# we really want id as an integer. Since GET/POST parameters
# are always passed as strings, let's convert it.
id = int(id)
if id > 0:
# if an id is specified, we're editing an existing contact.
contact = Contact.get(id)
title = "Edit Contact"
else:
# if no id is specified, we're entering a new contact.
contact = None
title = "New Contact"
# In the following template code, please note that we use
# Cheetah's $getVar() construct for the form values. We have
# to do this because contact may be set to None (see above).
template = Template('''
<h2>$title</h2>
<form action="./store" method="POST">
<input type="hidden" name="id" value="$id" />
Last Name: <input name="lastName" value="$getVar('contact.lastName', '')" /><br/>
First Name: <input name="firstName" value="$getVar('contact.firstName', '')" /><br/>
Phone: <input name="phone" value="$getVar('contact.phone', '')" /><br/>
Email: <input name="email" value="$getVar('contact.email', '')" /><br/>
URL: <input name="url" value="$getVar('contact.url', '')" /><br/>
<input type="submit" value="Store" />
</form>
''', [locals(), globals()])
return template.respond()
edit.exposed = True
def delete(self, id):
# Delete the specified contact
contact = Contact.get(int(id))
contact.destroySelf()
return 'Deleted. <a href="./">Return to Index</a>'
delete.exposed = True
def store(self, lastName, firstName, phone, email, url, id = None):
if id and int(id) > 0:
# If an id was specified, update an existing contact.
contact = Contact.get(int(id))
# We could set one field after another, but that would
# cause multiple UPDATE clauses. So we'll just do it all
# in a single pass through the set() method.
contact.set(
lastName = lastName,
firstName = firstName,
phone = phone,
email = email,
url = url)
else:
# Otherwise, add a new contact.
contact = Contact(
lastName = lastName,
firstName = firstName,
phone = phone,
email = email,
url = url)
return 'Stored. <a href="./">Return to Index</a>'
store.exposed = True
def reset(self):
# Drop existing table
Contact.dropTable(True)
# Create new table
Contact.createTable()
# Create some sample data
Contact(
firstName = 'Hendrik',
lastName = 'Mans',
email = 'hendrik@mans.de',
phone = '++49 89 12345678',
url = 'http://www.mornography.de')
return "reset completed!"
reset.exposed = True
print("If you're running this application for the first time, please go to http://localhost:8080/reset once in order to create the database!")
cherrypy.quickstart(ContactManager())

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>403 Unauthorized</title>
</head>
<body>
<h2>You can't do that!</h2>
<p>%(message)s</p>
<p>This is a custom error page that is read from a file.<p>
<pre>%(traceback)s</pre>
</body>
</html>

Binary file not shown.

View File

@ -1,35 +0,0 @@
"""
Tutorial - Hello World
The most basic (working) CherryPy application possible.
"""
# Import CherryPy global namespace
import cherrypy
class HelloWorld:
""" Sample request handler class. """
def index(self):
# CherryPy will call this method for the root URI ("/") and send
# its return value to the client. Because this is tutorial
# lesson number 01, we'll just send something really simple.
# How about...
return "Hello world!"
# Expose the index method through the web. CherryPy will never
# publish methods that don't have the exposed attribute set to True.
index.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(HelloWorld(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HelloWorld(), config=tutconf)

View File

@ -1,32 +0,0 @@
"""
Tutorial - Multiple methods
This tutorial shows you how to link to other methods of your request
handler.
"""
import cherrypy
class HelloWorld:
def index(self):
# Let's link to another method here.
return 'We have an <a href="showMessage">important message</a> for you!'
index.exposed = True
def showMessage(self):
# Here's the important message!
return "Hello world!"
showMessage.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(HelloWorld(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HelloWorld(), config=tutconf)

View File

@ -1,53 +0,0 @@
"""
Tutorial - Passing variables
This tutorial shows you how to pass GET/POST variables to methods.
"""
import cherrypy
class WelcomePage:
def index(self):
# Ask for the user's name.
return '''
<form action="greetUser" method="GET">
What is your name?
<input type="text" name="name" />
<input type="submit" />
</form>'''
index.exposed = True
def greetUser(self, name = None):
# CherryPy passes all GET and POST variables as method parameters.
# It doesn't make a difference where the variables come from, how
# large their contents are, and so on.
#
# You can define default parameter values as usual. In this
# example, the "name" parameter defaults to None so we can check
# if a name was actually specified.
if name:
# Greet the user!
return "Hey %s, what's up?" % name
else:
if name is None:
# No name was specified
return 'Please enter your name <a href="./">here</a>.'
else:
return 'No, really, enter your name <a href="./">here</a>.'
greetUser.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(WelcomePage(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(WelcomePage(), config=tutconf)

View File

@ -1,98 +0,0 @@
"""
Tutorial - Multiple objects
This tutorial shows you how to create a site structure through multiple
possibly nested request handler objects.
"""
import cherrypy
class HomePage:
def index(self):
return '''
<p>Hi, this is the home page! Check out the other
fun stuff on this site:</p>
<ul>
<li><a href="/joke/">A silly joke</a></li>
<li><a href="/links/">Useful links</a></li>
</ul>'''
index.exposed = True
class JokePage:
def index(self):
return '''
<p>"In Python, how do you create a string of random
characters?" -- "Read a Perl file!"</p>
<p>[<a href="../">Return</a>]</p>'''
index.exposed = True
class LinksPage:
def __init__(self):
# Request handler objects can create their own nested request
# handler objects. Simply create them inside their __init__
# methods!
self.extra = ExtraLinksPage()
def index(self):
# Note the way we link to the extra links page (and back).
# As you can see, this object doesn't really care about its
# absolute position in the site tree, since we use relative
# links exclusively.
return '''
<p>Here are some useful links:</p>
<ul>
<li><a href="http://www.cherrypy.org">The CherryPy Homepage</a></li>
<li><a href="http://www.python.org">The Python Homepage</a></li>
</ul>
<p>You can check out some extra useful
links <a href="./extra/">here</a>.</p>
<p>[<a href="../">Return</a>]</p>
'''
index.exposed = True
class ExtraLinksPage:
def index(self):
# Note the relative link back to the Links page!
return '''
<p>Here are some extra useful links:</p>
<ul>
<li><a href="http://del.icio.us">del.icio.us</a></li>
<li><a href="http://www.mornography.de">Hendrik's weblog</a></li>
</ul>
<p>[<a href="../">Return to links page</a>]</p>'''
index.exposed = True
# Of course we can also mount request handler objects right here!
root = HomePage()
root.joke = JokePage()
root.links = LinksPage()
# Remember, we don't need to mount ExtraLinksPage here, because
# LinksPage does that itself on initialization. In fact, there is
# no reason why you shouldn't let your root object take care of
# creating all contained request handler objects.
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(root, config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(root, config=tutconf)

View File

@ -1,83 +0,0 @@
"""
Tutorial - Object inheritance
You are free to derive your request handler classes from any base
class you wish. In most real-world applications, you will probably
want to create a central base class used for all your pages, which takes
care of things like printing a common page header and footer.
"""
import cherrypy
class Page:
# Store the page title in a class attribute
title = 'Untitled Page'
def header(self):
return '''
<html>
<head>
<title>%s</title>
<head>
<body>
<h2>%s</h2>
''' % (self.title, self.title)
def footer(self):
return '''
</body>
</html>
'''
# Note that header and footer don't get their exposed attributes
# set to True. This isn't necessary since the user isn't supposed
# to call header or footer directly; instead, we'll call them from
# within the actually exposed handler methods defined in this
# class' subclasses.
class HomePage(Page):
# Different title for this page
title = 'Tutorial 5'
def __init__(self):
# create a subpage
self.another = AnotherPage()
def index(self):
# Note that we call the header and footer methods inherited
# from the Page class!
return self.header() + '''
<p>
Isn't this exciting? There's
<a href="./another/">another page</a>, too!
</p>
''' + self.footer()
index.exposed = True
class AnotherPage(Page):
title = 'Another Page'
def index(self):
return self.header() + '''
<p>
And this is the amazing second page!
</p>
''' + self.footer()
index.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(HomePage(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HomePage(), config=tutconf)

View File

@ -1,64 +0,0 @@
"""
Tutorial - The default method
Request handler objects can implement a method called "default" that
is called when no other suitable method/object could be found.
Essentially, if CherryPy2 can't find a matching request handler object
for the given request URI, it will use the default method of the object
located deepest on the URI path.
Using this mechanism you can easily simulate virtual URI structures
by parsing the extra URI string, which you can access through
cherrypy.request.virtualPath.
The application in this tutorial simulates an URI structure looking
like /users/<username>. Since the <username> bit will not be found (as
there are no matching methods), it is handled by the default method.
"""
import cherrypy
class UsersPage:
def index(self):
# Since this is just a stupid little example, we'll simply
# display a list of links to random, made-up users. In a real
# application, this could be generated from a database result set.
return '''
<a href="./remi">Remi Delon</a><br/>
<a href="./hendrik">Hendrik Mans</a><br/>
<a href="./lorenzo">Lorenzo Lamas</a><br/>
'''
index.exposed = True
def default(self, user):
# Here we react depending on the virtualPath -- the part of the
# path that could not be mapped to an object method. In a real
# application, we would probably do some database lookups here
# instead of the silly if/elif/else construct.
if user == 'remi':
out = "Remi Delon, CherryPy lead developer"
elif user == 'hendrik':
out = "Hendrik Mans, CherryPy co-developer & crazy German"
elif user == 'lorenzo':
out = "Lorenzo Lamas, famous actor and singer!"
else:
out = "Unknown user. :-("
return '%s (<a href="./">back</a>)' % out
default.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(UsersPage(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(UsersPage(), config=tutconf)

View File

@ -1,44 +0,0 @@
"""
Tutorial - Sessions
Storing session data in CherryPy applications is very easy: cherrypy
provides a dictionary called "session" that represents the session
data for the current user. If you use RAM based sessions, you can store
any kind of object into that dictionary; otherwise, you are limited to
objects that can be pickled.
"""
import cherrypy
class HitCounter:
_cp_config = {'tools.sessions.on': True}
def index(self):
# Increase the silly hit counter
count = cherrypy.session.get('count', 0) + 1
# Store the new value in the session dictionary
cherrypy.session['count'] = count
# And display a silly hit count message!
return '''
During your current session, you've viewed this
page %s times! Your life is a patio of fun!
''' % count
index.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(HitCounter(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HitCounter(), config=tutconf)

View File

@ -1,47 +0,0 @@
"""
Bonus Tutorial: Using generators to return result bodies
Instead of returning a complete result string, you can use the yield
statement to return one result part after another. This may be convenient
in situations where using a template package like CherryPy or Cheetah
would be overkill, and messy string concatenation too uncool. ;-)
"""
import cherrypy
class GeneratorDemo:
def header(self):
return "<html><body><h2>Generators rule!</h2>"
def footer(self):
return "</body></html>"
def index(self):
# Let's make up a list of users for presentation purposes
users = ['Remi', 'Carlos', 'Hendrik', 'Lorenzo Lamas']
# Every yield line adds one part to the total result body.
yield self.header()
yield "<h3>List of users:</h3>"
for user in users:
yield "%s<br/>" % user
yield self.footer()
index.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(GeneratorDemo(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(GeneratorDemo(), config=tutconf)

View File

@ -1,107 +0,0 @@
"""
Tutorial: File upload and download
Uploads
-------
When a client uploads a file to a CherryPy application, it's placed
on disk immediately. CherryPy will pass it to your exposed method
as an argument (see "myFile" below); that arg will have a "file"
attribute, which is a handle to the temporary uploaded file.
If you wish to permanently save the file, you need to read()
from myFile.file and write() somewhere else.
Note the use of 'enctype="multipart/form-data"' and 'input type="file"'
in the HTML which the client uses to upload the file.
Downloads
---------
If you wish to send a file to the client, you have two options:
First, you can simply return a file-like object from your page handler.
CherryPy will read the file and serve it as the content (HTTP body)
of the response. However, that doesn't tell the client that
the response is a file to be saved, rather than displayed.
Use cherrypy.lib.static.serve_file for that; it takes four
arguments:
serve_file(path, content_type=None, disposition=None, name=None)
Set "name" to the filename that you expect clients to use when they save
your file. Note that the "name" argument is ignored if you don't also
provide a "disposition" (usually "attachement"). You can manually set
"content_type", but be aware that if you also use the encoding tool, it
may choke if the file extension is not recognized as belonging to a known
Content-Type. Setting the content_type to "application/x-download" works
in most cases, and should prompt the user with an Open/Save dialog in
popular browsers.
"""
import os
localDir = os.path.dirname(__file__)
absDir = os.path.join(os.getcwd(), localDir)
import cherrypy
from cherrypy.lib import static
class FileDemo(object):
def index(self):
return """
<html><body>
<h2>Upload a file</h2>
<form action="upload" method="post" enctype="multipart/form-data">
filename: <input type="file" name="myFile" /><br />
<input type="submit" />
</form>
<h2>Download a file</h2>
<a href='download'>This one</a>
</body></html>
"""
index.exposed = True
def upload(self, myFile):
out = """<html>
<body>
myFile length: %s<br />
myFile filename: %s<br />
myFile mime-type: %s
</body>
</html>"""
# Although this just counts the file length, it demonstrates
# how to read large files in chunks instead of all at once.
# CherryPy reads the uploaded file into a temporary file;
# myFile.file.read reads from that.
size = 0
while True:
data = myFile.file.read(8192)
if not data:
break
size += len(data)
return out % (size, myFile.filename, myFile.content_type)
upload.exposed = True
def download(self):
path = os.path.join(absDir, "pdf_file.pdf")
return static.serve_file(path, "application/x-download",
"attachment", os.path.basename(path))
download.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(FileDemo(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(FileDemo(), config=tutconf)

View File

@ -1,81 +0,0 @@
"""
Tutorial: HTTP errors
HTTPError is used to return an error response to the client.
CherryPy has lots of options regarding how such errors are
logged, displayed, and formatted.
"""
import os
localDir = os.path.dirname(__file__)
curpath = os.path.normpath(os.path.join(os.getcwd(), localDir))
import cherrypy
class HTTPErrorDemo(object):
# Set a custom response for 403 errors.
_cp_config = {'error_page.403' : os.path.join(curpath, "custom_error.html")}
def index(self):
# display some links that will result in errors
tracebacks = cherrypy.request.show_tracebacks
if tracebacks:
trace = 'off'
else:
trace = 'on'
return """
<html><body>
<p>Toggle tracebacks <a href="toggleTracebacks">%s</a></p>
<p><a href="/doesNotExist">Click me; I'm a broken link!</a></p>
<p><a href="/error?code=403">Use a custom error page from a file.</a></p>
<p>These errors are explicitly raised by the application:</p>
<ul>
<li><a href="/error?code=400">400</a></li>
<li><a href="/error?code=401">401</a></li>
<li><a href="/error?code=402">402</a></li>
<li><a href="/error?code=500">500</a></li>
</ul>
<p><a href="/messageArg">You can also set the response body
when you raise an error.</a></p>
</body></html>
""" % trace
index.exposed = True
def toggleTracebacks(self):
# simple function to toggle tracebacks on and off
tracebacks = cherrypy.request.show_tracebacks
cherrypy.config.update({'request.show_tracebacks': not tracebacks})
# redirect back to the index
raise cherrypy.HTTPRedirect('/')
toggleTracebacks.exposed = True
def error(self, code):
# raise an error based on the get query
raise cherrypy.HTTPError(status = code)
error.exposed = True
def messageArg(self):
message = ("If you construct an HTTPError with a 'message' "
"argument, it wil be placed on the error page "
"(underneath the status line by default).")
raise cherrypy.HTTPError(500, message=message)
messageArg.exposed = True
import os.path
tutconf = os.path.join(os.path.dirname(__file__), 'tutorial.conf')
if __name__ == '__main__':
# CherryPy always starts with app.root when trying to map request URIs
# to objects, so we need to mount a request handler root. A request
# to '/' will be mapped to HelloWorld().index().
cherrypy.quickstart(HTTPErrorDemo(), config=tutconf)
else:
# This branch is for the test suite; you can ignore it.
cherrypy.tree.mount(HTTPErrorDemo(), config=tutconf)

View File

@ -1,4 +0,0 @@
[global]
server.socket_host = "127.0.0.1"
server.socket_port = 8080
server.thread_pool = 10

View File

@ -1,7 +1,18 @@
# -*- coding: utf-8 -*-
import os
DEBUG = True
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_FILES_ROOT = os.path.join(ROOT_DIR, 'static')
STATIC_FILES_ROOT = os.path.join(ROOT_DIR, 'static')
#####################################################
# You can start editing settings after this comment #
#####################################################
# debug will get you error message and auto reload
# don't set this to True in production
DEBUG = True
# absolute path where the paste files should be store
# default in projectdirectory/static/content/
# use "/" even under Windows
PASTE_FILES_ROOT = os.path.join(STATIC_FILES_ROOT, 'content')

View File

@ -26,18 +26,21 @@ class Paste(object):
def __init__(self, uuid=None, content=None,
expiration=u'burn_after_reading',
expiration=None,
comments=None):
self.content = content
self.expiration = expiration
self.comments = comments
if isinstance(self.content, unicode):
self.content = self.content.encode('utf8')
if (not isinstance(expiration, datetime) and
'burn_after_reading' not in str(expiration)):
self.expiration = self.get_expiration(self.expiration)
self.uuid = uuid or hashlib.sha1(self.content).hexdigest()
self.expiration = self.get_expiration(expiration)
self.comments = comments
def get_expiration(self, expiration):
@ -57,7 +60,7 @@ class Paste(object):
Generic static content path builder. Return a path to
a location in the static content file dir.
"""
return os.path.join(settings.STATIC_FILES_ROOT, u'content', *dirs)
return os.path.join(settings.PASTE_FILES_ROOT, *dirs)
@classmethod
@ -88,13 +91,14 @@ class Paste(object):
expiration = paste.next().strip()
content = paste.next().strip()
comments = paste.read()[:-1] # remove the last coma
if expiration != u'burn_after_reading':
if "burn_after_reading" not in str(expiration):
expiration = datetime.strptime(expiration,'%Y-%m-%d %H:%M:%S.%f')
except StopIteration:
raise TypeError(u'File %s is malformed' % path)
except (IOError, OSError):
raise ValueError(u'Can not open paste from file %s' % path)
return Paste(uuid=uuid, comments=comments,
expiration=expiration, content=content)
@ -115,7 +119,6 @@ class Paste(object):
If comments are passed, they are expected to be serialized
already.
"""
data = {'content': self.content}
head, tail = self.uuid[:2], self.uuid[2:4]
# the static files are saved in project_dir/static/xx/yy/uuid
@ -139,6 +142,13 @@ class Paste(object):
if not os.path.isdir(path):
os.mkdir(path)
# add a timestamp to burn after reading to allow
# a quick period of time where you can redirect to the page without
# deleting the paste
if self.expiration == "burn_after_reading":
self.expiration = self.expiration + '#%s' % datetime.now()
# writethe paste
with open(self.path, 'w') as f:
f.write(unicode(self.expiration) + '\n')
f.write(self.content + '\n')
@ -152,7 +162,7 @@ class Paste(object):
"""
Delete the paste file.
"""
os.path.remove(self.path)
os.remove(self.path)
@classmethod

View File

@ -8,6 +8,8 @@
import os
import hashlib
from datetime import datetime, timedelta
from src import settings, setup_path, Paste
setup_path()
@ -31,28 +33,52 @@ def create_paste():
try:
content = unicode(request.forms.get('content', ''), 'utf8')
except UnicodeDecodeError:
content = u''
return {'status': 'error',
'message': u"Encoding error: the paste couldn't be saved."}
if content:
expiration = request.forms.get('expiration', u'burn_after_reading')
paste = Paste(expiration=expiration, content=content)
paste.save()
return {'status': 'ok',
'paste': paste.uuid}
return paste.uuid
return ''
return {'status': 'error',
'message': u"Serveur error: the paste couldn't be saved. Please try later."}
@app.route('/paste/:paste_id')
@view('paste')
def display_paste(paste_id):
now = datetime.now()
keep_alive = False
try:
paste = Paste.load(paste_id)
except (TypeError, ValueError):
abort(404, u"This paste does't exist or has expired")
# Delete the paste if it expired:
if 'burn_after_reading' in str(paste.expiration):
# burn_after_reading contains the paste creation date
# if this read appends 10 seconds after the creation date
# we don't delete the paste because it means it's the redirection
# to the paste that happens during the paste creation
try:
keep_alive = paste.expiration.split('#')[1]
keep_alive = datetime.strptime(keep_alive,'%Y-%m-%d %H:%M:%S.%f')
keep_alive = now < keep_alive + timedelta(seconds=10)
except IndexError:
keep_alive = False
if not keep_alive:
paste.delete()
return {'paste': paste}
elif paste.expiration < now:
paste.delete()
raise ValueError()
except (TypeError, ValueError):
abort(404, u"This paste doesn't exist or has expired")
return {'paste': paste, 'keep_alive': keep_alive}
@app.route('/static/<filename:path>')

View File

@ -28,11 +28,11 @@ $('button[type=submit]').click(function(e){
var data = {content: zerobin.encrypt(key, paste), expiration: expiration}
$.post('/paste/create', data)
.error(function() {
.error(function(error) {
alert('Paste could not be saved. Please try again later.');
})
.success(function(data) {
window.location = '/paste/' + data + '#' + key;
window.location = ('/paste/' + data['paste'] + '#' + key);
});
}

View File

@ -1,3 +1,15 @@
%if "burn_after_reading" in str(paste.expiration):
<div class="alert">
<strong>Warning!</strong>
%if keep_alive:
This paste will be deleted the next time it is read.
%else:
This paste has self-destructed. If you close this windows, there is not way
to recover it.
%end
</div>
%end
<div class="well">
<form action="/paste/clone" method="get" accept-charset="utf-8">