Pages: << 1 ... 5 6 7 8 9 10 11 12 13 14 15 ... 26 >>


Permalink 11:37:08 pm, by fumanchu Email , 226 words   English (US)
Categories: Dejavu

Dejavu 1.4 now in beta

After more than a year since 1.3 was released, I'm just about ready to officially release Dejavu 1.4! In addition to bugfixes, there are some major new features:

  • Sandbox.recall now returns a list (use xrecall to get an iterator).
  • Associations are now aware of whether they are to-one or to-many.
  • logic.Expressions can now take multiple positional arguments (so you can test multiple Units at once).
  • Improved multirecall, including full support for INNER and OUTER JOINs for all Storage Managers. Since the signatures for recall and multirecall now align, the "multirecall" name has been dropped; just call Sandbox.recall(classes, expr) whether you're querying a single class or multiple ones.
  • Units may now have arbitrary identifiers (primary keys).
  • Unit Properties have a new "default" attribute.
  • Simple inheritance is now supported; recalling one class will also recall its subclasses.
  • New Sandbox "magic recaller" methods, like inv = box.Invoice(13).
  • New Sandbox.view method, to retrieve persisted data without creating full Units.
  • A new Schema class to help manage changes to your model, and helper methods to sync database schemas.
  • New logging support.
  • A new test runner.
  • Python 2.4 fixes for codewalk, the test suite, and fixedpoint.

As you can see, a year's worth of work. ;) Feel free to kick the tires on all the new stuff. I should bless a release candidate in early January.


Permalink 02:08:17 pm, by fumanchu Email , 338 words   English (US)
Categories: Dejavu

Dejavu is adding schema versioning

I just dumped a first crack at a Schema class on the trunk. Test code is here (search for 'schema'), docs are here. I haven't written anything like this before, so if anyone has recommendations or warnings about the direction it's heading, now is the time to speak up (before 1.4 is officially released ;) )!

Basic design: there's a dejavu.Schema class which your app can subclass. Whenever you need to change the underlying database (or other persistence mechanism) schema of your app, you write a new upgrade_to_X method, where X is an incrementing version number. Each such method contains the commands which will upgrade an installation from (X - 1) to X.

At runtime, you call MySchema.upgrade(), and each deployment will run any upgrade_to_X methods that it hasn't yet run, in order. The "currently deployed version" number is stored in a magic DeployedVersion Unit.

The upgrade_to_X methods can choose to stay database-neutral and just use the (new) arena.add_property, drop_property, and rename_property methods. But because each Schema is application-specific, you can also write optimized instructions for your known StorageManagers. For example, say you need to change an int property to a string. The "database-neutral" way would be to have additional Arena methods for such tasks. Some of those methods may be added in the future, but nothing's stopping you now from writing non-portable SQL statements if you know your app is only deployed on, say, Postgres (but you should probably assert that before you execute the SQL statements).

Anyway, I'd be interested to hear from anyone else who has written database-versioning tools. Save me from a pitfall if you can. :) Have fun with the new Schema class and let's see if there are a couple of other common methods (like add_column) that should go into the Arena and the StorageManagers.


Permalink 04:34:13 pm, by fumanchu Email , 99 words   English (US)
Categories: General

Which super hero am I?

Do you need to ask?

You scored as Batman, the Dark Knight. As the Dark Knight of Gotham, Batman is a vigilante who deals out his own brand of justice to the criminals and corrupt of the city. He follows his own code and is often misunderstood. He has few friends or allies, but finds comfort in his cause.

Batman, the Dark Knight


Lara Croft


El Zorro


The Amazing Spider-Man


Neo, the "One"


Captain Jack Sparrow


James Bond, Agent 007




Indiana Jones


The Terminator


William Wallace


Which Action Hero Would You Be? v. 2.0
created with


Permalink 11:23:32 am, by fumanchu Email , 103 words   English (US)
Categories: WSGI

WSGI gateway for mod_python status fix

After much woe, I think I finally tracked down the status problems I was having with (which is now available on my "misc" Trac site). It should now correctly handle redirects, 404's, and .css and .js content. I think it also fixed my earlier "delayed content" problem.

I hereby nominate mod_python's status API for the "One Obvious Way To Do It" booby prize. Having req.status, a return value/status, and the option to raise a status makes far too many combinations.


Permalink 11:42:58 am, by fumanchu Email , 695 words   English (US)
Categories: CherryPy

What will CherryPy 3 look like?

