<<

The Functional Web The Framework A Web Toolkit for Haskell

Gregory Collins • Google Switzerland Doug Beardsley • Karamaan Group

askell is an advanced functional pro- the same inputs, always produce the same out- gramming language. The product of more put. This property means that you almost always H than 20 years of research, it enables rapid decompose a Haskell program into smaller con- development of robust, concise, and fast soft- stituent parts that you can test independently. ware. Haskell supports integration with other Haskell’s ecosystem also includes many power- languages and has loads of built-in concurrency, ful testing and code-coverage tools. parallelism primitives, and rich libraries. With Haskell also comes out of the box with a set its state-of-the-art testing tools and an active of easy-to-use primitives for parallel and con- community, Haskell makes it easier to produce current programming and for performance pro- flexible, maintainable, high-quality software. filing and tuning. Applications built with GHC The most popular Haskell implementation is enjoy solid multicore performance and can han- the (GHC), a high-­ dle hundreds of thousands of concurrent net- performance optimizing native-code compiler. work connections. We’ve been delighted to find Here, we look at Snap, a Web-development that Haskell really shines for Web programming. framework for Haskell. Snap combines many other Web-development environments’ best fea- What’s Snap? tures: writing Web code in an expressive high- Snap offers programmers a simple, expressive level language, a rapid development cycle, fast Web programming interface at roughly the same performance for native code, and easy deploy- level of abstraction as servlets. It includes a ment in production. fast, built-in HTTP server that drives application logic, so you can quickly create high-performance Why Is Haskell Good Web applications. Unlike some other Web frame- for Web Programming? works in higher-level languages, Snap lets you Haskell lets you write elegant, high-level code consume request data and stream response data that rivals the performance of lower-level, using a constant amount of memory. imperative languages. You can write declara- Snap features excellent documentation and tive programs at a high level of abstraction and tutorial materials, and, for a young project, it’s expressiveness, while still maintaining excel- quite robust. Haskell’s testing tools let you eas- lent performance. When you need to do bare- ily write a test suite with a high level of cover- metal bit-twiddling or need access to a library age. Snap uses Haskell’s cross-platform libraries that doesn’t yet have a Haskell equivalent, its to run on all major operating systems. A Snap- foreign function interface lets you easily drop based application also supports rapid develop- down to C. ment — an app running in development mode Writing solid, real-world code is easier in will load changes to your source code as soon Haskell than in other languages. It has strong as you make them. When you put your code into static typing, so many common programming production, you can create a single, fast, stand- errors, such as null pointer exceptions, can’t alone executable that’s easy to deploy. occur. You can separate an application’s core We designed Snap for efficiency; where logic from the parts that must interact with the appropriate, we use fast system calls such as outside world: “pure” functions in Haskell, given sendfile() and epoll(). On older versions of

84 Published by the IEEE Computer Society 1089-7801/11/$26.00 © 2011 IEEE IEEE INTERNET COMPUTING The Snap Framework

data ApplicationState = ApplicationState GHC, we have an optional binding { templateState :: HeistState Application to the libev C library for high-scal- , timerState :: TimerState ability I/O event scheduling. }

