-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSite1.lhs
135 lines (96 loc) · 5.05 KB
/
Site1.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
The Simplest Site
-----------------
Assuming you've downloaded [the source for this tutorial](https://www.github.com/emhoracek/fntutorial),
you can run your first website with the following commands:
```
stack build
stack exec site1
```
Visit localhost:3000, and you'll see a message!
How does it work?!
Well --- this is the source!! This page is written in "Literate Haskell", so
you can run it as source code. I'll take you through line by line and show you what's
going on.
> {-# LANGUAGE OverloadedStrings #-}
> import Web.Fn
> import Network.Wai (Response, Application)
> import Network.Wai.Handler.Warp (run)
> import Data.Text (Text)
These import the libraries we'll need for the website: Fn, WAI (Web Application
Interface), and Warp. In Haskell, you can import modules from libraries with
`import` + the module name. Then you can optionally restrict the imports to just
certain things. So we're only importing "Response" and "Application"
from Wai and only "run" from Warp.
Why are we importing these specific things?
* Fn is our web framework
* Warp is the server
* WAI is a common interface for web libraries and frameworks, and allows you to use all sorts of middleware (like it sounds, stuff in the middle between your app and the server).
* Data.Text allows us to use Text, which is [nicer than Strings](http://stackoverflow.com/questions/19608745/data-text-vs-string).
> data Context = Context { req :: FnRequest }
Next we make the context for our application. The idea is that
there's data we want to access when we're preparing a Response to send out to
a user. That data might be in a database, a cache, environment variables, wherever! In
Fn, we wrap all those things up in a "Context". Since in this part of the tutorial, we're
making the simplest app possible, we're only going to worry about the most
basic information the user sends us -- the request.
> instance RequestContext Context where
> getRequest ctxt = req ctxt
> setRequest ctxt newRequest = ctxt { req = newRequest }
These lines make `Context` an "instance" of `RequestContext`. Fn will use these
functions, `getRequest` and `setRequest`, to help us respond to requests.
Here's another RequestContext:
> data PizzaJungle = Totoro { request :: FnRequest,
> mothersMaidenName :: String,
> isAmbidextrous :: Bool }
> instance RequestContext PizzaJungle where
> getRequest pizzaJungle = request pizzaJungle
> setRequest pizzaJungle newReq = pizzaJungle { request = newReq }
Okay, I got very very silly here, but I want to make the point that it's not
really important that your app specifically have a `Context`, but it's important that
it has *something* (even a `PizzaJungle`!) that's a `RequestContext`. Also
notice that you can have as many and whatever fields you want in your context, as
long as you also have some field (name doesn't matter!) that's an `FnRequest`.</span>
Exercise
--------
Suppose your app had the `AppContext` below.
> data AppContext = AppContext { fnrequest :: FnRequest,
> secretKey :: Text }
How could you write the `RequestContext` instance for `AppContext`?
> -- uncomment the next lines by deleting the "--"s when you have a solution!
> -- instance RequestContext AppContext where
> -- getRequest ????
> -- setRequest ????
(Need help?)
Once you make changes, you should run `stack build` and `stack exec site1` to recompile and run the new code.
--------
A Route and a Handler
---------------------
Okay, so back to `Context`. How do we actually use that?
> site :: Context -> IO Response
> site ctxt = route ctxt [ end ==> indexHandler ]
> `fallthrough` notFoundText "Page not found."
Our "site" is a way to take our Context, and turn it into a
Response. Don't worry too much about it right now because I'll go into much
more depth in the next part on routes!
> indexHandler :: Context -> IO (Maybe Response)
> indexHandler ctxt = okText "Welcome to my first Haskell website."
Hey, it's our first handler! This handles a request by taking a context and
giving back a response (again, we'll go into more detail on just what that
`IO (Maybe Response)` there means in the next part). In this case, we're going
to do the same thing every time someone requests this page -- just send a message.
> main :: IO ()
> main = run 3000 waiApp
> waiApp :: Application
> waiApp = toWAI (Context defaultFnRequest) site
I said earlier that WAI is the interface between our site and the server. This
is where we use WAI. Warp expects an app, so we use Fn's "toWAI" function to
turn an initial context and our `site` into an Application. `run 3000 waiApp`
means to run the Warp server with this app on port 3000.
Exercise
--------
Change the site and handler to use the AppContext data structure instead of
Context. (You'll need to tell `waiApp` how to build an AppContext:
`AppContext defaultFnRequest "your string here"`). Can you make the
`indexHandler` return a response containing the `secretKey`?
[Back to previous](http://fnhaskell.com/tutorial/index.html)
[To the next](http://fnhaskell.com/tutorial/Site2.html)