Author: justi9
Date: 2009-03-19 11:45:47 -0400 (Thu, 19 Mar 2009)
New Revision: 3175
Modified:
mgmt/trunk/wooly/python/wooly/devel.py
mgmt/trunk/wooly/python/wooly/forms.py
mgmt/trunk/wooly/python/wooly/forms.strings
mgmt/trunk/wooly/python/wooly/model.py
mgmt/trunk/wooly/python/wooly/parameters.py
mgmt/trunk/wooly/python/wooly/resources.py
mgmt/trunk/wooly/python/wooly/tables.py
mgmt/trunk/wooly/python/wooly/widgets.py
mgmt/trunk/wooly/python/wooly/wsgiserver.py
Log:
Whitespace-only cleanup of wooly files
Modified: mgmt/trunk/wooly/python/wooly/devel.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/devel.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/devel.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -16,7 +16,7 @@
visited = set()
self.app.debug.urls.add("")
count = 0
-
+
while True:
for url in self.app.debug.urls:
if url not in visited:
@@ -74,7 +74,7 @@
if count == max:
break
-
+
count += 1
class DevelPage(Page):
Modified: mgmt/trunk/wooly/python/wooly/forms.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/forms.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/forms.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -27,9 +27,9 @@
origin = branch.marshal()
except: # we don't have a show_view
pass
-
+
return origin
-
+
def render_hidden_inputs(self, session, *args):
writer = Writer()
# remember our roots
@@ -41,7 +41,7 @@
for param in params:
key = param.path
-
+
if param.is_collection:
collection = session.get(key)
@@ -64,7 +64,7 @@
def write_hidden_input(self, name, value, writer):
writer.write("<input type='hidden' name='%s'
value='%s'/>" \
% (name, value))
-
+
class FormError(object):
def __init__(self, message=None):
self.message = message
@@ -190,10 +190,10 @@
class HiddenInput(ScalarInput):
def __init__(self, app, name):
super(HiddenInput, self).__init__(app, name, None)
-
+
self.param = BooleanParameter(app, "param")
self.add_parameter(self.param)
-
+
class IntegerInput(ScalarInput):
def __init__(self, app, name):
super(IntegerInput, self).__init__(app, name, None)
@@ -251,13 +251,13 @@
self.set(branch, True)
return super(FormButton, self).render_value(branch, *args)
-
+
def render_type(self, session, *args):
return "submit"
def render_onclick(self, session, *args):
return "click_button"
-
+
class CheckboxInputSet(FormInput, ItemSet):
def render_item_value(self, session, *args):
return None
@@ -287,7 +287,7 @@
self.add_child(self.__errors)
self.css_class = "field"
-
+
def validate(self, session):
errors = self.__errors.get(session)
@@ -300,10 +300,10 @@
def render_field_help(self, session, *args):
pass
-
+
def render_form_field_class(self, session, *args):
return self.css_class
-
+
class ScalarField(FormField):
def __init__(self, app, name, input):
super(ScalarField, self).__init__(app, name)
@@ -338,7 +338,7 @@
self.input = PasswordInput(app, "input")
self.add_child(self.input)
-
+
class IntegerField(ScalarField):
def __init__(self, app, name):
super(IntegerField, self).__init__(app, name, None)
@@ -356,7 +356,7 @@
errors.append(error)
else:
self.set(session, self.input.param.get_default(session))
-
+
# XXX make this use a RadioInputSet instead?
class RadioField(FormField):
def __init__(self, app, name, param):
Modified: mgmt/trunk/wooly/python/wooly/forms.strings
===================================================================
--- mgmt/trunk/wooly/python/wooly/forms.strings 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/forms.strings 2009-03-19 15:45:47 UTC (rev 3175)
@@ -115,7 +115,7 @@
div.compact.last {
margin-bottom: 1em;
}
-
+
[FormField.html]
<div class="{form_field_class}">
<div class="title">{title}</div> <div
class="field_help">{field_help}</div><div
class="clear_left"></div>
Modified: mgmt/trunk/wooly/python/wooly/model.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/model.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/model.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -40,7 +40,7 @@
if len(self.endpoints) > 1:
raise Exception("Too many endpoints")
-
+
endpoint = self.Endpoint()
endpoint.name = name
endpoint.multiplicity = multiplicity
@@ -144,7 +144,7 @@
new_that.unlock()
finally:
this.unlock()
-
+
class ModelObject(object):
sequence = 100
@@ -187,7 +187,7 @@
def do_add_method(self, that):
if that == None:
raise Exception()
-
+
end.add_object(this, that)
this.setmethod("do_add_" + end.name, do_add_method)
@@ -198,7 +198,7 @@
def do_remove_method(self, that):
if that == None:
raise Exception()
-
+
end.remove_object(this, that)
this.setmethod("do_remove_" + end.name, do_remove_method)
@@ -221,7 +221,7 @@
try:
for end in self.mclass.endpoints:
this = self
-
+
if end.is_scalar():
end.set_object(this, None)
else:
Modified: mgmt/trunk/wooly/python/wooly/parameters.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/parameters.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/parameters.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -12,12 +12,12 @@
@classmethod
def sep(cls):
return "|"
-
+
def add(self, session, value, full_key):
# full_key looks like <DictParameter.path>_<stuff>
keys = self.get(session)
-
+
# separate the DictParameter.path from the stuff
foo, stuff = full_key.split(DictParameter.sep(), 1)
if stuff:
@@ -26,8 +26,8 @@
def split_stuff(self, keys, stuff, value):
""" Each time there is a delimiter in stuff,
create a nested dictionary entry """
-
- # can't use: stuff_1, stuff_2 = stuff.split(DictParameter.sep(), 1)
+
+ # can't use: stuff_1, stuff_2 = stuff.split(DictParameter.sep(), 1)
stuff_list = stuff.split(DictParameter.sep(), 1)
stuff_1 = stuff_list[0]
if len(stuff_list) > 1:
@@ -40,16 +40,16 @@
keys[stuff_1] = value
elif stuff_1 in keys:
del keys[stuff_1]
-
+
def get_instance_key(self, key):
return DictParameter.sep().join((self.path, key))
-
+
def get_default(self, session):
return copy(self.default)
def clear(self):
self.default = dict()
-
+
class ListParameter(Parameter):
def __init__(self, app, name, param):
super(ListParameter, self).__init__(app, name)
Modified: mgmt/trunk/wooly/python/wooly/resources.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/resources.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/resources.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -19,7 +19,7 @@
def get(self, key):
if not self.strings:
self.load()
-
+
return self.strings.get(key)
def __repr__(self):
Modified: mgmt/trunk/wooly/python/wooly/tables.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/tables.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/tables.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -23,7 +23,7 @@
def get_visible_columns(self, session):
# default impl
return [col for col in self.columns if col.visible]
-
+
def get_request_visible_columns(self, session, vlist):
return [col for col in self.columns if col.visible or col.name in vlist]
@@ -76,7 +76,7 @@
def set_default_column_name(self, name):
self.scolumn.default = name
-
+
def is_reversed(self, session):
return self.reversed.get(session)
@@ -131,7 +131,7 @@
# in case a non-sql column is included in an sqltable # XXX ugh
def get_orderby_sql(self, session):
pass
-
+
class ItemTableColumnHeader(Widget):
def __init__(self, app, name, column):
super(ItemTableColumnHeader, self).__init__(app, name)
@@ -170,7 +170,7 @@
return "unsorted_up"
else:
return "unsorted_down"
-
+
class SqlTable(ItemTable):
def __init__(self, app, name):
super(SqlTable, self).__init__(app, name)
@@ -210,7 +210,7 @@
def build_sql(self, elems):
writer = Writer()
-
+
for elem in elems:
if elem:
writer.write(elem)
@@ -272,26 +272,26 @@
def find_item(self, session, *args):
""" Find items in the current ItemSet
-
+
To use this an SqlTable derived object needs to have a
[<class>.find_sql] section and override render_find_sql_where
and get_find_sql_values.
Returns a list of dictionaries. Each dictionary is a matched row.
"""
-
+
conn = self.get_connection(session)
if conn:
cursor = conn.cursor()
select = self.render_find_sql(session, *args)
where = self.render_sql_where(session, *args)
sql_values = self.get_sql_values(session, *args)
-
+
find_where = self.render_find_sql_where(session, *args)
find_values = self.get_find_sql_values(session, *args)
sql = " and ".join(["%s %s" % (select, where),
find_where])
if sql_values:
for sql_val in sql_values:
find_values[sql_val] = sql_values[sql_val]
-
+
try:
cursor.execute(sql, find_values)
return self.cursor_to_rows(cursor)
@@ -301,16 +301,16 @@
def cursor_to_rows(self, cursor):
cols = [spec[0] for spec in cursor.description]
rows = list()
-
+
for tuple in cursor:
row = dict()
for col, datum in zip(cols, tuple):
row[col] = datum
rows.append(row)
-
+
return rows
-
+
class SqlTableColumn(ItemTableColumn):
# XXX to be consistent with similar methods, rename to
# get_sql_order_by
Modified: mgmt/trunk/wooly/python/wooly/widgets.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/widgets.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/widgets.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -204,7 +204,7 @@
def render_none(self, session, *args):
"""For producing a message when the set is
empty"""
-
+
return None
class RenderingItemSet(Widget):
@@ -265,13 +265,13 @@
def get_items(self, session, *args):
"""Get the root items"""
pass
-
+
def get_child_items(self, session, *args):
pass
def render_child_items(self, session, *args):
writer = Writer()
-
+
for child in self.get_child_items(session, *args):
self.item_renderer.render(writer, session, child)
@@ -318,7 +318,7 @@
elif count < page_index * self.page_size:
self.pageset_index.set(session, 0)
self.page_index.set(session, 0)
-
+
return self.page_count.set(session, count)
def get_count(self, session):
@@ -335,10 +335,10 @@
def __link(self, href, content, class_=""):
return "<a href=\"%s\"%s>%s</a>" % \
(href, class_ and " class=\"%s\" " % class_ or "
", content)
-
+
def render_prev_page_link(self, session, *args):
page = self.page_index.get(session)
-
+
if page < 1:
html = self.__link(session.marshal(), "<", "pagenav
disabled")
else:
@@ -415,7 +415,7 @@
def render_class_attr(self, session, page):
if self.widget.page_index.get(session) == page:
return " class=\"selected\""
-
+
def render_href(self, session, page):
branch = session.branch()
self.widget.page_index.set(branch, page)
@@ -462,13 +462,13 @@
class ActionRenderer(TemplateRenderer):
def render_href(self, session, action):
return action[2] and " href=\"%s\"" % action[0] or
""
-
+
def render_content(self, session, action):
return action[1]
-
+
def render_tag(self, session, action):
return action[2] and "a" or "span"
-
+
def escape_amp(string):
return str(string).replace("&", "&")
@@ -482,4 +482,4 @@
t += "&" + name + ";"
else:
t += i
- return t
+ return t
Modified: mgmt/trunk/wooly/python/wooly/wsgiserver.py
===================================================================
--- mgmt/trunk/wooly/python/wooly/wsgiserver.py 2009-03-19 15:12:52 UTC (rev 3174)
+++ mgmt/trunk/wooly/python/wooly/wsgiserver.py 2009-03-19 15:45:47 UTC (rev 3175)
@@ -1,27 +1,27 @@
# Copyright (c) 2004-2007, CherryPy Team (team(a)cherrypy.org)
# All rights reserved.
-# Redistribution and use in source and binary forms, with or without modification,
+# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
-# * Redistributions of source code must retain the above copyright notice,
+# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
-# * Neither the name of the CherryPy Team nor the names of its contributors
-# may be used to endorse or promote products derived from this software
+# * Neither the name of the CherryPy Team nor the names of its contributors
+# may be used to endorse or promote products derived from this software
# without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""A high-speed, production ready, thread pooled, generic WSGI server.
@@ -30,23 +30,23 @@
(without using CherryPy's application machinery):
from cherrypy import wsgiserver
-
+
def my_crazy_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
-
- # Here we set our application to the script_name '/'
+
+ # Here we set our application to the script_name '/'
wsgi_apps = [('/', my_crazy_app)]
-
+
server = wsgiserver.CherryPyWSGIServer(('localhost', 8070), wsgi_apps,
server_name='localhost')
-
+
# Want SSL support? Just set these attributes
# server.ssl_certificate = <filename>
# server.ssl_private_key = <filename>
-
+
if __name__ == '__main__':
try:
server.start()
@@ -112,9 +112,9 @@
class HTTPRequest(object):
"""An HTTP Request (and response).
-
+
A single HTTP connection may consist of multiple request/response pairs.
-
+
connection: the HTTP Connection object which spawned this request.
rfile: the 'read' fileobject from the connection's socket
ready: when True, the request has been parsed and is ready to begin
@@ -128,13 +128,13 @@
transfer-coding. This value is set automatically inside
send_headers.
"""
-
+
def __init__(self, connection):
self.connection = connection
self.rfile = self.connection.rfile
self.sendall = self.connection.sendall
self.environ = connection.environ.copy()
-
+
self.ready = False
self.started_response = False
self.status = ""
@@ -142,7 +142,7 @@
self.sent_headers = False
self.close_connection = False
self.chunked_write = False
-
+
def parse_request(self):
"""Parse the next HTTP request start-line and
message-headers."""
# HTTP/1.1 connections are persistent by default. If a client
@@ -157,7 +157,7 @@
# Force self.ready = False so the connection will close.
self.ready = False
return
-
+
if request_line == "\r\n":
# RFC 2616 sec 4.1: "...if the server is reading the protocol
# stream at the beginning of a message and receives a CRLF
@@ -167,27 +167,27 @@
if not request_line:
self.ready = False
return
-
+
server = self.connection.server
environ = self.environ
environ["SERVER_SOFTWARE"] = "%s WSGI Server" %
server.version
-
+
method, path, req_protocol = request_line.strip().split(" ", 2)
environ["REQUEST_METHOD"] = method
-
+
# path may be an abs_path (including "http://host.domain.tld");
scheme, location, path, params, qs, frag = urlparse(path)
-
+
if frag:
self.simple_response("400 Bad Request",
"Illegal #fragment in Request-URI.")
return
-
+
if scheme:
environ["wsgi.url_scheme"] = scheme
if params:
path = path + ";" + params
-
+
# Unquote the path+params (e.g. "/this%20path" -> "this
path").
#
http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
#
@@ -196,7 +196,7 @@
# safely decoded."
http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
atoms = [unquote(x) for x in quoted_slash.split(path)]
path = "%2F".join(atoms)
-
+
if path == "*":
# This means, of course, that the last wsgi_app (shortest path)
# will always handle a URI of "*".
@@ -214,11 +214,11 @@
else:
self.simple_response("404 Not Found")
return
-
+
# Note that, like wsgiref and most other WSGI servers,
# we unquote the path but not the query string.
environ["QUERY_STRING"] = qs
-
+
# Compare request and server HTTP protocol versions, in case our
# server does not support the requested protocol. Limit our output
# to min(req, server). We want the following output:
@@ -243,24 +243,24 @@
# See
http://www.faqs.org/rfcs/rfc2145.html.
environ["ACTUAL_SERVER_PROTOCOL"] = server.protocol
self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
-
+
# If the Request-URI was an absoluteURI, use its location atom.
if location:
environ["SERVER_NAME"] = location
-
+
# then all the http headers
try:
self.read_headers()
except ValueError, ex:
self.simple_response("400 Bad Request", repr(ex.args))
return
-
+
creds = environ.get("HTTP_AUTHORIZATION", "").split("
", 1)
environ["AUTH_TYPE"] = creds[0]
if creds[0].lower() == 'basic':
user, pw = base64.decodestring(creds[1]).split(":", 1)
environ["REMOTE_USER"] = user
-
+
# Persistent connection support
if self.response_protocol == "HTTP/1.1":
if environ.get("HTTP_CONNECTION", "") ==
"close":
@@ -269,16 +269,16 @@
# HTTP/1.0
if environ.get("HTTP_CONNECTION", "") !=
"Keep-Alive":
self.close_connection = True
-
+
# Transfer-Encoding support
te = None
if self.response_protocol == "HTTP/1.1":
te = environ.get("HTTP_TRANSFER_ENCODING")
if te:
te = [x.strip().lower() for x in te.split(",") if x.strip()]
-
+
read_chunked = False
-
+
if te:
for enc in te:
if enc == "chunked":
@@ -289,11 +289,11 @@
self.simple_response("501 Unimplemented")
self.close_connection = True
return
-
+
if read_chunked:
if not self.decode_chunked():
return
-
+
# From PEP 333:
# "Servers and gateways that implement HTTP 1.1 must provide
# transparent support for HTTP 1.1's "expect/continue" mechanism.
@@ -313,23 +313,23 @@
# but it seems like it would be a big slowdown for such a rare case.
if environ.get("HTTP_EXPECT", "") ==
"100-continue":
self.simple_response(100)
-
+
self.ready = True
-
+
def read_headers(self):
"""Read header lines from the incoming stream."""
environ = self.environ
-
+
while True:
line = self.rfile.readline()
if not line:
# No more data--illegal end of headers
raise ValueError("Illegal end of headers.")
-
+
if line == '\r\n':
# Normal end of headers
break
-
+
if line[0] in ' \t':
# It's a continuation line.
v = line.strip()
@@ -337,20 +337,20 @@
k, v = line.split(":", 1)
k, v = k.strip().upper(), v.strip()
envname = "HTTP_" + k.replace("-", "_")
-
+
if k in comma_separated_headers:
existing = environ.get(envname)
if existing:
v = ", ".join((existing, v))
environ[envname] = v
-
+
ct = environ.pop("HTTP_CONTENT_TYPE", None)
if ct:
environ["CONTENT_TYPE"] = ct
cl = environ.pop("HTTP_CONTENT_LENGTH", None)
if cl:
environ["CONTENT_LENGTH"] = cl
-
+
def decode_chunked(self):
"""Decode the 'chunked' transfer
coding."""
cl = 0
@@ -369,15 +369,15 @@
"Bad chunked transfer coding "
"(expected '\\r\\n', got %r)" %
crlf)
return
-
+
# Grab any trailer headers
self.read_headers()
-
+
data.seek(0)
self.environ["wsgi.input"] = data
self.environ["CONTENT_LENGTH"] = str(cl) or ""
return True
-
+
def respond(self):
"""Call the appropriate WSGI app and write its iterable
output."""
response = self.wsgi_app(self.environ, self.start_response)
@@ -400,23 +400,23 @@
self.send_headers()
if self.chunked_write:
self.sendall("0\r\n\r\n")
-
+
def simple_response(self, status, msg=""):
"""Write a simple response back to the client."""
status = str(status)
buf = ["%s %s\r\n" % (self.connection.server.protocol, status),
"Content-Length: %s\r\n" % len(msg)]
-
+
if status[:3] == "413" and self.response_protocol ==
'HTTP/1.1':
# Request Entity Too Large
self.close_connection = True
buf.append("Connection: close\r\n")
-
+
buf.append("\r\n")
if msg:
buf.append(msg)
self.sendall("".join(buf))
-
+
def start_response(self, status, headers, exc_info = None):
"""WSGI callable to begin the HTTP response."""
if self.started_response:
@@ -432,31 +432,31 @@
self.status = status
self.outheaders.extend(headers)
return self.write
-
+
def write(self, chunk):
"""WSGI callable to write unbuffered data to the client.
-
+
This method is also used internally by start_response (to write
data from the iterable returned by the WSGI application).
"""
if not self.started_response:
raise AssertionError("WSGI write called before start_response.")
-
+
if not self.sent_headers:
self.sent_headers = True
self.send_headers()
-
+
if self.chunked_write and chunk:
buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"]
self.sendall("".join(buf))
else:
self.sendall(chunk)
-
+
def send_headers(self):
"""Assert, process, and send the HTTP response
message-headers."""
hkeys = [key.lower() for key, value in self.outheaders]
status = int(self.status[:3])
-
+
if status == 413:
# Request Entity Too Large. Close conn to avoid garbage.
self.close_connection = True
@@ -474,7 +474,7 @@
else:
# Closing the conn is the only way to determine len.
self.close_connection = True
-
+
if "connection" not in hkeys:
if self.response_protocol == 'HTTP/1.1':
if self.close_connection:
@@ -482,15 +482,15 @@
else:
if not self.close_connection:
self.outheaders.append(("Connection",
"Keep-Alive"))
-
+
if "date" not in hkeys:
self.outheaders.append(("Date", rfc822.formatdate()))
-
+
server = self.connection.server
-
+
if "server" not in hkeys:
self.outheaders.append(("Server", server.version))
-
+
buf = [server.protocol, " ", self.status, "\r\n"]
try:
buf += [k + ": " + v + "\r\n" for k, v in
self.outheaders]
@@ -512,7 +512,7 @@
def _ssl_wrap_method(method, is_reader=False):
"""Wrap the given method with SSL error-trapping.
-
+
is_reader: if False (the default), EOF errors will be raised.
If True, EOF errors will return "" (to emulate normal sockets).
"""
@@ -531,7 +531,7 @@
except SSL.SysCallError, e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""
-
+
errno = e.args[0]
if is_reader and errno in socket_errors_to_ignore:
return ""
@@ -539,13 +539,13 @@
except SSL.Error, e:
if is_reader and e.args == (-1, 'Unexpected EOF'):
return ""
-
+
thirdarg = None
try:
thirdarg = e.args[0][0][2]
except IndexError:
pass
-
+
if is_reader and thirdarg == 'ssl handshake failure':
return ""
if thirdarg == 'http request':
@@ -558,10 +558,10 @@
class SSL_fileobject(socket._fileobject):
"""Faux file object attached to a socket object."""
-
+
ssl_timeout = 3
ssl_retry = .01
-
+
close = _ssl_wrap_method(socket._fileobject.close)
flush = _ssl_wrap_method(socket._fileobject.flush)
write = _ssl_wrap_method(socket._fileobject.write)
@@ -573,7 +573,7 @@
class HTTPConnection(object):
"""An HTTP connection (active socket).
-
+
socket: the raw socket object (usually TCP) for this connection.
addr: the "bind address" for the remote end of the socket.
For IP sockets, this is a tuple of (REMOTE_ADDR, REMOTE_PORT).
@@ -581,12 +581,12 @@
server: the HTTP Server for this Connection. Usually, the server
object possesses a passive (server) socket which spawns multiple,
active (client) sockets, one for each connection.
-
+
environ: a WSGI environ template. This will be copied for each request.
rfile: a fileobject for reading from the socket.
sendall: a function for writing (+ flush) to the socket.
"""
-
+
rbufsize = -1
RequestHandlerClass = HTTPRequest
environ = {"wsgi.version": (1, 0),
@@ -596,16 +596,16 @@
"wsgi.run_once": False,
"wsgi.errors": sys.stderr,
}
-
+
def __init__(self, sock, addr, server):
self.socket = sock
self.addr = addr
self.server = server
self.__aborted = False
-
+
# Copy the class environ into self.
self.environ = self.environ.copy()
-
+
if SSL and isinstance(sock, SSL.ConnectionType):
timeout = sock.gettimeout()
self.rfile = SSL_fileobject(sock, "r", self.rbufsize)
@@ -619,11 +619,11 @@
else:
self.rfile = sock.makefile("rb", self.rbufsize)
self.sendall = sock.sendall
-
+
self.environ.update({"wsgi.input": self.rfile,
"SERVER_NAME": self.server.server_name,
})
-
+
if isinstance(self.server.bind_addr, basestring):
# AF_UNIX. This isn't really allowed by WSGI, which doesn't
# address unix domain sockets. But it's better than nothing.
@@ -637,7 +637,7 @@
def abort(self):
self.__aborted = True
-
+
def communicate(self):
"""Read each request and respond appropriately."""
try:
@@ -676,7 +676,7 @@
except:
if req:
req.simple_response("500 Internal Server Error", format_exc())
-
+
def close(self):
"""Close the socket underlying this connection."""
self.rfile.close()
@@ -696,24 +696,24 @@
class WorkerThread(threading.Thread):
"""Thread which continuously polls a Queue for Connection objects.
-
+
server: the HTTP Server which spawned this thread, and which owns the
Queue and is placing active connections into it.
ready: a simple flag for the calling server to know when this thread
has begun polling the Queue.
-
+
Due to the timing issues of polling a Queue, a WorkerThread does not
check its own 'ready' flag after it has started. To stop the thread,
it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
(one for each running WorkerThread).
"""
-
+
def __init__(self, server):
self.ready = False
self.server = server
self.currentConnection = None
threading.Thread.__init__(self)
-
+
def run(self):
try:
self.ready = True
@@ -721,7 +721,7 @@
conn = self.server.requests.get()
if conn is _SHUTDOWNREQUEST:
return
-
+
self.currentConnection = conn
try:
@@ -734,14 +734,14 @@
class SSLConnection:
"""A thread-safe wrapper for an SSL.Connection.
-
+
*args: the arguments to create the wrapped SSL.Connection(*args).
"""
-
+
def __init__(self, *args):
self._ssl_conn = SSL.Connection(*args)
self._lock = threading.RLock()
-
+
for f in ('get_context', 'pending', 'send', 'write',
'recv', 'read',
'renegotiate', 'bind', 'listen', 'connect',
'accept',
'setblocking', 'fileno', 'shutdown',
'close', 'get_cipher_list',
@@ -761,7 +761,7 @@
class CherryPyWSGIServer(object):
"""An HTTP server for WSGI.
-
+
bind_addr: a (host, port) tuple if TCP sockets are desired;
for UNIX sockets, supply the filename as a string.
wsgi_app: the WSGI 'application callable'; multiple WSGI applications
@@ -773,39 +773,39 @@
request_queue_size: the 'backlog' argument to socket.listen();
specifies the maximum number of queued connections (default 5).
timeout: the timeout in seconds for accepted connections (default 10).
-
+
protocol: the version string to write in the Status-Line of all
HTTP responses. For example, "HTTP/1.1" (the default). This
also limits the supported features used in the response.
-
-
+
+
SSL/HTTPS
---------
The OpenSSL module must be importable for SSL functionality.
You can obtain it from
http://pyopenssl.sourceforge.net/
-
+
ssl_certificate: the filename of the server SSL certificate.
ssl_privatekey: the filename of the server's private key file.
-
+
If either of these is None (both are None by default), this server
will not use SSL. If both are given and are valid, they will be read
on server start and used in the SSL context for the listening socket.
"""
-
+
protocol = "HTTP/1.1"
version = "CherryPy/3.0.3"
ready = False
_interrupt = None
ConnectionClass = HTTPConnection
-
+
# Paths to certificate and private key files
ssl_certificate = None
ssl_private_key = None
-
+
def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
max=-1, request_queue_size=5, timeout=10):
self.requests = Queue.Queue(max)
-
+
if callable(wsgi_app):
# We've been handed a single wsgi_app, in CP-2.1 style.
# Assume it's mounted at "".
@@ -817,7 +817,7 @@
self.mount_points = wsgi_app
self.mount_points.sort()
self.mount_points.reverse()
-
+
self.bind_addr = bind_addr
self.numthreads = numthreads or 1
if not server_name:
@@ -825,9 +825,9 @@
self.server_name = server_name
self.request_queue_size = request_queue_size
self._workerThreads = []
-
+
self.timeout = timeout
-
+
def start(self):
"""Run the server forever."""
# We don't have to trap KeyboardInterrupt or SystemExit here,
@@ -835,19 +835,19 @@
# If you're using this server with another framework, you should
# trap those exceptions in whatever code block calls start().
self._interrupt = None
-
+
# Select the appropriate socket
if isinstance(self.bind_addr, basestring):
# AF_UNIX socket
-
+
# So we can reuse the socket...
try: os.unlink(self.bind_addr)
except: pass
-
+
# So everyone can access the socket...
try: os.chmod(self.bind_addr, 0777)
except: pass
-
+
info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "",
self.bind_addr)]
else:
# AF_INET or AF_INET6 socket
@@ -871,7 +871,7 @@
except socket.gaierror:
# Probably a DNS issue. Assume IPv4.
info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "",
self.bind_addr)]
-
+
self.socket = None
msg = "No socket could be created"
for res in info:
@@ -886,11 +886,11 @@
break
if not self.socket:
raise socket.error, msg
-
+
# Timeout so KeyboardInterrupt can be caught on Win32
self.socket.settimeout(1)
self.socket.listen(self.request_queue_size)
-
+
# Create worker threads
for i in xrange(self.numthreads):
self._workerThreads.append(WorkerThread(self))
@@ -900,7 +900,7 @@
for worker in self._workerThreads:
while not worker.ready:
time.sleep(.1)
-
+
self.ready = True
while self.ready:
self.tick()
@@ -909,7 +909,7 @@
# Wait for self.stop() to complete. See _set_interrupt.
time.sleep(0.1)
raise self.interrupt
-
+
def bind(self, family, type, proto=0):
"""Create (or recreate) the actual socket
object."""
self.socket = socket.socket(family, type, proto)
@@ -918,7 +918,7 @@
if self.ssl_certificate and self.ssl_private_key:
if SSL is None:
raise ImportError("You must install pyOpenSSL to use HTTPS.")
-
+
# See
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.use_privatekey_file(self.ssl_private_key)
@@ -926,7 +926,7 @@
self.socket = SSLConnection(ctx, self.socket)
self.populate_ssl_environ()
self.socket.bind(self.bind_addr)
-
+
def tick(self):
"""Accept a new connection and put it on the
Queue."""
try:
@@ -951,7 +951,7 @@
# Just try again. See
http://www.cherrypy.org/ticket/479.
return
raise
-
+
def _get_interrupt(self):
return self._interrupt
def _set_interrupt(self, interrupt):
@@ -961,11 +961,11 @@
interrupt = property(_get_interrupt, _set_interrupt,
doc="Set this to an Exception instance to "
"interrupt the server.")
-
+
def stop(self):
"""Gracefully shutdown a server that is serving
forever."""
self.ready = False
-
+
sock = getattr(self, "socket", None)
if sock:
if not isinstance(self.bind_addr, basestring):
@@ -997,12 +997,12 @@
if hasattr(sock, "close"):
sock.close()
self.socket = None
-
+
# Must shut down threads here so the code that calls
# this method can know when all threads are stopped.
for worker in self._workerThreads:
self.requests.put(_SHUTDOWNREQUEST)
-
+
# Don't join currentThread (when stop is called inside a request).
current = threading.currentThread()
while self._workerThreads:
@@ -1014,7 +1014,7 @@
worker.join()
except AssertionError:
pass
-
+
def populate_ssl_environ(self):
"""Create WSGI environ entries to be merged into each
request."""
cert = open(self.ssl_certificate).read()
@@ -1022,11 +1022,11 @@
self.ssl_environ = {
# pyOpenSSL doesn't provide access to any of these AFAICT
## 'SSL_PROTOCOL': 'SSLv2',
-## SSL_CIPHER string The cipher specification name
-## SSL_VERSION_INTERFACE string The mod_ssl program version
-## SSL_VERSION_LIBRARY string The OpenSSL program version
+## SSL_CIPHER string The cipher specification name
+## SSL_VERSION_INTERFACE string The mod_ssl program version
+## SSL_VERSION_LIBRARY string The OpenSSL program version
}
-
+
# Server certificate attributes
self.ssl_environ.update({
'SSL_SERVER_M_VERSION': cert.get_version(),
@@ -1034,17 +1034,17 @@
## 'SSL_SERVER_V_START': Validity of server's certificate (start
time),
## 'SSL_SERVER_V_END': Validity of server's certificate (end
time),
})
-
+
for prefix, dn in [("I", cert.get_issuer()),
("S", cert.get_subject())]:
# X509Name objects don't seem to have a way to get the
# complete DN string. Use str() and slice it instead,
# because str(dn) == "<X509Name object
'/C=US/ST=...'>"
dnstr = str(dn)[18:-2]
-
+
wsgikey = 'SSL_SERVER_%s_DN' % prefix
self.ssl_environ[wsgikey] = dnstr
-
+
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
# for any value to contain slashes itself (in a URL).
while dnstr:
@@ -1055,5 +1055,3 @@
if key and value:
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
self.ssl_environ[wsgikey] = value
-
-