The correct answer is: "nobody knows". But here are some ideas I've been kicking around the ol' cranium lately...

[09:32] *** now talking in #cherrypy
[10:22] <Lawouach> where to start
[10:22] <Lawouach> what's your basic idea toward 3.0?
[10:22] <@fumanchu> oh, I have so many ;)
[10:22] <Lawouach> lol
[10:22] <Lawouach> say big general ones :)
[10:22] <Lawouach> not details per se
[10:23] <@fumanchu> 1) make CP have a kick-butt,
    non-CP-specific toolkit (lib/httptools), that is SO
    good that Quixote, Django, et al can't *help* but
    decide to use it instead of their own server processes
[10:24] <@fumanchu> even if they don't like the way CP
    maps handlers to URL's, for example
[10:24] <@fumanchu> they should be able to build a
    server with the behavior they like out of lib/httptools
[10:25] <Lawouach> we want to be lib that rule them all :)
[10:25] <@fumanchu> yup
[10:26] <Lawouach> i agree as long as we don't become a
    framework on our own, but i already know it's not what
    you intend :)
[10:26] <@fumanchu> right
[10:26] <@fumanchu> it's an anti-framework approach
[10:26] <@fumanchu> we make writing-a-web-framework
    into a weekend's work
[10:27] <@fumanchu> take some from column A; try all of column B
[10:27] <Lawouach> do you want to stay very low-level
    (aka HTTP wrapper level) or make it a bit higher level
    and provide functions such as the bast_match() we were
    talking about last week?
[10:27] <@fumanchu> best_match would be fine as long
    as it doesn't depend upon cherrypy
[10:28] <Lawouach> right, this was a bad example
[10:28] <Lawouach> but basically where httptools should stop?
[10:28] <@fumanchu> I think that can be open-ended
[10:28] <Lawouach> i think we should keep the level
    you've been doing till now
[10:29] <@fumanchu> 2) then, by pulling a ton of code
    out of _cphttptools (putting it in lib/httptools instead),
    I want to see if we can get the Request and Response
    objects down to a tiny size
[10:34] <@fumanchu> the trunk version of _cphttptools
    is already 60% of its 2.1 size
[10:35] <Lawouach> right. hmmm
[10:37] <@fumanchu> and a *lot* of what's left is very OO
[10:38] <@fumanchu> so, one idea I'm toying with: allow
    developers to use their own subclasses of Request
    and Response
[10:40] <@fumanchu> if we make it super-easy to use custom
    Request subclasses, then they will want to start
[10:40] <@fumanchu> take out the filter logic, and becomes:
def _run(self, requestLine, headers, rfile):

    self.headers = list(headers)
    self.headerMap = httptools.HeaderMap()
    self.simpleCookie = Cookie.SimpleCookie()
    self.rfile = rfile

    except cherrypy.RequestHandled:
    except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
[10:40] <Lawouach> regarding the subclassing of request
    and response, i'm know that it could interest very
    much the guys behind itools
[10:40] <@fumanchu> yes
[10:40] <@fumanchu> and Ben Bangert (routes)
[10:41] <@fumanchu> anyway, if is *that* simple,
    then who needs filters?
[10:41] <@fumanchu> just code them procedurally into your method
[10:43] <@fumanchu> looking over the filters that are built in...
[10:44] <@fumanchu> I think that half could be done just as
    easily as lib/httptools functions
[10:44] <@fumanchu> and half could be "always on"
[10:44] <@fumanchu> (if we continue to improve them, like
    encodingfilter, to meet the HTP spec)
[10:44] <@fumanchu> HTTP
[10:44] <Lawouach> that's my white cheap :) (i don't think this
    expression exists so i make it up!)
[10:45] <Lawouach> i really want CP to be HTTP conditionnaly compliant
    at least :)
[10:45] <Lawouach> and maybe in CP 4.0 to be unconditionnaly compliant!
[10:45] <Lawouach> :p
[10:45] <@fumanchu> I completely agree
[10:46] <@fumanchu> anyway, I want to stress that I'm still playing
    with these ideas
[10:46] <@fumanchu> nothing's set in stone
[10:47] <Lawouach> since you've be proposing them a while back,
    i've been a great fan of them
[10:47] <@fumanchu> and trying to implement them will turn up
    lots of problems, I'm sure
[10:47] <@fumanchu> oh, well thanks
[10:47] <Lawouach> that's why i don't have so many different
    things to bring for cp 3.0
