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:
parent
19480bc1f0
commit
1a46d998ac
@ -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)
|
@ -1,3 +0,0 @@
|
||||
[/]
|
||||
log.error_file: "error.log"
|
||||
log.access_file: "access.log"
|
@ -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 |
@ -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
|
@ -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
|
||||
|
||||
|
@ -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(), '/', {'/': {}})
|
@ -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()
|
@ -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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'> </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 |
@ -1 +0,0 @@
|
||||
Hello, world
|
@ -1 +0,0 @@
|
||||
Dummy stylesheet
|
@ -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-----
|
@ -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.")
|
||||
|
@ -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.")
|
||||
|
@ -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()
|
@ -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')
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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.")
|
||||
|
@ -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")
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.")
|
||||
|
@ -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()
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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¬athing=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¬athing=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¶m2=bar',
|
||||
'/paramerrors/one_positional_args_kwargs/foo?param2=bar¶m3=baz',
|
||||
'/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
|
||||
'/paramerrors/one_positional_kwargs?param1=foo¶m2=bar¶m3=baz',
|
||||
'/paramerrors/one_positional_kwargs/foo?param4=foo¶m2=bar¶m3=baz',
|
||||
'/paramerrors/no_positional',
|
||||
'/paramerrors/no_positional_args/foo',
|
||||
'/paramerrors/no_positional_args/foo/bar/baz',
|
||||
'/paramerrors/no_positional_args_kwargs?param1=foo¶m2=bar',
|
||||
'/paramerrors/no_positional_args_kwargs/foo?param2=bar',
|
||||
'/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar¶m3=baz',
|
||||
'/paramerrors/no_positional_kwargs?param1=foo¶m2=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¶m2=foo', error_msgs[2]),
|
||||
('/paramerrors/one_positional_args/foo?param1=foo¶m2=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¶m3=baz', error_msgs[2]),
|
||||
('/paramerrors/one_positional_kwargs/foo?param1=foo¶m2=bar¶m3=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¶m2=foo', error_msgs[2]),
|
||||
('/paramerrors/one_positional_args/foo', 'param1=foo¶m2=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¶m3=baz', error_msgs[2]),
|
||||
('/paramerrors/one_positional_kwargs/foo', 'param1=foo¶m2=bar¶m3=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¶m3=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, <b>really</b>, 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)
|
||||
|
@ -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")
|
||||
|
@ -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])
|
||||
|
@ -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">')
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
@ -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.")
|
||||
|
@ -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'")
|
||||
|
@ -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)
|
||||
|
@ -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!")
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -1,3 +0,0 @@
|
||||
|
||||
# This is used in test_config to test unrepr of "from A import B"
|
||||
thing2 = object()
|
@ -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())
|
@ -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.
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
@ -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)
|
@ -1,4 +0,0 @@
|
||||
[global]
|
||||
server.socket_host = "127.0.0.1"
|
||||
server.socket_port = 8080
|
||||
server.thread_pool = 10
|
17
settings.py
17
settings.py
@ -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')
|
28
src/paste.py
28
src/paste.py
@ -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
|
||||
|
40
start.py
40
start.py
@ -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>')
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
Loading…
Reference in New Issue
Block a user