ANN: hvac 0.1b, a transactional, declarative framework for lightweight web applications

hvac (short for http view and controller) has been my project for the last little while, and is finally in a fairly usable state, so I’m opening up the repo (darcs get http://community.haskell.org/~sclv/hvac/) for folks to play with and to get some feedback. While its not yet ready for hackage, the package as provided should be fully cabal installable. Documentation is available at http://community.haskell.org/~sclv/hvac/html_docs/hvac/

The aim of hvac is to provide an environment that makes the creation of lightweight fastcgi-based web applications as simple as possible, with an emphasis on concise, declarative style code, correct concurrent transactional logic, and transparency in adding caching combinators.

There are two included example programs, naturally neither of which is feature complete. They share a common login module of about 50 lines of code, excluding imports and templates.

The first program is a classic, greenspun-style message board with basic login functionality. It totals roughly 40 lines and tends to use just under 4mb of memory on my system.

The second is a wiki based on Pandoc and the PandocWiki code. The code totals roughly 30 lines (rendering borrowed from PandocWiki aside) and uses about 5mb of memory on my system.

hvac processes all requests in the STM monad, with some bells attached to properly interleave STM with session, database and filesystem operations such that they all conceptually occur together in a single transaction per request. Currently it is only fully tested with sqlite, but it should operate, modulo a few tweaks, with an database accessible via HDBC.

hvac is particularly designed to use the HStringTemplate library as an output layer, in a simple declarative fashion, and HStringTemplate has been improved in the process (about which more below). As the StringTemplate grammar is explicitly sub-turing, this ensures a clean separation of program logic from presentation, while providing a nonetheless fairly powerful language to express typical display tasks.

The included cache combinators, still experimental, should allow a simple and fine-grained control over the level of caching of various disk-bound operations. Phantom types are used to ensure that no functions that modify state may be cached.

To give a flavor of hvac code, the following is the complete (twenty lines!) source of the wiki controller (due to sql statements, some lines are rather long, and I’ve added a few odd breaks to make them work on this page):

wikiController tmpl =
 h |/ "login" *> login_plug tmpl
 <|>
 (h |/ "wiki" |\\ \pageName ->
    h |// "POST" *>
          withValidation [ ("contents", return) ]
          (\ [contents] -> do
             pageId <- selectVal "id from pages where name=?" [toSql pageName]
             maybe (addErrors [("Login","must be logged in.")] >> continue)
                (\user -> case fromSql pageId of
                            Just (_::Int) ->
                              execStat
     "insert into page_hist(pageId,contents,author) values(?,?,?)"
          [pageId, toSql contents, toSql . userName $ user]
                            Nothing -> do
                              execStat
     "insert into pages(name,locked) values(?,?)"
          [toSql pageName, toSql (0::Int)]
                              pid <- selectVal "max(id) from pages" []
                              execStat
     "insert into page_hist(pageId,contents,author) values(?,?,?)"
          [pid, toSql contents, toSql . userName $ user]) =<< getSes
             continue)
         <|> do
         pageId <- selectVal "id from pages where name=?" [toSql pageName]
         (join $ renderf (tmpl "showPage") ("pageName", pageName)
               "pageContents" |= selectRow
     "* from page_hist where pageId=? order by time desc limit 1" [pageId] ))
   <|> (redirect . ( ++ "/wiki/Index") =<< scriptName)

Future directions for work on hvac include: Stress testing for correctness of transactional logic and benchmarks. Exploration of various efficiency tweaks. Unit tests. Further development of the cache combinator API. Improvement of the example apps and addition of a few others (a blog maybe). Expansion of the library of validator functions. Exploration of transferring hvac to the standard FastCGI bindings (currently it uses a custom modified version to work properly with STM). Improvement of the database layer, particularly with regards to common paging functions. Creation of a set of simple combinators for generating CRUD (create read update delete) pages. Creation of a minimal set of standard templates (maybe).

12 Comments »

  1. Looks very interesting – I noticed one thing – there seems to be a bug in the regex for the email validator – it only accepts uppercase characters. See following diff:

    -validEmail x = if x =~ “^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$”
    +validEmail x = if x =~ “^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$”

    However, with that fix I’m still getting fastcgi errors registering a user in the board demo… But that could be due to a problem with fcgi, not hvac.

  2. Ahh, figured out the problem – another minor bug. In common.hs:
    line 43:
    -execStat “insert into users (name, pass, email) values(?,?)” [toSql user, toSql pass, toSql email]
    +execStat “insert into users (name, pass, email) values(?,?,?)” [toSql user, toSql pass, toSql email]

    It had only two ‘?’ when it needed three to insert all the values…

    Overall, pretty impressed, very lightweight, runs quickly.

  3. sclv said

    Thanks for the fixes! checked in and pushed.

  4. nIce one & useful too

  5. can you tell me some sites which provides the command set of microsoft SQL server 2005 for the beginners / Developers…..

  6. Hmm.. another minor fix for the example code. Lighty is being a pain (at least here, with my version), the fix being adding:
    “check-local” => “disable”
    to the fastcgi.server section of the config. Without that it pretty consistently 404s everything (because it is looking for the file hvac-board, etc).

    Also, I’m not sure what platform you are developing on but here (on linux), case sensitivity is killing the import Examples.Common – the examples directory needs to be “Examples” and common.hs needs to be “Common.hs”. People on #haskell pointed out that both Windows and OSX wouldn’t have that problem…

    Perhaps there is a better place to voice these concerns (bug tracker? email? #haskell?), but with the most recent patch (32), “hs for fastcgi”, linking is broken with the example hvac-board.hs – I can give you exact errors if you’d like, but for now I just am working with the previous revision… Perhaps you are just in the middle of changing things, and it will be resolved.

    (I’ve temporarily given up getting it working on nginx, but I’ll get back to that).

  7. plop said

    Hello =)

    I don’t know if it’s the correct place to ask you about your (nice) framework :).

    From the type signature of the controller function, one would guess that you cannot do arbitrary IO operations ? (This is why you provide the hv*File function).

    But, if I need to perform something like, listing the content of a directory (being a web developper discovering Haskell, my hello world is implementing a web gallery), what would I need to do that don’t use unsafePerformIO and it’s friends ?

  8. sclv said

    That’s a good question. As you can see, hvac prevents unrestricted IO because that would break transactional semantics. As is, hvac, uses posix flocks to keep its file operations properly atomic. A simple solution would be to write a function hvReadDir (MonadSTM m) :: FilePath -> m String; hvReadDir = liftSTM . unsafeIOToSTM

    liftSTM and the MonadSTM class are exported from Network.Framework.HVAC.Classes, which isn’t usually exposed. I suppose I could expose it, making clear that this was internal stuff provided.

    The problem with this is that it is not strictly transactional, as a file may be added or deleted between the time you read the directory and the time you take the next action on the directory — i.e. if you’re creating a file if it does not exist, by the time you create the file, it may actually exist.

    This problem exists on essentially all webapp frameworks, but most don’t force you to think about it. This being a Haskell framework that tries to act like Haskell, it does. After all, far too much web code is written with the assumption of atomic transactional semantics but without any implementation of it. :-)

    You can look at the code for hvReadFile to get an idea of how I handle flocks. But to extend this treatment means that reading a directory, writing a file, and reading a file would all *also* (or, actually, only) need to lock on the directory level rather than the file level, which is somewhat inefficient. The point being that optimistic transactional semantics on a filesystem are a somewhat hard problem, although not massively so, and require some thinking about.

    The safest and most efficient way would probably be to read your directory contents into a tvar at the start of an app, and every time a file is written to the directory (within hvac) to update the tvar. This keeps everything within the existent atomic and transactional bounds, and is actually probably faster to boot, whether or not one is worried about such things.

    If the files are being written by an external process, then one would need to also launch a worker thread to periodically update the tvar with the most current information.

    A bit more heavyweight, but similar to how “genuine” web galleries work is to keep your associations between file information and the actual filename in a database table.

    A photo gallery sample app would be very useful to illustrate these techniques and explore the problem space.

    Of course, there may be a simpler solution and I could just be overcomplicating things. Given that your hello world is a web gallery, you must have some experience thinking about this, and I’d appreciate your thoughts. :-)

    One piece that hvac does lack at this point is a writeUniqueFile function that guarantees that the file written to does not already exist. It’s easy enough to implement one, I suppose, but it is something that should be handled transparently and behind-the-scenes, but I haven’t felt a strong pressure to. If you’d find that useful, I could move it up my priorities list.

  9. sclv said

    I should also note that thinking through the file stuff after that last reply, I now realize that flocks are more trouble than they’re worth, and one can use “proxy” TVars to get nice optimistic behavior instead at a much lower overhead and less code (and with more portability to boot!), which should make writing a useful ReadDir less painful too.

  10. Art Silver said

    Nice framework idea. Most tend to be bloated and try to be the end all be all of coding.

  11. Zoneaire said

    Very interesting program, and intelligently made.

  12. […] is a new, extremely lightweight web framework written in haskell. To learn more about it, check out the hvac announcement . The purpose of this short article is to try to make sense of all the symbols that you will […]

RSS feed for comments on this post · TrackBack URI

Leave a comment