On Thu, Sep 30, 2004 at 03:15:33PM -0400, Aaron Switzer wrote:
> I do agree that a "lower-level" extension point would be useful and more
> inline with OO design. I guess stripping out the HTML specific stuff
> would be a good starting point.
I couldn't make CVS diff honor the -N flag for some reason, so here is a
first cut at a new class HTTPContent, containing the non-HTML parts
formerly in Page (and a diff to Page.py).
Please comment.
I'd like to remove preAction and postAction from Page as well, or at
least make them be called in a more consistent fashion. Right now they
get called for _action_ style things, but not when defaultAction() is
invoked. I suggest removing Page.preAction() and Page.postAction() and
letting them fall through to the empty versions in HTTPContent, and then
consistently calling them before and after *all* actions, including
defaultAction(). Maybe this should be another diff...
--
[EMAIL PROTECTED]
Some people have a way with words, while others... erm... thingy.
from Common import *
from HTTPServlet import HTTPServlet
from WebUtils import Funcs
from Application import EndResponse
class HTTPContentError(Exception):
pass
class HTTPContent(HTTPServlet):
"""
HTTPContent is a type of HTTPServlet that is more convenient for
Servlets which represent content generated in response to
GET and POST requests.
Subclasses typically override defaultAction().
In `awake`, the page sets self attributes: `_transaction`,
`_response` and `_request` which subclasses should use as
appropriate.
For the purposes of output, the `write` and `writeln`
convenience methods are provided.
If you plan to produce HTML content, you should start by looking
at Page instead of this lower-level class.
"""
## Transactions ##
def awake(self, transaction):
"""
Makes instance variables from the transaction. This is
where Page becomes unthreadsafe, as the page is tied to
the transaction. This is also what allows us to
implement functions like `write`, where you don't
need to pass in the transaction or response.
"""
HTTPServlet.awake(self, transaction)
self._response = transaction.response()
self._request = transaction.request()
self._session = None # don't create unless needed
assert self._transaction is not None
assert self._response is not None
assert self._request is not None
def respondToGet(self, transaction):
"""
Invoked in response to a GET request method. All methods
are passed to `_respond`
"""
self._respond(transaction)
def respondToPost(self, transaction):
"""
Invoked in response to a GET request method. All methods
are passed to `_respond`
"""
self._respond(transaction)
def _respond(self, transaction):
"""
Handles actions if an ``_action_`` or ``_action_XXX``
field is defined, otherwise invokes `writeHTML`.
Invoked by both `respondToGet` and `respondToPost`.
"""
req = transaction.request()
# Support old style actions from 0.5.x and below.
# Use setting OldStyleActions in Application.config
# to use this.
if self.transaction().application().setting('OldStyleActions', ) \
and req.hasField('_action_'):
action = self.methodNameForAction(req.field('_action_'))
actions = self._actionSet()
if actions.has_key(action):
self.preAction(action)
apply(getattr(self, action), (transaction,))
self.postAction(action)
return
else:
raise HTTPContentError, "Action '%s' is not in the
public list of actions, %s, for %s." % (action, actions.keys(), self)
# Check for actions
for action in self.actions():
if req.hasField('_action_%s' % action) or \
req.field('_action_', None) == action or \
(req.hasField('_action_%s.x' % action) and \
req.hasField('_action_%s.y' % action)):
self.handleAction(action)
return
# If no action was found, run the default.
self.defaultAction()
def defaultAction(self):
"""
The core method that gets called as a result of requests.
Subclasses should override this.
"""
pass
def sleep(self, transaction):
"""
:ignore:
We unset some variables. Very boring.
"""
self._session = None
self._request = None
self._response = None
self._transaction = None
HTTPServlet.sleep(self, transaction)
## Access ##
def application(self):
"""
The `Application` instance we're using.
"""
return self._transaction.application()
def transaction(self):
"""
The `Transaction` we're currently handling.
"""
return self._transaction
def request(self):
"""
The request (`HTTPRequest`) we're handling.
"""
return self._request
def response(self):
"""
The response (`HTTPResponse`) we're handling.
"""
return self._response
def session(self):
"""
The session object, i.e., a state for the current
user (associated with a browser instance, really).
If no session exists, then a session will be created.
"""
if not self._session:
self._session = self._transaction.session()
return self._session
## Writing ##
def write(self, *args):
"""
Writes the arguments, which are turned to strings
(with `str`) and concatenated before being written
to the response.
"""
for arg in args:
self._response.write(str(arg))
def writeln(self, *args):
"""
Writes the arguments (like `write`), adding a newline
after.
"""
for arg in args:
self._response.write(str(arg))
self._response.write('\n')
## Threading ##
def canBeThreaded(self):
"""
Returns 0 because of the instance variables we set up
in `awake`. """
return 0
## Actions ##
def handleAction(self, action):
"""
Invoked by `_respond` when a legitimate action has
been found in a form. Invokes `preAction`, the actual
action method and `postAction`.
Subclasses rarely override this method.
"""
self.preAction(action)
getattr(self, action)()
self.postAction(action)
def actions(self):
"""
Returns a list of method names that are allowable
actions from HTML forms. The default implementation
returns []. See `_respond` for more about actions.
"""
return []
def preAction(self, actionName):
"""
Invoked by self prior to invoking a action method.
The `actionName` is passed to this method,
although it seems a generally bad idea to rely on
this. However, it's still provided just in case you
need that hook.
By default this does nothing.
"""
pass
def postAction(self, actionName):
"""
Invoked by self after invoking a action method.
Subclasses may override to
customize and may or may not invoke super as they see
fit. The `actionName` is passed to this method,
although it seems a generally bad idea to rely on
this. However, it's still provided just in case you
need that hook.
By default this does nothing.
"""
pass
def methodNameForAction(self, name):
"""
This method exists only to support "old style" actions
from WebKit 0.5.x and below.
Invoked by _respond() to determine the method name for
a given action name (which usually comes from an HTML
submit button in a form). This implementation simple
returns the name. Subclasses could "filter" the name
by altering it or looking it up in a
dictionary. Subclasses should override this method
when action names don't match their method names.
"""
## 2003-03 ib @@: old-style actions are gone, so this
## method should go too.
return name
def urlEncode(self, s):
"""
Alias for `WebUtils.Funcs.urlEncode`. Quotes special
characters using the % substitutions.
"""
## @@: urllib.quote, or
return Funcs.urlEncode(s)
def urlDecode(self, s):
"""
Alias for `WebUtils.Funcs.urlDecode`. Turns special
% characters into actual characters.
"""
return Funcs.urlDecode(s)
def forward(self, URL, context=None):
"""
Forwards this request to another servlet. See
`Application.forward` for details. The main
difference is that here you don't have to pass in the
transaction as the first argument.
"""
self.application().forward(self.transaction(), URL, context)
def includeURL(self, URL, context=None):
"""
Includes the response of another servlet in the
current servlet's response. See
`Application.includeURL` for details. The main
difference is that here you don't have to pass in the
transaction as the first argument.
"""
self.application().includeURL(self.transaction(), URL, context)
def callMethodOfServlet(self, URL, method, *args, **kwargs):
"""
Call a method of another servlet. See
`Application.callMethodOfServlet` for details. The
main difference is that here you don't have to pass in
the transaction as the first argument.
A `context` keyword argument is also possible, even
though it isn't present in the method signature.
"""
return apply(self.application().callMethodOfServlet,
(self.transaction(), URL, method) + args, kwargs)
def endResponse(self):
"""
When this method is called during `awake` or
`respond`, servlet processing will end immediately,
and the accumulated response will be sent.
Note that `sleep` will still be called, providing a
chance to clean up or free any resources.
"""
raise EndResponse
def sendRedirectAndEnd(self, url):
"""
Sends a redirect back to the client and ends the
response. This is a very popular pattern.
"""
self.response().sendRedirect(url)
self.endResponse()
## Utility ##
def sessionEncode(self, url=None):
"""
Utility function to access `Session.sessionEncode`.
Takes a url and adds the session ID as a parameter.
This is for cases where you don't know if the client
will accepts cookies.
"""
if url == None:
url = self.request().uri()
return self.session().sessionEncode(url)
## Private Utility ##
def _actionSet(self):
""" Returns a dictionary whose keys are the names
returned by actions(). The dictionary is used for a
quick set-membership-test in self._respond. Subclasses
don't generally override this method or invoke it. """
if not hasattr(self, '_actionDict'):
self._actionDict = {}
for action in self.actions():
self._actionDict[action] = 1
return self._actionDict
## Exception Reports ##
def writeExceptionReport(self, handler):
"""
Writes extra information to the exception report.
The `handler` argument is the exception handler, and
information is written there (using `writeTitle`,
`write`, and `writeln`). This information is added
to the exception report.
See `WebKit.ExceptionHandler` for more information.
"""
handler.writeln('''Servlets can provide debugging information here by
overriding writeExceptionReport().<br>For example:
<pre> exceptionReportAttrs = 'foo bar baz'.split()
def writeExceptionReport(self, handler):
handler.writeTitle(self.__class__.__name__)
handler.writeAttrs(self, self.exceptionReportAttrs)
handler.write('any string')
</pre>
See WebKit/ExceptionHandler.py for more information.
''')
Index: Page.py
===================================================================
RCS file: /cvsroot/webware/Webware/WebKit/Page.py,v
retrieving revision 1.33
diff -u -r1.33 Page.py
--- Page.py 1 Dec 2003 21:12:09 -0000 1.33
+++ Page.py 2 Oct 2004 18:39:32 -0000
@@ -1,16 +1,12 @@
from Common import *
-from HTTPServlet import HTTPServlet
+from HTTPContent import HTTPContent, HTTPContentError
from WebUtils import Funcs
from Application import EndResponse
-class PageError(Exception):
- pass
-
-
-class Page(HTTPServlet):
+class Page(HTTPContent):
"""
- Page is a type of HTTPServlet that is more convenient for
+ Page is a type of HTTPContent that is more convenient for
Servlets which represent HTML pages generated in response to
GET and POST requests. In fact, this is the most common type
of Servlet.
@@ -20,13 +16,6 @@
They might also choose to override `writeHTML` entirely.
- In `awake`, the page sets self attributes: `_transaction`,
- `_response` and `_request` which subclasses should use as
- appropriate.
-
- For the purposes of output, the `write` and `writeln`
- convenience methods are provided.
-
When developing a full-blown website, it's common to create a
subclass of `Page` called `SitePage` which defines the common look
and feel of the website and provides site-specific convenience
@@ -36,120 +25,11 @@
## Transactions ##
- def awake(self, transaction):
- """
- Makes instance variables from the transaction. This is
- where Page becomes unthreadsafe, as the page is tied to
- the transaction. This is also what allows us to
- implement functions like `write`, where you don't
- need to pass in the transaction or response.
- """
- HTTPServlet.awake(self, transaction)
- self._response = transaction.response()
- self._request = transaction.request()
- self._session = None # don't create unless needed
- assert self._transaction is not None
- assert self._response is not None
- assert self._request is not None
-
- def respondToGet(self, transaction):
- """
- Invoked in response to a GET request method. All methods
- are passed to `_respond`
- """
- self._respond(transaction)
-
- def respondToPost(self, transaction):
- """
- Invoked in response to a GET request method. All methods
- are passed to `_respond`
- """
- self._respond(transaction)
-
- def _respond(self, transaction):
- """
- Handles actions if an ``_action_`` or ``_action_XXX``
- field is defined, otherwise invokes `writeHTML`.
- Invoked by both `respondToGet` and `respondToPost`.
- """
- req = transaction.request()
-
- # Support old style actions from 0.5.x and below.
- # Use setting OldStyleActions in Application.config
- # to use this.
- if self.transaction().application().setting('OldStyleActions', ) \
- and req.hasField('_action_'):
- action = self.methodNameForAction(req.field('_action_'))
- actions = self._actionSet()
- if actions.has_key(action):
- self.preAction(action)
- apply(getattr(self, action), (transaction,))
- self.postAction(action)
- return
- else:
- raise PageError, "Action '%s' is not in the public
list of actions, %s, for %s." % (action, actions.keys(), self)
-
- # Check for actions
- for action in self.actions():
- if req.hasField('_action_%s' % action) or \
- req.field('_action_', None) == action or \
- (req.hasField('_action_%s.x' % action) and \
- req.hasField('_action_%s.y' % action)):
- self.handleAction(action)
- return
-
- self.defaultAction()
-
def defaultAction(self):
- self.writeHTML()
-
- def sleep(self, transaction):
- """
- :ignore:
- We unset some variables. Very boring.
- """
- self._session = None
- self._request = None
- self._response = None
- self._transaction = None
- HTTPServlet.sleep(self, transaction)
-
-
- ## Access ##
-
- def application(self):
- """
- The `Application` instance we're using.
- """
- return self._transaction.application()
-
- def transaction(self):
- """
- The `Transaction` we're currently handling.
- """
- return self._transaction
-
- def request(self):
- """
- The request (`HTTPRequest`) we're handling.
- """
- return self._request
-
- def response(self):
- """
- The response (`HTTPResponse`) we're handling.
- """
- return self._response
-
- def session(self):
"""
- The session object, i.e., a state for the current
- user (associated with a browser instance, really).
- If no session exists, then a session will be created.
+ The default action in a Page is to writeHTML().
"""
- if not self._session:
- self._session = self._transaction.session()
- return self._session
+ self.writeHTML()
## Generating results ##
@@ -180,10 +60,12 @@
tag. Invoked by writeBody().
With the prevalence of stylesheets (CSS), you can
- probably skip this particular HTML feature.
+ probably skip this particular HTML feature, but for
+ historical reasons this sets the page to black text
+ on white.
"""
- return 'color=black bgcolor=white'
+ return 'color="black" bgcolor="white"'
def writeHTML(self):
"""
@@ -267,7 +149,7 @@
implementation does nothing. Subclasses should override if
necessary. A typical implementation is:
- self.writeln('\t<link rel=stylesheet href=StyleSheet.css
type=text/css>')
+ self.writeln('\t<link rel="stylesheet" href="StyleSheet.css"
type="text/css">')
"""
pass
@@ -317,109 +199,23 @@
self.writeln('<p> This page has not yet customized its content. </p>')
- ## Writing ##
-
- def write(self, *args):
- """
- Writes the arguments, which are turned to strings
- (with `str`) and concatenated before being written
- to the response.
- """
- for arg in args:
- self._response.write(str(arg))
-
- def writeln(self, *args):
- """
- Writes the arguments (like `write`), adding a newline
- after.
- """
- for arg in args:
- self._response.write(str(arg))
- self._response.write('\n')
-
-
- ## Threading ##
-
- def canBeThreaded(self):
- """
- Returns 0 because of the instance variables we set up
- in `awake`. """
- return 0
-
-
- ## Actions ##
-
- def handleAction(self, action):
- """
- Invoked by `_respond` when a legitimate action has
- been found in a form. Invokes `preAction`, the actual
- action method and `postAction`.
-
- Subclasses rarely override this method.
- """
- self.preAction(action)
- getattr(self, action)()
- self.postAction(action)
-
- def actions(self):
- """
- Returns a list of method names that are allowable
- actions from HTML forms. The default implementation
- returns []. See `_respond` for more about actions.
- """
-
- return []
-
def preAction(self, actionName):
"""
- Invoked by self prior to invoking a action method. The
- implementation basically writes everything up to but
- not including the body tag. Subclasses may override
- to customize and may or may not invoke super as they
- see fit. The `actionName` is passed to this method,
- although it seems a generally bad idea to rely on
- this. However, it's still provided just in case you
- need that hook.
+ For a page, we first writeDocType(), <html>, and then
+ writeHead().
"""
- ## 2003-03 ib @@: Whyzit a bad idea to rely on it?
self.writeDocType()
self.writeln('<html>')
self.writeHead()
def postAction(self, actionName):
"""
- Invoked by self after invoking a action method. The
- implementation basically writes everything after the
- close of the body tag (in other words, just the
- ``</html>`` tag). Subclasses may override to
- customize and may or may not invoke super as they see
- fit. The `actionName` is passed to this method,
- although it seems a generally bad idea to rely on
- this. However, it's still provided just in case you
- need that hook.
+ Simply close the html tag (</html>).
"""
self.writeln('</html>')
- def methodNameForAction(self, name):
- """
- This method exists only to support "old style" actions
- from WebKit 0.5.x and below.
- Invoked by _respond() to determine the method name for
- a given action name (which usually comes from an HTML
- submit button in a form). This implementation simple
- returns the name. Subclasses could "filter" the name
- by altering it or looking it up in a
- dictionary. Subclasses should override this method
- when action names don't match their method names.
- """
- ## 2003-03 ib @@: old-style actions are gone, so this
- ## method should go too.
- return name
-
- """
- **Convenience Methods**
- """
+ ## Convenience Methods ##
def htmlEncode(self, s):
"""
@@ -434,168 +230,3 @@
HTML entities.
"""
return Funcs.htmlDecode(s)
-
- def urlEncode(self, s):
- """
- Alias for `WebUtils.Funcs.urlEncode`. Quotes special
- characters using the % substitutions.
- """
- ## @@: urllib.quote, or
- return Funcs.urlEncode(s)
-
- def urlDecode(self, s):
- """
- Alias for `WebUtils.Funcs.urlDecode`. Turns special
- % characters into actual characters.
- """
- return Funcs.urlDecode(s)
-
- def forward(self, URL, context=None):
- """
- Forwards this request to another servlet. See
- `Application.forward` for details. The main
- difference is that here you don't have to pass in the
- transaction as the first argument.
- """
- self.application().forward(self.transaction(), URL, context)
-
- def includeURL(self, URL, context=None):
- """
- Includes the response of another servlet in the
- current servlet's response. See
- `Application.includeURL` for details. The main
- difference is that here you don't have to pass in the
- transaction as the first argument.
- """
- self.application().includeURL(self.transaction(), URL, context)
-
- def callMethodOfServlet(self, URL, method, *args, **kwargs):
- """
- Call a method of another servlet. See
- `Application.callMethodOfServlet` for details. The
- main difference is that here you don't have to pass in
- the transaction as the first argument.
-
- A `context` keyword argument is also possible, even
- though it isn't present in the method signature.
- """
- return apply(self.application().callMethodOfServlet,
(self.transaction(), URL, method) + args, kwargs)
-
- def endResponse(self):
- """
- When this method is called during `awake` or
- `respond`, servlet processing will end immediately,
- and the accumulated response will be sent.
-
- Note that `sleep` will still be called, providing a
- chance to clean up or free any resources.
- """
- raise EndResponse
-
- def sendRedirectAndEnd(self, url):
- """
- Sends a redirect back to the client and ends the
- response. This is a very popular pattern.
- """
- self.response().sendRedirect(url)
- self.endResponse()
-
- """
- **Utility**
- """
-
- def sessionEncode(self, url=None):
- """
- Utility function to access `Session.sessionEncode`.
- Takes a url and adds the session ID as a parameter.
- This is for cases where you don't know if the client
- will accepts cookies.
- """
- if url == None:
- url = self.request().uri()
- return self.session().sessionEncode(url)
-
-
- """
- **Private Utility**
- """
-
- def _actionSet(self):
- """ Returns a dictionary whose keys are the names
- returned by actions(). The dictionary is used for a
- quick set-membership-test in self._respond. Subclasses
- don't generally override this method or invoke it. """
- if not hasattr(self, '_actionDict'):
- self._actionDict = {}
- for action in self.actions():
- self._actionDict[action] = 1
- return self._actionDict
-
-
- """
- **Validate HTML output** (developer debugging)
- """
-
- def validateHTML(self, closingTags='</body></html>'):
- """
- Validate the current response data using Web Design
- Group's HTML validator available at
- http://www.htmlhelp.com/tools/validator/
-
- Make sure you install the offline validator (called
- ``validate``) which can be called from the command-line.
- The ``validate`` script must be in your path.
-
- Add this method to your SitePage (the servlet from
- which all your servlets inherit), override
- Page.writeBodyParts() in your SitePage like so::
-
- def writeBodyParts(self):
- Page.writeBodyParts()
- self.validateHTML()
-
- The ``closingtags`` param is a string which is appended
- to the page before validation. Typically, it would be
- the string ``</body></html>``. At the point this method
- is called (e.g. from `writeBodyParts`) the page is not
- yet 100% complete, so we have to fake it.
- """
-
- # don't bother validating if the servlet has redirected
- status = self.response().header('status', None)
- if status and status.find('Redirect') != -1:
- return
-
- response = self.response().rawResponse()
- contents = response['contents'] + closingTags
- from WebUtils import WDGValidator
- errorText = WDGValidator.validateHTML(contents)
- if not errorText:
- return
- self.write(errorText)
-
- """
- **Exception Reports**
- """
-
- def writeExceptionReport(self, handler):
- """
- Writes extra information to the exception report.
- The `handler` argument is the exception handler, and
- information is written there (using `writeTitle`,
- `write`, and `writeln`). This information is added
- to the exception report.
-
- See `WebKit.ExceptionHandler` for more information.
- """
- handler.writeTitle(self.__class__.__name__)
- handler.writeln('''Servlets can provide debugging information here by
overriding writeExceptionReport().<br>For example:
-<pre> exceptionReportAttrs = 'foo bar baz'.split()
- def writeExceptionReport(self, handler):
- handler.writeTitle(self.__class__.__name__)
- handler.writeAttrs(self, self.exceptionReportAttrs)
- handler.write('any string')
-</pre>
-
-See WebKit/ExceptionHandler.py for more information.
-''')