/
HelloWorld.hs
152 lines (139 loc) · 4.7 KB
/
HelloWorld.hs
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
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
module HelloWorld where
import Servant ( PlainText
, Get
, Proxy(..)
, type (:>) -- Syntax for importing type operator
, type (:<|>)
, (:<|>)(..)
)
import Servant.Server (Handler, Server, Application, serve)
import Network.Wai.Handler.Warp (run)
import Control.Monad.IO.Class (liftIO)
-- "Hello world" of servant web application
-- In this we will create a web application with
-- only two routes/endpoints, which are
--
-- /name, which return a hard coded name
-- /age, which returns a hard coded age
--
-- The two functions that are supposed to
-- handle these two endpoints can be seen below.
--
handlerName :: Handler String
handlerName = return "sras"
handlerAge :: Handler String
handlerAge = liftIO (return "30" :: IO String) -- Using liftIO just to show that we can do arbitrary IO in the Handler
-- The handler functions for Servant should run in a 'Handler'
-- monad, which is something that is part of the Servant
-- This monad is an instance of MonadIO class, so you
-- can do arbitrary IO in your handlers. You can see this
-- done in the 'age' handler, where we lift a value of type
-- 'IO String' to a value of type 'Handler String'
type ServantType = "person" :> "name" :> Get '[PlainText] String
:<|> "person" :> "age" :> Get '[PlainText] String
-- Now we come to the most unique feature of servant
-- which is, the webapplication represented as a type.
-- Each endpoint have its own type, and these types are
-- joined by the :<|> type operator, which ends up being
-- the type of the server that contains all the constituent
-- endpoints.
--
-- Let us start with a simple endpoint. Say we want this
-- endpoint to be avilable at url "/person/name" using GET method.
-- And say, we want this to return a plain text content to the browser.
--
-- The type of this endpoint can be
--
-- "person" :> "name" :> Get '[PlainText] String
--
-- Note that we had to separate the path segments using the :> operator
-- and it wouldn't work if we specify the path
-- as "person/name" :> Get '[PlainText] String
--
-- Next is the 'Get' part, which decides the HTTP Method
-- by which this endpoint can be accessed. Servant provides
-- the following methods
--
-- GET
-- POST
-- HEAD
-- PUT
-- DELETE
-- TRACE
-- CONNECT
-- OPTIONS
-- PATCH
--
-- After the Method, we have this type level list
-- '[PlainText]. This configures the type of formats
-- that this endpoint could return. Right now we have
-- only PlainText in this list. So this endpoint can
-- only output stuff in plain text format. The content
-- type header will contain "text/plain".
--
-- The available formats bundled with Servant are
-- PlainText, FormUrlEncoded, OctetStream and JSON
--
-- We will see how to add an Html type in the next example
server :: Server ServantType
server = handlerName :<|> handlerAge
-- In the above lines, we combine the handlers
-- (just like we combined the types representing
-- handlers in the step before). Here too we
-- can use the :<|> operator to combine handlers.
-- Only here this is a regular operator, that
-- operate of values, instead of types.
--
app :: Application
app = serve (Proxy :: Proxy ServantType) server
-- Here we make a regular wai application
-- from the servants representation of the
-- web app. If you are not familar with the `Proxy` stuff
-- It is something that is part of Data.Proxy module, and
-- is something that is commonly used in fancy typelevel
-- stuff.
--
-- Now that we have an wai `Application`,
-- we are out of the magical land of Servant and
-- back to the familiar world of wai.
mainFn :: IO ()
mainFn = run 4000 app
-- Now let us see how this app behaves
--
-- curl -v 127.0.0.1:4000/person/age
-- * Trying 127.0.0.1...
-- * Connected to 127.0.0.1 (127.0.0.1) port 4000 (#0)
-- > GET /age HTTP/1.1
-- > Host: 127.0.0.1:4000
-- > User-Agent: curl/7.47.0
-- > Accept: */*
-- >
-- < HTTP/1.1 200 OK
-- < Transfer-Encoding: chunked
-- < Date: Sun, 12 Nov 2017 02:59:50 GMT
-- < Server: Warp/3.2.13
-- < Content-Type: text/plain;charset=utf-8
-- <
-- * Connection #0 to host 127.0.0.1 left intact
-- 30
--
-- curl -v 127.0.0.1:4000/person/name
-- * Trying 127.0.0.1...
-- * Connected to 127.0.0.1 (127.0.0.1) port 4000 (#0)
-- > GET /name HTTP/1.1
-- > Host: 127.0.0.1:4000
-- > User-Agent: curl/7.47.0
-- > Accept: */*
-- >
-- < HTTP/1.1 200 OK
-- < Transfer-Encoding: chunked
-- < Date: Sun, 12 Nov 2017 03:17:44 GMT
-- < Server: Warp/3.2.13
-- < Content-Type: text/plain;charset=utf-8
-- <
-- * Connection #0 to host 127.0.0.1 left intact