quetz-mod_python-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Graham Dumpleton" <grah...@dscpl.com.au>
Subject Issues with "Session use with classes" wiki example.
Date Tue, 05 Dec 2006 02:45:07 GMT
Some more details on issues I brought up before about session example.

BTW, in respect of what version of mod_python we write examples for, my
preference would be that we target mod_python 3.3 and then as followup by way
of embedded notes, footnotes or subpages, indicate how it may have to be
changed to work with older versions or whether it will even work with older
versions. By targeting 3.3, I see it as subtle encouragement for people to
upgrade by making it obvious that if they are using a quite old version, that
they aren't going to be getting too much support going forward as they have
been passed by.

More comments below.

Graham Dumpleton wrote ..
> Sorry, but I am going to take issue with where this session example is
> headed
> as there are number of things being done which I at least would regard
> as being
> bad practice. Please don't take this wrong way, your effort in doing this
> stuff in
> the first place is much appreciated and I hope I don't dissuade you from
> continuing. It is just a matter of using our collective minds to ensure
> that
> overall the example is robust and secure.
> 
> I'll quickly mention the issues I see and when I get a chance and if no
> one else
> has piped up and explained it more, I respond in more detail.
> 
> The first as I mentioned once before is that you are still using MemorySession
> as the session type which means this example will only work for people
> on
> Windows and will not always work for people using prefork/worker on UNIX
> platforms.

It will also not allow sharing of sessions between interpreter instances on
Windows either. If you are going to stick with using MemorySession then all
these caveats have to be explained, but in doing that, it becomes more than
just a simple example which defeats the purpose. Thus, still prefer not using
PythonOption in a simple example and trust that mod_python makes the
sensible choice for the platform being used.

> The second is that it promotes a bad practice of having functions accessible
> under multiple URLs thereby exacerbating a problem with mod_python.publisher
> related to trailing slashes and determining relative URLs. Ie., the following
> both
> map to the same thing.
> 
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo/foo

A previous post where I have mentioned this issue is:

  http://www.modpython.org/pipermail/mod_python/2006-March/020501.html
  http://www.modpython.org/pipermail/mod_python/2006-November/022474.html

The simple way of avoiding this in this example is to made 'foo' be '_foo' so it
can't be accessed direct. A better way though would be not to use a class at
all and instead have the example use a basic function. There is no reason to use
a class and it just makes the example overly complicated and may make users
think they have to wrap up their handlers in classes when they don't have to.

Note though that doing this still will not eliminate all the trailing slash problems
as some are caused by shortcomings in mod_python.publisher that can't
practically be fixed as doing so will break a lot of existing code more than likely.

> The third is that in general it is exposing all sorts of private stuff
> which it should
> not.
> 
>   http://your.domain.com/ExampleSession/ExampleSession.py/tags
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo/t
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo/session
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo/session/....

Any private data should have a leading underscore so it can't be accessed.
This includes member data of class instances which can be browsed.

> The fourth is that it isn't thread safe and will fail with a multithread
> MPM.

The example uses a single instance of a class as the request handler, but then
caches information as member attributes of that single instance to allow
subsequent functions called to get access to them. This can stuff up badly
as if there are concurrent threads executing the same handler, a second
request can override the member attributes before the first gets to access
them and use them.

This is another reason why as a simple example, a class should not be involved
and a simple function used instead. If data needs to be held somewhere so
that other functions can access it, it should be stored in the request object
and the request object passed around between functions explicitly.

The only other safe way of doing it is to create an instance of the class for
every request. For example something like:

  class Instance:

    def __init__(self, type, *args, **kwargs):
        self.__type = type
        self.__args = args
        self.__kwargs = kwargs

    def __call__(self, req):
        assert(type(self.__type) in [types.ClassType, types.TypeType])
        target = self.__type(*self,__args,**self.__kwargs)
        return util.fs_apply_data(target, req.form)

  foo = Instance(Bar, tags)

> The fifth is that it caches the session object and thus holds a reference
> to
> the request object beyond the actual life time of the request. With the
> exposure
> of private stuff above, my guess is I could crash the web server by using
> the
> following URL after first request has been done.
> 
>   http://your.domain.com/ExampleSession/ExampleSession.py/foo/session/make_cookie

If the session is stored as req.session and req passed around, this wouldn't
be an issue. Creating an instance of the class per request would also avoid the
issue.

> The sixth is that I believe we should be persuading users to not be doing
> session creation stuff inside of individual publisher functions as they
> then
> tend to duplicate the same code everywhere. It might be better to show
> them
> how to use stacked handlers to have the session object created even before
> publisher gets invoked, or at least show them the convention of storing
> the
> session object as req.session and always checking for its existence there
> before creating it, so as to avoid deadlocks. That or at least hive the
> session
> creation into a common function they call.

The first option here is to use:

  PythonHandler _sessions
  PythonHandler mod_python.publisher

