Updated 2006-04-03 19:21:37

This is an experimental approach to web application development in Tcl. The idea is simple, but seems to be very flexible - create a separate thread (or thread pools in the future) for an application and handle all commands related to an application within that thread.

Another thing which was implemented here is creating Itcl class for the application and creating an instance of the application for each session - so an "object" is bound to a client (there are some open issues with cookie-disabled browsers). This way the object can use variables to store particular data and Iapp framework handles cleanup of the objects as well as it can store some more complex Tcl data structures - like results from database queries. The actual return of the data is done with the original thread so the worker thread is mainly used for data manipulation and returning the actual HTML. The API will also allow access to query variables (this is subject to many changes at the moment).

2006-04-03 The main issue is the actual performance loss when compared to usual AOLserver approach - because of thread communication etc. A simple job of returning a static data proved that this concept is about 5 times slower - about 4000 (standard AOLserver approach) req/sec vs 800 req/sec (Iapp approach). The tests were executed on the following machine:
 vendor_id      : AuthenticAMD
 cpu family     : 15
 model          : 5
 model name     : AMD Opteron(tm) Processor 144
 stepping       : 8
 cpu MHz                : 1799.998
 cache size     : 1024 KB

Another test, where 64kB string was to be returned, resulted in 500 req/sec while 200 req/sec were possible using Iapp approach, so when the size of the content increases, the difference actually decreases.

The interesting part of this approach is the actual ease of how things can be implemented. The following is a template for doing a login/logout system:
 itcl::clas itcl::class iapp::rh::login {
    protected variable loginUserID ""
    protected variable loginUserType ""

    protected method serializeInt {} {
        set serializeData(rh::login::loginUserID) $loginUserID
        set serializeData(rh::login::loginUserType) $loginUserType
    }

    protected method deserializeInt {} {
        if {[info exists serializeData(rh::login::loginUserID)]} {
            set loginUserID $serializeData(rh::login::loginUserID)
        }  else  {
            set loginUserID ""
        }
        if {[info exists serializeData(rh::login::loginUserType)]} {
            set loginUserType $serializeData(rh::login::loginUserType)
        }  else  {
            set loginUserType ""
        }
    }

    public method handleLogin {queryprefix type} {
        upvar querydata querydata
        if {($loginUserID == "")
            &&
            ([info exists querydata(${queryprefix}login)] || [info exists querydata(${queryprefix}login.x)])
            &&
            ([info exists querydata(${queryprefix}username)] && [info exists querydata(${queryprefix}password)])
        } {
            set loginUserID [loginAuthorize $querydata(${queryprefix}username) $querydata(${queryprefix}password)]

            if {$loginUserID != ""} {
                set loginUserType $type
            }

            if {$loginUserID == ""} {
                return loginFailed
            }  else  {
                return login
            }

        }  elseif {($loginUserID != "")
            &&
            ([info exists querydata(${queryprefix}logout)] || [info exists querydata(${queryprefix}logout.x)])
        }  {
            set loginUserID ""
            set loginUserType ""

            return logout
        }  elseif {($loginUserID != "") && (![string equal $type $loginUserType])} {
            # do not allow users of different types - logout to be sure
            set loginUserID ""
            set loginUserType ""

            return logout
        }

        return ""
    }

    ## virtual methods
    protected method loginAuthorize {username password} {
        return ""
    }
 }

And the handler class that tests this:
 itcl::class ::sessionHandler {
    inherit \
        ::iapp::serialize \
        ::iapp::requesthandler \
        ::iapp::rh::login

    public method handleRequest {} {
        switch -- $suffix {
            access {
                set cmd [handleLogin iapp_ main]

                set html "CMD=$cmd<br />"
                append html "<form method=\"POST\" action=\"/app/access\">"
                if {$loginUserID == ""} {
                    append html "User: <input name=\"iapp_username\"><br />"
                    append html "Pass: <input type=\"password\" name=\"iapp_password\"><br />"
                    append html "<input type=\"submit\" name=\"iapp_login\" value=\"LOG IN\"><br />"
                }  else  {
                    append html "<h1>Logged in as $loginUserID</h1><br />"
                    append html "<input type=\"submit\" name=\"iapp_logout\" value=\"LOG OUT\"><br />"
                }
                append html "</form>"
                returnData 200 text/html $html
                return
            }
        }
        returnData 200 text/html "Page $suffix not found"
    }

    protected method loginAuthorize {username password} {
        if {[regexp "\[A-Za-z0-9\]{1,8}" $username] && [string equal "${username}123" $password]} {
            return id-$username
        }
        return ""
    }
 }

While this is not the most elegant code for now, it serves as a proof of concept. Another interesting idea is that the same iapp::factory class can be used to handle other thread-local data (ie. system users that could store their local data and provide some methods that work based on their privileges).

For now, future of this project remains a huge unknown, but I guess I will continue to investigate this possibility and work on it if I find this interesting enough.

Original posting on AOLserver mailing list:
 (...)

 Previously (with 3.4) I've been using the following:

 1/ code that allowed dynamic loading of "packages";
 The code that was contained in it's own namespace and was loaded/unloaded on demand with reloading on changes.

 2/ nsdqe provided session handling - setting the cookie and using ns_set for data plus serialization to harddrive
 This was handling all the session stuff plus logging in/logging out.

 3/ nscache and similar code to handle cache'ing

 This seems to work ok, but for generic approach what I really lack is an OO - ie so that someone could develop a generic "ecommerce"
 class and inherit for specific implementations. This can be done writing plain procs and passing proper args/configuration
 variables, but I find this task a job for actual OO.

 This also has a downside that ns_cache cannot actually cache Tcl_Obj so if I would get a result from the db this would get
 serialized to a string and deserialized on first use. I did develop some cache mechanism that does "deepcopy" of the objects and
 then caches the Tcl_Obj themselves for each thread, this improved the speed a lot.

 Right now I'm wondering the pros and cons of the following solution (using 4.0.10, 8.4.12, thread 2.6.3 and some other extensions).

 1/ similar code, but one that would initialize a separate thread for each package and load all the neccessary code in there - the
 package code would be Itcl classes and plain Tcl code
 (in the future this could use VFS and maybe tbcload to allow redistribution of the code)

 2/ session mechanism (which would require sessions to initialize main website engine) that would create instance of an application
 object per each session - so that each user would be an instance

 3/ most data would be cached using Tcl_Objs (maybe even not using nscache)

 4/ Background jobs could also use the same thread, using common non-AOLserver methods (after/fileevent etc)

 The objects would allow serialization/deserialization by supplying name-value data that would be saved by the session manager.
 Session would only allow X number of concurrent sessions and serialize+delete the oldest ones when new ones need to be created.

 Pros:

 * much better app designing framework

 * using event loop for packages

 * one thread per package would solve most multi user scenarios

 Cons:

 * any change in the package code would require serializing all instances and reloading the package
  (but it's not common to experiment with on production servers)

 * non-session aware browsers will not be able to use most of the functionality
  (some functionality could be done by supplying a generic instance for all non-session aware)

 * it would be easy to do a DoS by generating a lot of sessions
  (since serialization/deserialization would take a lot of time)

 * sync DB operations could cause a freeze for many customers (especially with long lasting queries)

[ Category Application | Category Object Orientation | Category Package | Category Internet | Category TclHttpd ]