[10:51] <@fumanchu> one of the nice things about these ideas
    for 3.0 is that the bulk of the work can be done within
    the 2.x branch


Permalink 09:27:43 pm, by fumanchu Email , 302 words   English (US)
Categories: Python

Oh so very stumped

Dear lazyweb,

After 6 hours, I am utterly stumped. I've got an application built with a popular Python web application server, via mod_python, and keep seeing data bleed from one request to the next. That is, if I:

  1. Request a page that has a css <link>,
  2. Request a non-existent jjj.css file (resulting in a 404), and then
  3. Request a different, non-existent mmm.css file (another 404), I see this in the third window:


The requested URL /jjj.css was not found on this server.

Apache/2.0.55 (Win32) mod_ssl/2.0.55 OpenSSL/0.9.8a mod_python/3.2.2b Python/2.4.2 mod_auth_sspi/1.0.2 Server at Port 443
HTTP/1.1 404 Not Found Date: Tue, 22 Nov 2005 01:57:37 GMT Server: Apache/2.0.55 (Win32) mod_ssl/2.0.55 OpenSSL/0.9.8a mod_python/3.2.2b Python/2.4.2 mod_auth_sspi/1.0.2 Content-Length: 371 Keep-Alive: timeout=15, max=94 Connection: Keep-Alive Content-Type: text/html; charset=iso-8859-1

Not Found

The requested URL /mmm.css was not found on this server.

Apache/2.0.55 (Win32) mod_ssl/2.0.55 OpenSSL/0.9.8a mod_python/3.2.2b Python/2.4.2 mod_auth_sspi/1.0.2 Server at Port 443

The body of request #2 is present in request #3, and so are the headers of request #3! Frightening.

This happens reliably with both Firefox and IE. It happens whether I use HTTPS or not. It happens whether I use authentication or not. It happens when I strip the modpython gateway-for-WSGI I wrote down to 80 lines.

It stops happening when I use CherryPy's builtin WSGI server, so I don't think any part of CP is to blame, which leaves a bug in mod_python or Apache2. I'm particularly inclined to blame them because, although CherryPy and Apache itself log both the missing responses as 404, Ethereal shows me that the actual third response, as received by the client, has a 200 response code!

So I'm stumped. Any solutions, pointers, or flights of debugging fantasy accepted.


Permalink 04:28:17 pm, by fumanchu Email , 309 words   English (US)
Categories: General

Reacting to business challenges

James Robertson (among others) has been following the Sony rootkit fiasco, and comments:

Some of the management meetings at Sony must have been utterly fascinating over the last few days, as they slowly worked their way around to doing the right thing.

I can't help but wonder how my own company's management would respond to a similar challenge. My guess is that we would have a similar set of reactions. That is, we would choose the following reactions, in order:

  1. Try to ignore or deny the problem, for reasons of expediency—we have more pressing matters to deal with, mostly involving keeping ourselves afloat long enough to reach our visionary goals.
  2. When (if?) the outcry reaches an unacceptable level, decide on a quick fix.
  3. When (if?) the quick fix has been roundly trashed as too quick, decide to implement what was "the right fix all along".
  4. When (if?) the outcry has spilled into other business, try to assuage fears with verbal promises, not new policies or visible proof.

I imagine that each step was instituted by a progressively-more-senior level of management. It's hard to imagine a company with more than 5 employees doing things any differently; there are simply too many such challenges (and too many "if's")—a company which discussed, implemented, and guaranteed a full fix for all of them would quickly smother itself in bureaucracy and second-guessing. In other words, my hunch is that Sony's error was probably systemic (the result of being a large company) and not moral.

Perhaps some issues, like Sony's rootkit issue, should side-step the above sequence and jump their response straight to 4th gear. I'd be interested to hear anyone's logic for deciding which issues need that and which ones don't.


Permalink 01:10:20 am, by fumanchu Email , 39 words   English (US)
Categories: General

Sorry about the recent outages

These blogs have been getting hammered by some unknown process; Apache and MySQL start taking up all my RAM. It's mostly "unknown" because I can't be bothered to fix it at the moment—too much else going on.


Permalink 01:18:00 am, by fumanchu Email , 879 words   English (US)
Categories: Python, WSGI

WSGI wrapper for mod_python

Update Nov 6, 2005: Finally got it to work with Apache2-prefork on Unix (it only worked on mpm_winnt until now).

Update Oct 25, 2005: I was having a problem setting up a new install of my CherryPy application, using this recipe. It turned out that I didn't have the right interpreter_name in my PythonImport directive:

