happs-tutorial-0.8: templates/getandpost.st
<h3>GET and POST data</h3>
<p>GET data is data attached to an http request via the url string. In the job board app, an example of this is
<a href=/tutorial/viewprofile?user=tphyahoo>tphyahoo's profile</a>, where the user is specified on the url string.
Another example is <a href=/tutorial/viewjob?user=tphyahoo&job=darcshub>fetching a particular job</a>.
<p>POST data is data attached to a request after submitting a form.
One place this is used is in the <a href=/tutorial/register>registration</a> process.
Once you have a user created, you can also see POST in action by editing your profile or creating jobs.
<p>Happstack deals with GET and POST data similarly.
<p>Let's look at how
<a href=/tutorial/viewprofile?user=tphyahoo>tphyahoo's profile</a> gets displayed.
<p><a href=/src/Controller.hs>Controller.hs</a>: ... , dir "viewprofile" [ methodSP GET \$ userProfile rglobs ] ...
<p>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.</p>
<p>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.</p>
<p>ghci>:i FromData
<br>:i FromData
<br>class FromData a where fromData :: RqData a
<br> -- Defined in Happstack.Server.SimpleHTTP
<br>instance [overlap ok] (FromData a, FromData b) => FromData (a, b)
<br> -- Defined in Happstack.Server.SimpleHTTP
<br>instance [overlap ok] (FromData a, FromData b, FromData c) => FromData (a, b, c)
<br> -- Defined in Happstack.Server.SimpleHTTP
<br>instance [overlap ok] (FromData a, FromData b, FromData c, FromData d) => FromData (a, b, c, d)
<br> -- Defined in Happstack.Server.SimpleHTTP
<br>instance [overlap ok] (FromData a) => FromData (Maybe a)
<br> -- Defined in Happstack.Server.SimpleHTTP
</p>
<p>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.</p>
<p>ghci>:t look
<br>look :: String -> RqData String
</p>
<p>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.</p>
<p><a href=/src/ControllerGetActions.hs>ControllerGetActions.hs</a>: </p>
<p>data UserNameUrlString = UserNameUrlString {profilename :: String}
<br>instance FromData UserNameUrlString where
<br> fromData = liftM UserNameUrlString (look "user" `mplus` return "")</p>
<p>Since we have an instance of FromData defined, we can use it with a code fragment like the following.
<br>...UserNameUrlString user <- getData >>= maybe mzero return...
</p>
<p>ghci> :t getData
<br>getData :: (ServerMonad m, FromData a) => m (Maybe a)</p>
<p>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.
<br>ghci> :t withData
<br>withData :: (withData :: (FromData a,Control.Monad.MonadPlus m, ServerMonad m) => (a -> m r) -> m r
</p>
<p>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.</p>
<p>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.</p>
<p>
Next we cover <a href="/tutorial/file-uploads">uploading files</a></p>