Getting Started with Snap Figure 1. The ApplicationState data type contains in-memory application Snap requires the state, which persists between requests. (http://hackage.haskell.org/platform), which ships with a binary installer for most major platforms. The Haskell platform includes the GHC applicationInitializer :: Initializer ApplicationState compiler, documentation, libraries, applicationInitializer = do and tools; think of it as “Haskell: heist <- heistInitializer “resources/templates” batteries included.” emptyTemplateState Once you’ve installed the Haskell timer <- timerInitializer platform, you can download, build, return $ ApplicationState heist timer and install Snap’s framework and all its dependencies using the cabal Figure 2. An Initializer handles a Snap application’s startup, reloads, and : cleanup.

$ cabal update $ cabal install snap $ cabal install the ApplicationState type, which $ myproject -p 8000 we’ll define next (see Figure 1). The Snap installation builds the Snap applications hold some state snap executable, which you can use Now, point your Web browser to in memory that they use to service to get started with a basic Snap proj- http://localhost:8000/; the server requests. Things in this category ect. By default, cabal installs exe- should respond with the default include templates and caches. In our cutables to $HOME/.cabal/bin; the canned response. particular case, ApplicationState following instructions assume that contains the set of templates we use this directory is on your $PATH. Programming with Snap: to service user requests (using our To set up a new example Snap A Simple Example heist templating library), as well as project (which is intended for use as Let’s look more closely at what’s in a TimerState, an example extension an example or jumping-off point for the default skeleton application. that stores the last time the applica- your own Snap Web applications), (This article describes a prerelease tion was reloaded. run these commands: version of Snap 0.3, which is sched- Application.hs also includes uled to be released before this issue code to initialize the application on $ mkdir myproject goes to print. Some details might load or reload. In Snap, we call this $ cd myproject change slightly.) code an Initializer, which handles $ snap init The snap init command creates a a Snap application’s startups, reloads, few files in thesrc/ subdirectory, the and cleanup (see Figure 2). This code The snap init command creates important ones being Application. loads all the template files from the a skeletal Snap project in the current hs, which contains the application’s resources/templates directory, ini- directory. This is a complete work- state definitions, and Site.hs, which tializes the reload timer, and creates ing Snap application that doesn’t do contains your site’s Web handlers. our ApplicationState object. much but that you can modify to First, let’s look at some code from Let’s turn our attention to src/ start fleshing out your first project. Application.hs: Site.hs, which contains our appli- The snap init command creates cation’s Web handler code. The skele- the Main module for this project in type Application = SnapExtend ton application contains two example src/Main.hs. When you build this ApplicationState handlers. One answers requests to get project with cabal install, an the site’s root page; the other answers executable called myproject is cre- This line indicates that your applica- requests to //foo by printing ated in $HOME/.cabal/bin. To build tion extends the Snap type (the type a message containing the foo input and run the example application, of basic Web handlers without any string (see Figures 3 through 5). execute these shell commands: associated in-memory state) with You can read this as, “If we’re

JANUARY/FEBRUARY 2011 85 The Functional Web

index = ifTop $ heistLocal (bindSplices indexSplices) $ render “index” This sets up a routing table for the where site’s URLs. Requests for the / URL indexSplices = are routed to the index handler; [ (“start-time,” startTimeSplice) requests for /echo/foo are routed , (“current-time,” currentTimeSplice) to the echo handler after we set ] stuff=foo in the request’s param- eter map. Figure 3. The root or homepage handler. This handler binds a couple of heist The site handler’s second half, template splices and renders the index template. after the main routing table, serves any files from the disk. Thea <|> b syntax means “try a; if it fails, try b.” In this case, if the user requests ... a URL that doesn’t match any of the

routing-table entries — for exam- ple, /screen.css — the fileServe function tries to find the file under the resources/static directory and serves it back to the user if it’s found. If fileServe can’t find the file, the fileServe handler fails, causing the site handler to fail, which causes Snap to produce a “404
Config generated at:
Page generated at:
Not Found” response. ... Benchmarks Figure 4. An example of splices in heist. The start-time/ and current- We compared Snap to five common time/ tags are bound to Haskell code, which renders HTML output. Web frameworks: 2.3.5 (using the internal server), 1.2.2, Apache 2.2.16, PHP 5.2.14, and Node.js 0.2.4. echo = do Our comparison involved two message <- decodedParam “stuff” benchmarks, which we ran on a heistLocal (bindString “message” message) $ render “echo” quad-core Xeon machine running at where 2.33 GHz. The pong benchmark (see decodedParam p = fromMaybe <*> urlDecode <$> fromMaybe “” Figure 7a) is basically “Hello, World!” <$> getParam p It responds to requests by sending the string pong. (For the Apache/PHP Figure 5. The /echo handler. This code answers a request to /echo/foo by line in this figure, we used Apache printing a message containing the string foo. to serve the file and Apache with mod_php to issue the pong response.) The file benchmark (see Figure 7b) at the root page, bind the follow- resources/templates/index.tpl, measures how fast each server can ing HTML tags and render the index you can see these splices at work (see send a single 49-Kbyte image file. template.” A splice, in heist par- Figure 4). We obtained the benchmark num- lance, is a procedure you can bind The handler in Figure 5 picks the bers by using the popular httperf to an XHTML tag that produces stuff parameter out of the request’s benchmarking tool. markup that gets spliced into the parameter map (we’ll explain where Snap performed fairly well. HTML output. Here, we’re saying that comes from in a second), URL- With logging turned on, it served that the tag should decodes it, binds the resulting mes- files roughly 40 percent faster than report the time the server was last sage to a splice, and renders the Apache; with logging turned off, it restarted and that the splice should report the cur- Finally, we can look at the main For more information about our rent Web server time. Looking at handler entry point (see Figure 6). testing methodology, including links

86 www.computer.org/internet/ IEEE INTERNET COMPUTING The Snap Framework

site = route [ (“/,” index) to the source code, visit http://snap , (“/echo/:stuff,” echo) framework.com/benchmarks. ] <|> fileServe “resources/static” What’s in Store for Snap? Haskell is a great language that Figure 6. The top-level site handler. It handles a couple of URL endpoints (/ tends to inspire a quasireligious and /echo) and shows an example of chaining together Snap handlers with devotion among its adherents. This the <|> operator. isn’t necessarily because Haskell programmers are dogmatic or driven by concerns of ideological purity. It’s Snap (no logging) because the qualitative experience of hacking in Haskell, once you’ve Snap (w/ logging) learned it, induces a Zen-like calm Node.js (no logging) that few other languages come close to approaching. Compared to tradi- Apache (+ PHP) w/ logging tional imperative languages, Haskell Grails often feels like slipping into a warm bath. Once you’ve gotten your pro- RoR (no logging) gram past the type checker and 0 5,000 10,000 15,000 20,000 25,000 30,000 35,000 40,000 QuickCheck has tested your code (a) Pong benchmark against thousands of randomly gen- erated test cases, you can feel com- Snap (no logging) fortable that your program works correctly, with a lot less anxiety, Snap (w/ logging) sweat, and tears. Node.js (no logging) The flip side is that Haskell’s learning curve is fairly steep. Pro- Apache (+ PHP) w/ logging grammers coming from traditional Grails languages must unlearn many pro- gramming habits that serve them RoR (no logging) well in other languages. This is because Haskell is fundamentally 0 2,000 4,000 6,000 8,000 10,000 12,000 (b) File benchmark different — even basic things, such as how values are computed from Figure 7. Comparing Snap with other common Web frameworks using the (a) expressions, are different and unfa- pong and (b) file benchmarks; values in requests per second (higher is better.) miliar. However, Haskell’s popular- ity has increased exponentially in recent years, and we’re hopeful that trated on building a stable, effi- to contribute or experiment with more programmers will take the time cient substrate upon which to build Snap, more information, tutorials, to check out a language that offers higher-level features. and documentation are available at many tangible advantages. http://snapframework.com. Snap is a young library early in its evolutionary life cycle. The basic nap is seeing some early adop- Gregory Collins is a site reliability engineer core is solid, and we plan to add fea- S tion. Right now, we believe it’s at Google Switzerland. Contact him at tures such as file upload support, most interesting to Web developers [email protected]. HTML5 parsing, user authentication, who care about performance and and facilities for building larger Web expressiveness and who enjoy roll- Doug Beardsley is a quantitative developer applications out of smaller, plugga- ing up their sleeves and learning at the Karamaan Group. Contact him at ble parts. This is largely a question about exciting new techniques to [email protected]. of time and manpower; we’re confi- build fast, solid Web applications. dent we’ll be able to offer a complete We welcome contributions of bug Selected CS articles and columns set of Web-programming facilities. reports, requests for improvement, are also available for free at http:// However, up to now, we’ve concen- and especially code. If you’ like ComputingNow.computer.org.

JANUARY/FEBRUARY 2011 87