In _sessions.py have:

  def handler(req):
    if not hasattr(req, 'session'):
      req.session = Session.Session(req)
    return apache.OK

Any publisher function can then assume that the session already exists and just
access it as req.session. This code file has a leading underscore in its name so
that publisher will ignore it when it is stored in the document tree.

Next option is to use a wrapper class and you wrap every function that wants to
be able to use sessions:

  class SessionEnabled:
    def __init__(self, target):
      self.__target = target
    def __call__(self):
      return util.apply_fs_data(self.__target, req.form)

  def _function(req, a, b):
    return a,b

  function = SessionEnabled(_function)

If your version of Python supports decorators, you could write code to turn
SessionEnabled into a decorator and you could instead say something like:

  @session_enabled
  def function(req, a, b):
    return a.b

If one was using mod_python 3.3, especially if one was using forms based login,
it would be much better to use an authenhandler. This is a more complicated topic
though.

Graham

> More later.
> 
> Graham
> 
> Apache Wiki wrote ..
> > Dear Wiki user,
> > 
> > You have subscribed to a wiki page or wiki category on "Mod_python Wiki"
> > for change notification.
> > 
> > The following page has been changed by MartinStoufer:
> > http://wiki.apache.org/mod_python/Session_use_with_classes
> > 
> > The comment on the change is:
> > Added working example
> > 
> > ------------------------------------------------------------------------------
> >   
> >   Once you understand this framework, you can then add your own content.
> >   
> > - First, lets augment a directory element in the ''httpd.conf'' to enable
> > both the Publisher model and session support.
> > + The nominal URL you will be using is of the following form:
> > + {{{
> > + http://your.domain.com/ExampleSession.py/foo
> > + }}}
> >   
> > + If you want to enforce and/or simplify the type of backing store for
> > sessions running under mod_python, you can augment the directory element
> > in the ''httpd.conf''. We also enable the Publisher model here.
> >   {{{
> >   <Directory /usr/local/apache2/htdocs/DomProdLib>
> >       AddHandler mod_python .py
> > @@ -22, +26 @@
> > 
> >   
> >   Here we see the '''[http://modpython.org/live/current/doc-html/dir-handlers-ph.html
> > PythonHandler]''' using the publisher object. Utilizing and extending
> this
> > is described in [http://modpython.org/live/current/doc-html/hand-pub.html
> > Section 6.1] of the mod_python documentation.
> >   
> > - We also see a new '''[http://modpython.org/live/current/doc-html/dir-other-po.html
> > PythonOption]''' added calling for the ''session'' option to be set with
> > the ''[http://modpython.org/live/current/doc-html/pyapi-sess.html MemorySession]''
> > value. Other values are available and are defined in  [http://modpython.org/live/current/doc-html/pyapi-sess.html
> > Section 4.5] of the mod_python documentation. This is not strictly required
> > as a default one will be selected for you by mod_python/Apache when your
> > session based application is run.
> > + We also see our new '''[http://modpython.org/live/current/doc-html/dir-other-po.html
> > PythonOption]''' added calling for the ''session'' option to be set with
> > the ''[http://modpython.org/live/current/doc-html/pyapi-sess.html MemorySession]''
> > value. Other values are available and are defined in  [http://modpython.org/live/current/doc-html/pyapi-sess.html
> > Section 4.5] of the mod_python documentation. 
> > + 
> > + If you choose not to constrain your mod_python applications to all
> use
> > the same session, you have two programattic options.  
> > + 
> > +  Use the default selected for you by mod_python/Apache when your session
> > based application is run:: Save this file as ''Example``Session.py''
> {{{#!python
> > + from mod_python import apache, Session
> > + 
> > + tags = (1,2,3,4)
> > + 
> > + class Bar:
> > +    def __call__(self, req):
> > +        self.session = Session.Session(req)
> > +        if self.session.is_new():
> > +           self.t = self.session.created()
> > +        return self.foo()
> > + 
> > +    def __init__(self, *args):
> > +        # Do some important initializing here
> > +        pass
> > + 
> > +    def foo(self):
> > +        delta = int(self.session.last_accessed()-self.t)
> > +        try:
> > +            self.session['hits'] += 1
> > +        except:
> > +            self.session['hits'] = 1
> > + 
> > +        self.session.save()
> > +        return """Session ID: %s
> > + This session has been active for %d seconds
> > + This session has been accessed %d times""" % (self.session.id(), delta,
> > self.session['hits'])
> > + 
> > + foo=Bar(tags)
> > + }}}
> > +  
> > +  Instantiate a derived Session class:: {{{Bar}}}
> > + 
> > + One major difference you'll notice is that the `__init__` isn't used
> > directly by mod_python. You can of course initialize internals here from
> > global values, but it isn't until the `__call__` is invoked that the
> ''req''
> > object is passed in and gives us the hook we need to process the request.
> >   
> >   === The rest is forthcoming in short order ===
> >   ---- 

Mime
View raw message