Web/Literature/Practical web programming in Haskell
From HaskellWiki
(Added web info box, more changes to this page to come) |
(Stripped out irrelevant/old links, updated versions and added some links) |
||
| Line 4: | Line 4: | ||
{{Template:Formal under construction}} | {{Template:Formal under construction}} | ||
| - | + | This tutorial focuses on CGI and FastCGI programming. For more introductory information, see [[Web/Literature|tutorials, blogs and research]] and [[Web/Forums_and_Discussion|discussion]]. | |
| - | + | ||
== Introduction == | == Introduction == | ||
This tutorial aims to get you started with writing web applications | This tutorial aims to get you started with writing web applications | ||
| - | in Haskell. We describe a relatively light-weight | + | in Haskell. We describe a relatively light-weight approach to Haskell web programming |
| - | approach to Haskell web programming | + | |
which uses a CGI library and an XHTML combinator library. | which uses a CGI library and an XHTML combinator library. | ||
| Line 28: | Line 26: | ||
We also present FastCGI, and an approach to using dynamically | We also present FastCGI, and an approach to using dynamically | ||
loaded Haskell code. | loaded Haskell code. | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
=== Assumed knowledge === | === Assumed knowledge === | ||
| Line 56: | Line 32: | ||
We will assume that you have some familiarity with the following | We will assume that you have some familiarity with the following | ||
concepts: | concepts: | ||
| - | |||
==== Haskell ==== | ==== Haskell ==== | ||
| Line 63: | Line 38: | ||
want to learn about Haskell in general, have a look at the lists of | want to learn about Haskell in general, have a look at the lists of | ||
[[books and tutorials]]. You may want to start with [[Haskell in 5 steps]]. | [[books and tutorials]]. You may want to start with [[Haskell in 5 steps]]. | ||
| - | |||
==== (X)HTML ==== | ==== (X)HTML ==== | ||
| Line 76: | Line 50: | ||
The combinators in the XHtml library do not make much sense unless you | The combinators in the XHtml library do not make much sense unless you | ||
understand at least some parts of HTML. | understand at least some parts of HTML. | ||
| - | |||
==== CGI ==== | ==== CGI ==== | ||
| Line 86: | Line 59: | ||
To really understand how the CGI library works, you probably need to know | To really understand how the CGI library works, you probably need to know | ||
a thing or two about CGI. ([http://www.comp.leeds.ac.uk/Perl/Cgi/start.html Tutorial].) | a thing or two about CGI. ([http://www.comp.leeds.ac.uk/Perl/Cgi/start.html Tutorial].) | ||
| - | |||
| - | |||
| - | |||
== Required software == | == Required software == | ||
| Line 98: | Line 68: | ||
However, any Haskell implementation that supports Haskell98 and multi-parameter | However, any Haskell implementation that supports Haskell98 and multi-parameter | ||
type classes should work. | type classes should work. | ||
| - | |||
=== Libraries: xhtml and cgi === | === Libraries: xhtml and cgi === | ||
| Line 105: | Line 74: | ||
<tt>cgi</tt> packages, download them from | <tt>cgi</tt> packages, download them from | ||
[http://hackage.haskell.org/packages/hackage.html HackageDB]. | [http://hackage.haskell.org/packages/hackage.html HackageDB]. | ||
| - | |||
=== Web server === | === Web server === | ||
| Line 115: | Line 83: | ||
so that they can run on that machine. This normally means that the machines | so that they can run on that machine. This normally means that the machines | ||
must to have the same architecture and run the same operating system. | must to have the same architecture and run the same operating system. | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
== Compiling and running web applications == | == Compiling and running web applications == | ||
| Line 166: | Line 88: | ||
Use GHC to produce a binary executable called <tt>prog.cgi</tt> from the Haskell | Use GHC to produce a binary executable called <tt>prog.cgi</tt> from the Haskell | ||
source code file <tt>prog.hs</tt>: | source code file <tt>prog.hs</tt>: | ||
| + | |||
<pre> | <pre> | ||
| - | ghc --make | + | ghc --make -o prog.cgi prog.hs |
</pre> | </pre> | ||
| Line 174: | Line 97: | ||
of the web server. | of the web server. | ||
| - | Linking your applications statically | + | [[Web/Literature/Static_linking|Linking your applications statically]] |
| - | + | ||
will avoid problems with missing libraries on the web server. | will avoid problems with missing libraries on the web server. | ||
To run the compiled program, visit the URL of the CGI | To run the compiled program, visit the URL of the CGI | ||
program with your web browser. | program with your web browser. | ||
| - | |||
== Simple examples == | == Simple examples == | ||
| Line 198: | Line 119: | ||
cgiMain :: CGI CGIResult | cgiMain :: CGI CGIResult | ||
| - | cgiMain = output $ renderHtml page | + | cgiMain = output $ renderHtml page/Static_linking |
main :: IO () | main :: IO () | ||
| Line 240: | Line 161: | ||
runCGI :: CGI CGIResult -> IO () | runCGI :: CGI CGIResult -> IO () | ||
</haskell> | </haskell> | ||
| - | |||
==== HTML combinators ==== | ==== HTML combinators ==== | ||
| Line 264: | Line 184: | ||
The function <code>renderHtml</code> (FIXME: explain variants) produces a string containing the | The function <code>renderHtml</code> (FIXME: explain variants) produces a string containing the | ||
document. | document. | ||
| - | |||
=== Getting user input === | === Getting user input === | ||
| Line 296: | Line 215: | ||
getInput :: String -> CGI (Maybe String) | getInput :: String -> CGI (Maybe String) | ||
</haskell> | </haskell> | ||
| - | |||
=== Cookies === | === Cookies === | ||
| Line 327: | Line 245: | ||
Here we use <code>newCookie</code>, <code>setCookie</code> and <code>readCookie</code> to store and retrieve a counter | Here we use <code>newCookie</code>, <code>setCookie</code> and <code>readCookie</code> to store and retrieve a counter | ||
cookie in the browser. If you want to get the string value of a cookie, use <code>getCookie</code> instead of <code>readCookie</code>. | cookie in the browser. If you want to get the string value of a cookie, use <code>getCookie</code> instead of <code>readCookie</code>. | ||
| - | |||
=== File uploads === | === File uploads === | ||
| Line 374: | Line 291: | ||
For efficiency reasons, we use Data.ByteString.Lazy to represent the file contents. | For efficiency reasons, we use Data.ByteString.Lazy to represent the file contents. | ||
getInputFPS gets the value of an input variable as a lazy ByteString. | getInputFPS gets the value of an input variable as a lazy ByteString. | ||
| - | |||
=== Error handling === | === Error handling === | ||
| Line 383: | Line 299: | ||
when an exception is thrown. It can be useful to set | when an exception is thrown. It can be useful to set | ||
the response code, e.g. 404. | the response code, e.g. 404. | ||
| - | |||
=== Returning non-HTML === | === Returning non-HTML === | ||
| Line 409: | Line 324: | ||
Examples: RSS | Examples: RSS | ||
| - | |||
=== Setting response headers === | === Setting response headers === | ||
| Line 417: | Line 331: | ||
Example: output raw file data (with last-modified) | Example: output raw file data (with last-modified) | ||
| - | |||
== Going further == | == Going further == | ||
| Line 423: | Line 336: | ||
This section explores some of possibilities beyond the basic web application | This section explores some of possibilities beyond the basic web application | ||
programming. | programming. | ||
| - | |||
=== Extending the CGI monad with monad transformers === | === Extending the CGI monad with monad transformers === | ||
| Line 498: | Line 410: | ||
import System.IO (stdin, stdout) | import System.IO (stdin, stdout) | ||
| - | runApp :: App CGIResult -> IO () | + | runApp :: App CGIResult -> IO ()http://cpansearch.perl.org/src/AUTRIJUS/Language-Haskell-0.01/hugs98-Nov2003/fptools/hslibs/text/html/doc/doc.htm |
runApp (App a) = | runApp (App a) = | ||
bracket (connect "host" "db" "user" "password") | bracket (connect "host" "db" "user" "password") | ||
| Line 512: | Line 424: | ||
gets released properly when the monad ends or if an exception is | gets released properly when the monad ends or if an exception is | ||
thrown. | thrown. | ||
| - | |||
=== Templating === | === Templating === | ||
There are times when you absolutely do not want to embed (X)HTML in Haskell. You can separate the code and the presentation (the Holy Grail of erm, web development). The code will be, well, Haskell, and the presentation will be buried inside templates. This might not be the case: fortunately, there is a very nice templating engine available: [[HStringTemplate]]; also very useful is [http://hackage.haskell.org/package/hakyll hakyll], a simple static site generator library, mainly aimed at creating blogs and brochure sites. | There are times when you absolutely do not want to embed (X)HTML in Haskell. You can separate the code and the presentation (the Holy Grail of erm, web development). The code will be, well, Haskell, and the presentation will be buried inside templates. This might not be the case: fortunately, there is a very nice templating engine available: [[HStringTemplate]]; also very useful is [http://hackage.haskell.org/package/hakyll hakyll], a simple static site generator library, mainly aimed at creating blogs and brochure sites. | ||
| - | |||
=== FastCGI === | === FastCGI === | ||
| Line 534: | Line 444: | ||
Take a look at lightweight, minimalistic FastCGI-based web frameworks: [http://community.haskell.org/~sclv/hvac/ HVAC] (Haskell view and controller) and [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/kibro Kibro]. | Take a look at lightweight, minimalistic FastCGI-based web frameworks: [http://community.haskell.org/~sclv/hvac/ HVAC] (Haskell view and controller) and [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/kibro Kibro]. | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
== Database-driven web-applications == | == Database-driven web-applications == | ||
| - | |||
=== Database connectivity === | === Database connectivity === | ||
See [http://hackage.haskell.org/package/Takusen Takusen] and [http://software.complete.org/software/projects/show/hdbc HDBC]. If you would like to write queries in Haskell (and not SQL), see also [http://haskelldb.sourceforge.net/ HaskellDB], which integrates with HDBC. | See [http://hackage.haskell.org/package/Takusen Takusen] and [http://software.complete.org/software/projects/show/hdbc HDBC]. If you would like to write queries in Haskell (and not SQL), see also [http://haskelldb.sourceforge.net/ HaskellDB], which integrates with HDBC. | ||
| - | |||
| - | |||
| - | |||
FastCGI aren't restarted for each request, only the runFastCGI part is re-run. Everything (handles, datastructures etc.) you do outside of that loop will be persistent. However you need to handle errors yourself, because you're operating outside of handleErrors. | FastCGI aren't restarted for each request, only the runFastCGI part is re-run. Everything (handles, datastructures etc.) you do outside of that loop will be persistent. However you need to handle errors yourself, because you're operating outside of handleErrors. | ||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
| - | |||
== See also == | == See also == | ||
| - | * | + | * [[Web/Literature|Haskell web development blogs, research and literature]] |
| - | + | ||
''Authors: Björn Bringert'' | ''Authors: Björn Bringert'' | ||
''Authors: Don Stewart'' | ''Authors: Don Stewart'' | ||
Revision as of 13:47, 3 October 2010
This page is under construction. Feel free to help out. If you make substantial edits, please add your name to the authors list at the bottom of this page, so that you can be credited if this is ever published in another medium.
This tutorial focuses on CGI and FastCGI programming. For more introductory information, see tutorials, blogs and research and discussion.
Contents |
1 Introduction
This tutorial aims to get you started with writing web applications in Haskell. We describe a relatively light-weight approach to Haskell web programming which uses a CGI library and an XHTML combinator library.
We think that while the approach we describe here is not as sophisticated or innovative as some other approaches, it is simple, portable and easy to understand if you are already familiar with web programming in other languages.
The tutorial starts with preliminaries such as how to install the necessary software and how to compile and run your web applications. We then show a number of working small example programs which introduce the basic features of the CGI and XHtml libraries. We then move on to how to use monad transformers to add application specific functionality such as sessions to the CGI monad, and how to create database-driven web applications. We also present FastCGI, and an approach to using dynamically loaded Haskell code.
1.1 Assumed knowledge
This tutorial is not meant as an introduction to Haskell or web programming. We will assume that you have some familiarity with the following concepts:
1.1.1 Haskell
This tutorial is not meant as a first introduction to Haskell. If you want to learn about Haskell in general, have a look at the lists of books and tutorials. You may want to start with Haskell in 5 steps.
1.1.2 (X)HTML
HTML (HyperText Markup Language) is the "the lingua franca for publishing hypertext on the World Wide Web. The XHtml library which we use in this tutorial produces XHTML 1.0, which is HTML 4.0 formulated as XML.
The combinators in the XHtml library do not make much sense unless you understand at least some parts of HTML.
1.1.3 CGI
CGI (Common Gateway Interface) programs are programs which run on the web server. They are given input which comes from the user's browser, and their output is given to the browser.
To really understand how the CGI library works, you probably need to know a thing or two about CGI. (Tutorial.)
2 Required software
2.1 Haskell compiler
GHC, the Glasgow Haskell Compiler, is the Haskell implementation that we will use in this tutorial. However, any Haskell implementation that supports Haskell98 and multi-parameter type classes should work.
2.2 Libraries: xhtml and cgi
If your Haskell implementation does not come with the xhtml and cgi packages, download them from HackageDB.
2.3 Web server
You need to have access to a web server on which you can run CGI programs. The most convenient way to do this when learning and developing is to run a web server on your development machine. If you run the programs on some other machine you need to make sure that you compile your programs so that they can run on that machine. This normally means that the machines must to have the same architecture and run the same operating system.
3 Compiling and running web applications
Use GHC to produce a binary executable called prog.cgi from the Haskell source code file prog.hs:
ghc --make -o prog.cgi prog.hs
Put the compiled program in the cgi-bin directory, or give it the extension .cgi, depending on the configuration of the web server.
Linking your applications statically will avoid problems with missing libraries on the web server.
To run the compiled program, visit the URL of the CGI program with your web browser.
4 Simple examples
4.1 Hello World
Here is a very simple example which just outputs some static HTML. The type signatures in this code are optional. We show them here for clarity, but omit them in some later examples.
import Network.CGI import Text.XHtml page :: Html page = body << h1 << "Hello World!" cgiMain :: CGI CGIResult cgiMain = output $ renderHtml page/Static_linking main :: IO () main = runCGI $ handleErrors cgiMain
The page function constructs an HTML document which consists
of a body containing a single header element which contains the text
"Hello World". The CGI-action cgiMain renders the HTML document
as a string, and produces that string as output. The main function
runs cgiMain, using the normal CGI protocol for input and output.
It also uses handleErrors to output an error page in case |cgiMain|
throws an exception.
Fans of one-liners may like this version better (handleErrors has been
omitted since this simple program will not throw any exceptions):
import Text.XHtml import Network.CGI main = runCGI . output . renderHtml $ body << h1 << "Hello World!"
These are some of the important functions used in this example:
-- creates a string containing the HTML document. renderHtml :: Html -> String -- outputs a string as the body of the HTTP response. output :: String -> CGI CGIResult -- Catches any exception thrown by the given CGI action, returns an -- error page with a 500 Internal Server Error, showing the exception -- information, and logs the error. handleErrors :: CGI CGIResult -> CGI CGIResult -- Runs a CGI action which produces a CGIResult, using the CGI protocol -- to get the inputs and send the outputs. runCGI :: CGI CGIResult -> IO ()
4.1.1 HTML combinators
See also [1].
Html is the type of HTML fragments. It comes from the Text.XHtml module.
There are functions for all XHTML 1.0 elements.
Some examples:
- header, body
- h1, h2, ...
- thediv
- p
- image
The << operator is used for nesting HTML.
+++ concatenates HTML.
Attributes are added to tags using the ! operator.
The function renderHtml (FIXME: explain variants) produces a string containing the
document.
4.2 Getting user input
This program shows a form which asks the user for her name. When the form is submitted, the program greets the user by name.
import Network.CGI import Text.XHtml inputForm = form << [paragraph << ("My name is " +++ textfield "name"), submit "" "Submit"] greet n = paragraph << ("Hello " ++ n ++ "!") page t b = header << thetitle << t +++ body << b cgiMain = do mn <- getInput "name" let x = maybe inputForm greet mn output . renderHtml $ page "Input example" x main = runCGI $ handleErrors cgiMain
This code makes use of the getInput function from the CGI library:
-- Get the value of an input variable, for example from a form. -- If the variable has multiple values, the first one is returned. getInput :: String -> CGI (Maybe String)
4.3 Cookies
import Network.CGI import Text.XHtml import Control.Monad (liftM) import Data.Maybe (fromMaybe) hello :: Int -> Html hello 0 = h1 << "Welcome!" +++ p << "This is the first time I see you." hello c = h1 << "Welcome back!" +++ p << ("I have seen you " ++ show c ++ " times before.") page :: String -> Html -> Html page t b = header << thetitle << t +++ body << b cgiMain :: CGI CGIResult cgiMain = do c <- liftM (fromMaybe 0) $ readCookie "mycookie" setCookie (newCookie "mycookie" (show (c+1))) output . renderHtml . page "Cookie example" $ hello c main :: IO () main = runCGI $ handleErrors cgiMain
Here we use newCookie, setCookie and readCookie to store and retrieve a counter
cookie in the browser. If you want to get the string value of a cookie, use getCookie instead of readCookie.
4.4 File uploads
FIXME: use a safer example
-- Accepts file uploads and saves the files in the given directory. -- WARNING: this script is a SECURITY RISK and only for -- demo purposes. Do not put it on a public web server. import Network.CGI import Text.XHtml import qualified Data.ByteString.Lazy as BS import Control.Monad (liftM) import Data.Maybe (fromJust) uploadDir = "../upload" fileForm = form ! [method "post", enctype "multipart/form-data"] << [afile "file", submit "" "Upload"] saveFile n = do cont <- liftM fromJust $ getInputFPS "file" let f = uploadDir ++ "/" ++ basename n liftIO $ BS.writeFile f cont return $ paragraph << ("Saved as " +++ anchor ! [href f] << f +++ ".") page t b = header << thetitle << t +++ body << b basename = reverse . takeWhile (`notElem` "/\\") . reverse cgiMain = do mn <- getInputFilename "file" h <- maybe (return fileForm) saveFile mn output . renderHtml $ page "Upload example" h main = runCGI $ handleErrors cgiMain
We first output a file upload form, which should use the HTTP POST method,
and the multipart/form-data content type. Here we seen an example of the use of
HTML attributes, added with the ! operator.
For efficiency reasons, we use Data.ByteString.Lazy to represent the file contents. getInputFPS gets the value of an input variable as a lazy ByteString.
4.5 Error handling
handleErrors catches all exceptions and outputs a default error page with some information about the exception. You can write you own exception handler if you want to do something else when an exception is thrown. It can be useful to set the response code, e.g. 404.
4.6 Returning non-HTML
Of course we do not have to output HTML. Use setHeader to set the value of the Content-type header, and you can output whatever string you like.
In this example we return an image:
import Network.CGI import System.IO import qualified Data.ByteString.Lazy as B main = do b <- B.readFile "./img/test.jpg" -- read the image runCGI . handleErrors . cgiMain $ b cgiMain :: B.ByteString -> CGI CGIResult cgiMain p = do -- we need to set the appropriate content-type setHeader "Content-type" "image/jpg" outputFPS p
Examples: RSS
4.7 Setting response headers
You can use the setHeader function to set arbitrary HTTP response headers.
You can also set the response code, as seen above.
Example: output raw file data (with last-modified)
5 Going further
This section explores some of possibilities beyond the basic web application programming.
5.1 Extending the CGI monad with monad transformers
At this point, you should be able to create many useful CGI scripts. As your scripts get more ambitious, however, you may find yourself needing to pass "global" parameters to your CGI actions (e.g. database connections, session information.) Rather than explicitly passing these values around, you can extend the CGI monad to do this work for you.
Thetransformer, allowing us to build a new monad that does everything the CGI monad does -- and more!
For example, let's define a new CGI monad that provides a database connection (in this example, we use the
it will be used by the CGI application, I'll call the new monad "App".
Should this not compile for you, you need to enable some extensions:
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleInstances #-}
After importing the appropriate modules, we define a new type,
monad that the monad transformers are modifying. Usually this will be
theaction in the monad will return.
import Control.Monad.Reader import Network.CGI import Network.CGI.Monad import Database.HSQL.PostgreSQL newtype AppT m a = App (ReaderT Connection (CGIT m) a) deriving (Monad, MonadIO, MonadReader Connection)
common use of this new monad.
type App a = AppT IO a
we must support.
instance MonadCGI (AppT IO) where cgiAddHeader n v = App . lift $ cgiAddHeader n v cgiGet x = App . lift $ cgiGet x
So now we have an App monad that gives us all the functionality of CGI, but also carries around a database connection. The last step is to define the function that creates the monad so we can run actions inside it.
import Control.Exception (bracket) import System.IO (stdin, stdout) runApp :: App CGIResult -> IO ()http://cpansearch.perl.org/src/AUTRIJUS/Language-Haskell-0.01/hugs98-Nov2003/fptools/hslibs/text/html/doc/doc.htm runApp (App a) = bracket (connect "host" "db" "user" "password") disconnect (\c -> do { env <- getCGIVars ; hRunCGI env stdin stdout (runCGIT (runReaderT a c)) ; return () } )
(either fill in your account/password information, or change
gets released properly when the monad ends or if an exception is thrown.
5.2 Templating
There are times when you absolutely do not want to embed (X)HTML in Haskell. You can separate the code and the presentation (the Holy Grail of erm, web development). The code will be, well, Haskell, and the presentation will be buried inside templates. This might not be the case: fortunately, there is a very nice templating engine available: HStringTemplate; also very useful is hakyll, a simple static site generator library, mainly aimed at creating blogs and brochure sites.
5.3 FastCGI
FastCGI is a standard for CGI-like programs that are not restarted for every request. This reduces the overhead involved in handling each request, and reduces the servers response time for each request. The overhead involved in starting a new process for each request can also include the need to set up new DB connections every time. With FastCGI, DB connections can be reused.
Install FastCGI. Get a web server which can run FastCGI programs. Import Network.FastCGI. Use runFastCGI.
See also a tutorial by Paul R Brown: Wiring Haskell Into a FastCGI Web Server
Take a look at lightweight, minimalistic FastCGI-based web frameworks: HVAC (Haskell view and controller) and Kibro.
6 Database-driven web-applications
6.1 Database connectivity
See Takusen and HDBC. If you would like to write queries in Haskell (and not SQL), see also HaskellDB, which integrates with HDBC.
FastCGI aren't restarted for each request, only the runFastCGI part is re-run. Everything (handles, datastructures etc.) you do outside of that loop will be persistent. However you need to handle errors yourself, because you're operating outside of handleErrors.
7 See also
Authors: Björn Bringert Authors: Don Stewart