PythonImport module interpreter_name

Therefore, the CherryPy server started in a different intepreter than the one being used for the requests. It must exactly match the value of req.interpreter, and is case-sensitive. I've updated the code with comments to that effect (just to have it all in one place).

Update Aug 11, 2005: I was having a problem serving .css and .js pages. CherryPy's standalone WSGI server did fine, but mod_python did not. I finally tracked it down to the fact that I was both setting apache's req.status and returning req.status from the handler. Funky. It worked when I chose to simply return the value, and not set it.

Update June 5, 2005:

  1. Pages take forever to terminate when returning a status of 200--apache.OK must be returned instead in that case.

  2. Added code for hotshot profiling.

  3. Added code for using paste.lint

As I mentioned I was doing last week, I wrote a more complete WSGI wrapper for modpython. Here it is. Feedback welcome. Phil Eby told me he'd like a mod_python wrapper for inclusion in wsgiref; he should feel free to use this one however he sees fit. ;)

WSGI wrapper for mod_python. Requires Python 2.2 or greater.

Example httpd.conf section for a CherryPy app called "mcontrol":

<Directory D:\htdocs\mcontrol>
    SetHandler python-program
    PythonHandler wsgiref.modpy_wrapper::handler
    PythonOption application cherrypy.wsgiapp::wsgiApp
    PythonOption import mcontrol.cherry::startup


import sys
from mod_python import apache
from wsgiref.handlers import BaseCGIHandler

class InputWrapper(object):
    def __init__(self, req):
        self.req = req
    def close(self):
    def read(self, size=-1):
    def readline(self):
        return self.req.readline()
    def readlines(self, hint=-1):
        return self.req.readlines(hint)
    def __iter__(self):
        line = self.readline()
        while line:
            yield line
            # Notice this won't prefetch the next line; it only
            # gets called if the generator is resumed.
            line = self.readline()

class ErrorWrapper(object):
    def __init__(self, req):
        self.req = req
    def flush(self):
    def write(self, msg):
    def writelines(self, seq):

bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', "
             "when running a version of mod_python < 3.1")

class Handler(BaseCGIHandler):
    def __init__(self, req):
        options = req.get_options()
        # Threading and forking
            q = apache.mpm_query
        except AttributeError:
            threaded = options.get('multithread', '').lower()
            if threaded == 'on':
                threaded = True
            elif threaded == 'off':
                threaded = False
                raise ValueError(bad_value % "multithread")
            forked = options.get('multiprocess', '').lower()
            if forked == 'on':
                forked = True
            elif forked == 'off':
                forked = False
                raise ValueError(bad_value % "multiprocess")
            threaded = q(apache.AP_MPMQ_IS_THREADED)
            forked = q(apache.AP_MPMQ_IS_FORKED)
        env = dict(apache.build_cgi_env(req))
        if req.headers_in.has_key("authorization"):
            env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"]
        self.request = req
        self._write = req.write
    def _flush(self):
    def send_headers(self):
        self.headers_sent = True
        # Can't just return 200 or the page will hang until timeout
        s = int(self.status[:3])
        if s == 200:
            self.finalstatus = apache.OK
            self.finalstatus = s
        # the headers.Headers class doesn't have an iteritems method...
        for key, val in self.headers.items():
            if key.lower() == 'content-length':
                if val is not None:
            elif key.lower() == 'content-type':
                self.request.content_type = val
                self.request.headers_out[key] = val

_counter = 0

def profile(req):
    # Call this function instead of handler
    # to get profiling data for each call.
    import hotshot, os.path
    ppath = os.path.dirname(__file__)
    if not os.path.exists(ppath):
    global _counter
    _counter += 1
    ppath = os.path.join(ppath, "" % _counter)
    prof = hotshot.Profile(ppath)
    result = prof.runcall(handler, req)
    return result

def handler(req):
    config = req.get_config()
    debug = int(config.get("PythonDebug", 0))
    options = req.get_options()
    # Because PythonImport cannot be specified per Directory or Location,
    # take any 'import' PythonOption's and import them. If a function name
    # in that module is provided (after the "::"), it will be called with
    # the request as an argument. The module and function, if any, should
    # be re-entrant (i.e., handle multiple threads), and, since they will
    # be called per request, must be designed to run setup code only on the
    # first request (a global 'first_request' flag is usually enough).
    import_opt = options.get('import')
    if import_opt:
        atoms = import_opt.split('::', 1)
        modname = atoms.pop(0)
        module = __import__(modname, globals(), locals(), [''])
        if atoms:
            func = getattr(module, atoms[0])
    # Import the wsgi 'application' callable and pass it to
    modname, objname = options['application'].split('::', 1)
    module = __import__(modname, globals(), locals(), [''])
    app = getattr(module, objname)
    h = Handler(req)
