-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSite2.lhs
177 lines (126 loc) · 6.48 KB
/
Site2.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
Route and handlers
------------------
Let's learn more about how routes and handlers work.
Again, you can run this "site" by typing:
`stack exec site2`
And again, I'll take you through how it works, line by line!
First, lets get the basic imports from part 1 out of the way:
> {-# LANGUAGE OverloadedStrings #-}
> import Web.Fn
> import Network.Wai (Response, Application)
> import Network.Wai.Handler.Warp (run)
And some useful stuff for text:
> import Data.Monoid ((<>))
(Monoids are things you can smoosh together (like text!), the `<>`
operator lets you smoosh them.)
> import qualified Data.Text as T
> import Data.Text (Text)
> tShow :: Show a => a -> Text
> tShow = T.pack . show
(This take anything you can use `show` on and turn it into Text.)
We'll use the same Context:
> data Context = Context { req :: FnRequest }
> instance RequestContext Context where
> getRequest ctxt = req ctxt
> setRequest ctxt newRequest = ctxt { req = newRequest }
Because we still don't need anything besides a request from a user.
And WAI and Warp are the same:
> main :: IO ()
> main = run 5000 waiApp
> waiApp :: Application
> waiApp = toWAI (Context defaultFnRequest) site
But `site` is going to be a bit different, because we're going to add more routes!
> site :: Context -> IO Response
> site ctxt = route ctxt [ end ==> indexHandler
> , path "hello" // end ==> helloHandler
> , path "hello" // segment // end ==> helloNameHandler
> , path "add" // end ==> addHandler
> , path "add" // segment // segment // end ==> addNumbersHandler
> , path "add" // segment // segment // end ==> addWordsHandler
> ]
> `fallthrough` notFoundText "Page not found."
`route` is a function that takes a RequestContext and a list of
routes. It will try to match the current requested path against one of
the paths listed in the routes. Fn lets you use the `//` operator to
combine different url patterns into a complete path that might match a
request. Then you use the `==>` operator to tell which Handler might
handle a request for that path.
I say "might" match or handle a request because there's always the possibility
that while, say, "/add/2/2" makes sense for the "addNumbersHandler" to handle,
maybe "/add/apple/oranges" should be handled by the "addWordsHandler". But if
none of the routes can handle it, the request will "fallthrough" and `site`
will respond with a the text "Page not found."
> indexHandler :: Context -> IO (Maybe Response)
> indexHandler ctxt = okText "Welcome to my SECOND Haskell website! Try visiting \"hello\" or \"add\"!"
Hey, it's our old friend the indexHandler! And a very similar one
called `helloHandler`. This time, let's take a closer look at the type
signature. It takes a Context and returns something with this other
type, `IO (Maybe Response)`.
[`okText`](http://hackage.haskell.org/package/fn-0.3.0.1/docs/Web-Fn.html#v:okText)
has the type `Text -> IO (Maybe Response)`. We give it Text, it
creates a Response with the right status code (200) and content type
(text/plain), then returns "Just" that response.
Here's a the handler which will handle "localhost:3000/hello", according to this route: `path "hello" // end ==> helloHandler`.
This handler looks very similar!
> helloHandler :: Context -> IO (Maybe Response)
> helloHandler ctxt = okText "Hello, world!"
But the next route is a little different. `path "hello" // segment //
end ==> helloNameHandler` can handle a URL like
"localhost:3000/hello/libby". The `segment` pattern means that part of
the URL is passed as an argument to the handler. This also affects
the type of the handler.
> helloNameHandler :: Context -> Text -> IO (Maybe Response)
> helloNameHandler ctxt name = okText ("Hello, " <> name <> "!")
This Handler has an additional argument and we've specified that it's
text so that we can combine it with the word "hello" to create a
greeting.
So far all these Handlers always return `okText`, which I said earlier
is "Just" some response. Can the handlers return Nothing instead? They
sure can!
> silentTreatmentHandler :: Context -> Text -> IO (Maybe Response)
> silentTreatmentHandler ctxt name =
> case name of
> "dbp" -> return Nothing
> otherwise -> okText ("Hello, " <> name <> "!")
This is a handler that can choose to ignore some people! If I use this
handler instead of `helloNameHandler`, then visit
"localhost:3000/hello/Libby", I still get "Hello, Libby!". But if I
type "localhost:3000/hello/dbp", the handler chooses NOT to handle
that request. Poor dbp gets ignored. :(
Exercises
---------
Add `silentTreatmentHandler` as a route to site. Is it possible to
have both `helloNameHandler` and `silentTreatmentHandler`? Does it
do what you would expect?
Add a `rudeHello` that "catches" requests that fall through
`silentTreatmentHandler`.
Matching on types
-----------------
> addHandler :: Context -> IO (Maybe Response)
> addHandler ctxt = okText "Find the result of adding different things by visitng \"add/firstThing/secondThing/\""
The last couple routes show how Fn tries to match segments with types.
These two route patterns:
```
path "add" // segment // segment // end ==> addNumbersHandler
path "add" // segment // segment // end ==> addWordsHandler
```
look exactly the same! But "add/1/2" will be handled by `addNumbersHandler`:
> addNumbersHandler :: Context -> Int -> Int -> IO (Maybe Response)
> addNumbersHandler ctxt firstNumber secondNumber =
> okText $ "The sum of " <> tShow firstNumber <> " and " <> tShow secondNumber
> <> " is " <> tShow (firstNumber + secondNumber)
resulting an response saying, "The sum of 1 and 2 is 3".
On the other hand, "add/cat/dog" will be handled by `addWordsHandler`:
> addWordsHandler :: Context -> Text -> Text -> IO (Maybe Response)
> addWordsHandler ctxt firstWord secondWord =
> okText $ firstWord <> " and " <> secondWord <> " added together is "
> <> (firstWord <> secondWord)
resulting in, "cat and dog added together is catdog".
How does this work? A request with the pattern
"add/something/something" will reach an `add // segment // segment`
route. Haskell will figure out that `segment` needs
to be an `Int` in order to be passed to `addNumbersHandler`. So Fn
will try to parse "something" into an Int! But that makes no sense, so
it returns "Nothing" -- the request falls through to the next route.
[Back to previous](http://fnhaskell.com/tutorial/Site1.html)
[To the next](http://fnhaskell.com/tutorial/Site3.html)