1. new in this version
  2. build a web 2.0 app in happstack
  3. why happstack is cool
  4. getting started with happstack
  5. prerequisites
  6. cabal install me
  7. first shot at happstack
  8. url handling
  9. basic HTML inclusion
  10. templates
  11. stringtemplate basics
  12. debugging
  13. form data: get and post
  14. form data: file uploads
  15. cookies
  16. introduction to macid
  17. first steps with macid
  18. scaling with multimaster
  19. using macid safely
  20. macid dummy data
  21. changing the data model
  22. macid stress test
  23. limitations of macid
  24. foreign characters
  25. IxSets
  26. cron jobs
  27. thanks
  28. appendix (floundering in ghci)

GET and POST data

GET data is data attached to an http request via the url string. In the job board app, an example of this is tphyahoo's profile, where the user is specified on the url string. Another example is fetching a particular job.

POST data is data attached to a request after submitting a form. One place this is used is in the registration process. Once you have a user created, you can also see POST in action by editing your profile or creating jobs.

Happstack deals with GET and POST data similarly.

Let's look at how tphyahoo's profile gets displayed.

Controller.hs: ... , dir "viewprofile" [ methodSP GET $ userProfile rglobs ] ...

using dir and methodSP as above is a common pattern. dir pops the head element of the path array ["viewprofile"], resulting in an empty array. methodSP checks that the path array is empty and that the method is GET. If so, control is passed to the next sp: userProfile rglobs, which is in another module.

Now once we can select for any HTTP method, how do we actually grab data from the request? Fundamentally, this comes down to the FromData class. Again, we can fire up ghci and check it out.

ghci>:i FromData
:i FromData
class FromData a where fromData :: RqData a
-- Defined in Happstack.Server.SimpleHTTP
instance [overlap ok] (FromData a, FromData b) => FromData (a, b)
-- Defined in Happstack.Server.SimpleHTTP
instance [overlap ok] (FromData a, FromData b, FromData c) => FromData (a, b, c)
-- Defined in Happstack.Server.SimpleHTTP
instance [overlap ok] (FromData a, FromData b, FromData c, FromData d) => FromData (a, b, c, d)
-- Defined in Happstack.Server.SimpleHTTP
instance [overlap ok] (FromData a) => FromData (Maybe a)
-- Defined in Happstack.Server.SimpleHTTP

As you can see, there are a few convenience instances defined for tuples of FromData instances and for the lift of a FromData instance into Maybe. How does one actually make an instance of FromData yourself, though? The basic way is to use look.

ghci>:t look
look :: String -> RqData String

Another note is that RqData is an instance of Monad and MonadPlus. Between these instances and look you should be able to easily define your own instances of FromData. We'll be looking at an example from the code running this tutorial below.

ControllerGetActions.hs:

data UserNameUrlString = UserNameUrlString {profilename :: String}
instance FromData UserNameUrlString where
  fromData = liftM UserNameUrlString (look "user" `mplus` return "")

Since we have an instance of FromData defined, we can use it with a code fragment like the following.
...UserNameUrlString user <- getData >>= maybe mzero return...

ghci> :t getData
getData :: (ServerMonad m, FromData a) => m (Maybe a)

There's also another function you can use which is helpful when you don't actually need do notation and just want a one line function.
ghci> :t withData
withData :: (withData :: (FromData a,Control.Monad.MonadPlus m, ServerMonad m) => (a -> m r) -> m r

The above is the main pattern for processing GET or POST data in Happstack. Please look through ControllerGetActions.hs to get more examples of using this.

To summarize, you decide what argument type withData should accept. This might be a datatype you have already defined, like User or Job (which already plays a major role in the app), or it could be an ad-hoc datatype which is only used this once in the form processing. Whatever the case, you declare that data type an instance of FromData, and define how it should grab data attached to the request. In this case, UserNameUrlString takes one argument, so we use liftM -- for two args we would use liftM2, three args liftM3 etc. To get that one arg, we look for a request GET variable named "user" and if we don't find it we use a reasonable default, in this case the empty string. The result is a value of type UserNameUrlString which gets passed to the ServerPartT handler in the userProfile function.

Next we cover uploading files