##    from paste import lint
##    app = lint.middleware(app)
    # finalstatus was set in Handler.send_headers()
    return h.finalstatus


Permalink 01:08:47 am, by fumanchu Email , 1452 words   English (US)
Categories: CherryPy

URL-rewriting in CherryPy 2.1

There are a lot of reasons, and places, why a developer would want an original Request-URI to be treated as if it were another. CherryPy 2.1.0 has a (possibly bewildering) array of attributes, core code, and filters which either enable rewriting or are affected by it. Here's how I see the state of the art (this is not gospel--much is my opinion regarding design intent).


First, some features which depend on rewriting:

  1. Generating URL's to spit back out in HTML.
  2. HTTP Redirects and their targets (new URL).
  3. Handler dispatch: mapping URI's to handler methods. Includes...
  4. Arbitrary mount points: allowing a deployer to mount an application at an arbitrary base URI.
  5. Config lookups, since the config map is keyed by URI (path only--no queryString or fragment).
  6. Logging: do you log the original URI or the rewritten one, or both? in different logs? in all messages?

Request Attributes

Now, cherrypy.request has the following attributes (grabbed straight from the book):

  • requestLine: This attribute is a string containing the first line of the raw HTTP request; for example, "GET /path/page HTTP/1.1".
  • method: This attribute is a string containing the HTTP request method, such as GET or POST.
  • path: This attribute is a string containing the path of the resource the client requested.
  • queryString: This attribute is a string containing the query string of the request (the part of the URL following '?').
  • protocol: This attribute is a string containing the HTTP protocol of the request in the form of HTTP/x.x

Let's take an example HTTP requestLine and see if we can't parse it out:

     DELETE /path/to/handler/?param=somevalue HTTP/1.1
     \____/ \_______________/ \_____________/ \______/
     method       path          queryString   protocol

Pretty straightforward; no overlaps. Note that if the Request-URI includes a scheme and host, that'll be stripped when path is formed.

There are a couple of other URI-related request attributes:

  • base: This attribute is a string containing the root URL of the server. By default, it is equal to scheme://headerMap['Host'].
  • browserUrl: This attribute is a string containing the URL the client requested. By default, it is equal to base + path, plus the queryString, if provided.

Since the requestLine doesn't always include the scheme or host (it may, rarely), these are obtained from other sources and joined into base. The browserUrl joins the base, the path, and the queryString to form a complete, absolute URI (what was hopefully in the Address bar of the end-user's web browser, if that's applicable).

Finally, we have these copies/substitutes for the functionality provided by path:

  • objectPath: This attribute is a string containing the path of the exposed method that will be called to handle this request. This is usually the same as cherrypy.request.path, but can be changed in a filter to change which method is actually called.
  • originalPath: This attribute is a string containing the original value of cherrypy.request.path, in case it is modified by a filter during the request.

The objectPath may be used to control dispatching, but there's nothing in the core that uses it that way. Since it's almost always None, dispatching usually falls back to the value of path. Once the handler dispatch is completed, then objectPath contains the route to the found handler, expressed as a path; in the above example, it might be "/path/to/handler/index" if an "index" function handles the request.

The originalPath is also an odd attribute. You would think that CherryPy core features, especially those which use or implement URI rewriting, would make use of this value. But none of them do. It gets set but never used.

How to rewrite in 2.1

Rewriting "base"

This is what the builtin baseUrlFilter does, so that an instance of CherryPy running behind Apache with mod_proxy or mod_rewrite can spit back out proper URI's in HTML, redirects, etc. As far as I can tell, this works well and has no issues with the rest of CherryPy. The only other value which overlaps with the value of base is browserUrl, which the filter also rewrites.

Rewriting "path"

Another way to rewrite is to use a filter that changes the value of path for you as early as possible. For example, I use a VirtualPathFilter which does this:

class VirtualPathFilter(object):
    """Filter that changes cherrypy.request.path, stripping a set prefix."""

    def onStartResource(self):
        if cherrypy.config.get('virtualPathFilter.on', False):
            prefix = cherrypy.config.get('virtualPathFilter.prefix', '')
            if prefix:
                path = cherrypy.request.path
                if path == prefix:
                    path = '/'
                elif path.startswith(prefix):
                    path = path[len(prefix):]
                cherrypy.request.path = path

This allows me to provide feature #4, arbitrary mount points. I write my application as if it were always mounted at /, but the deployer can then provide a virtualPathFilter.prefix to turn the URL /prefix/page?id=3 into /page?id=3.

Unfortunately, if the other pieces of CherryPy aren't written to support arbitrary mount points, then this scheme falls apart. And they aren't so written. I've just broken many of our other features:

  1. Generating URL's to spit back out in HTML. Broken. I now have to manually provide prefix to my HTML templates, or take on the nightmare of making every generated URL into a URL which is relative (e.g. "../../otherpage") to the current one.
  2. HTTP Redirects and their targets. Broken. I now have to manually provide prefix to each instance (or use relative URL's). But I can't control CherryPy's redirect instances! For example, when CherryPy tries to redirect index methods by adding a trailing slash to the requested URI, it uses the value of path, which I've rewritten.
  3. Handler dispatch: not broken.
  4. Arbitrary mount points: not broken.
  5. Config lookups. Broken? Some other filter which does a config lookup could run their onStartResource method before mine. Since my filter is user-defined, it is forced to run after all of the builtin ones; none of those currently perform config lookups, however. If any of the server.* config entries are specified somewhere other than "global", then we have the same issue. Finally, what's to stop a future CP developer from adding more such problems (as they fix other bugs)?
  6. Logging: the error.log and access.log will both use the original URI (from requestLine). Broken? or not? One? Both?

Rewriting "objectPath"

An alternative to rewriting the path is to use a filter that changes the value of objectPath instead, before the handler is looked up and called. For example, we could change VirtualPathFilter to do this instead:

class VirtualPathFilter(object):
    """Filter that changes cherrypy.request.objectPath, stripping a set prefix."""

    def beforeMain(self):
        if cherrypy.config.get('virtualPathFilter.on', False):
            prefix = cherrypy.config.get('virtualPathFilter.prefix', '')
            if prefix:
                path = cherrypy.request.path
                if path == prefix:
                    path = '/'
                elif path.startswith(prefix):
                    path = path[len(prefix):]
                cherrypy.request.objectPath = path

Are there any side-effects to this approach?

  1. Generating URL's to spit back out in HTML: Broken. No change from rewriting path.
  2. HTTP Redirects and their targets: Broken. No change from rewriting path.
  3. Handler dispatch: not broken.
  4. Arbitrary mount points: not broken.
  5. Config lookups. Broken. A call to config.get() defaults to using path, which we haven't rewritten, which might seem all right until you try to deploy the app: every configMap key must be rewritten to prefix the mount point, and this must be done separately for each site. Some might call this an acceptable trade-off. I don't. ;)
  6. Logging: probably not considered broken.

Recommendations for CherryPy 2.2

We need to fix rewriting path or objectPath, or both. Let's try fixing objectPath:

  1. Generating URL's to spit back out in HTML: What to do about user code? Tell them to always use relative URL's? Not acceptable, really. A rewriting filter needs some way to "unrewrite" an arbitrary path, it seems. A prefix-only rewriter could do this, for example, but not a regex-rewriter. Maybe a prefix stripped from path could just be suffixed to base?
  2. HTTP Redirects and their targets: Same issue as #1. But also make the trailing-slash hack redirect by using browserUrl instead of (objectPath or path) + queryString. Note that HTTPRedirect already uses browserUrl.
  3. Handler dispatch: not broken.
  4. Arbitrary mount points: not broken.
  5. Config lookups. Make config.get try objectPath? There's a big problem there: objectPath might grow an extra "/index" or "/default" suffix halfway through the request process. So we'd have to separate the two concepts into a "searchPath" and a "foundPath". Even if we did that, we would still have the issue that path-rewriting does (user-defined filters run late). We would have to find a way to run a rewriting filter before most (all?) others. Maybe rewriting shouldn't be a filter at all—maybe it should be part of the fixed API, if only for prefixed mount points.
  6. Logging: probably not considered broken.

Seems we have our work cut out for us.

<< 1 ... 5 6 7 8 9 10 11 12 13 14 15 ... 26 >>

March 2017
Sun Mon Tue Wed Thu Fri Sat
 << <   > >>
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  


The requested Blog doesn't exist any more!

XML Feeds