/
Server.purs
264 lines (227 loc) · 8.54 KB
/
Server.purs
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
module Hyper.Node.Server
( HttpRequest
, HttpResponse
, NodeResponse
, writeString
, write
, defaultOptions
, defaultOptionsWithLogging
, runServer
, runServer'
) where
import Prelude
import Data.HTTP.Method as Method
import Data.Int as Int
import Data.StrMap as StrMap
import Node.HTTP as HTTP
import Node.Stream as Stream
import Control.IxMonad (ipure, (:*>), (:>>=))
import Control.Monad.Aff (Aff, launchAff, makeAff)
import Control.Monad.Aff.AVar (putVar, takeVar, modifyVar, makeVar', AVAR, makeVar)
import Control.Monad.Aff.Class (class MonadAff, liftAff)
import Control.Monad.Eff (Eff)
import Control.Monad.Eff.Class (class MonadEff, liftEff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Eff.Exception (Error, catchException, error)
import Control.Monad.Error.Class (throwError)
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap)
import Data.Tuple (Tuple(..))
import Hyper.Conn (Conn)
import Hyper.Middleware (Middleware, evalMiddleware, lift')
import Hyper.Middleware.Class (getConn, modifyConn)
import Hyper.Port (Port(..))
import Hyper.Request (class ReadableBody, class Request, RequestData)
import Hyper.Response (class ResponseWritable, class Response, ResponseEnded, StatusLineOpen)
import Hyper.Status (Status(..))
import Node.Buffer (Buffer)
import Node.Encoding (Encoding(..))
import Node.HTTP (HTTP)
import Node.Stream (Writable)
data HttpRequest
= HttpRequest HTTP.Request RequestData
instance requestHttpRequest :: Monad m => Request HttpRequest m where
getRequestData = do
getConn :>>=
case _ of
{ request: HttpRequest _ d } -> ipure d
-- A limited version of Writable () e, with which you can only write, not end,
-- the Stream.
newtype NodeResponse m e
= NodeResponse (Writable () e -> m Unit)
writeString :: forall m e. MonadAff e m => Encoding -> String -> NodeResponse m e
writeString enc str = NodeResponse $ \w -> liftAff (makeAff (writeAsAff w))
where
writeAsAff w fail succeed =
Stream.writeString w enc str (succeed unit) >>=
if _
then succeed unit
else fail (error "Failed to write string to response")
write :: forall m e. MonadAff e m => Buffer -> NodeResponse m e
write buffer = NodeResponse $ \w ->
liftAff (makeAff (\fail succeed -> void $ Stream.write w buffer (succeed unit)))
instance stringNodeResponse :: (MonadAff e m) => ResponseWritable (NodeResponse m e) m String where
toResponse = ipure <<< writeString UTF8
instance stringAndEncodingNodeResponse :: (MonadAff e m) => ResponseWritable (NodeResponse m e) m (Tuple String Encoding) where
toResponse (Tuple body encoding) =
ipure (writeString encoding body)
instance bufferNodeResponse :: (MonadAff e m)
=> ResponseWritable (NodeResponse m e) m Buffer where
toResponse buf =
ipure (write buf)
readBody
:: forall e.
HttpRequest
-> Aff (http :: HTTP, avar :: AVAR | e) String
readBody (HttpRequest request _) = do
let stream = HTTP.requestAsStream request
completeBody <- makeVar
chunks <- makeVar' ""
e <- liftEff (catchException (pure <<< Just) (fillBody stream chunks completeBody *> pure Nothing))
case e of
Just err -> throwError err
Nothing -> takeVar completeBody
where
fillBody stream chunks completeBody = do
Stream.onDataString stream UTF8 \chunk -> void do
launchAff (modifyVar (_ <> chunk) chunks)
Stream.onEnd stream $ void (launchAff (takeVar chunks >>= putVar completeBody))
instance requestBodyReaderReqestBody :: (Monad m, MonadAff (http :: HTTP, avar :: AVAR | e) m)
=> ReadableBody HttpRequest m String where
readBody =
_.request <$> getConn :>>=
case _ of
r -> lift' (liftAff (readBody r))
-- TODO: Make a newtype
data HttpResponse state = HttpResponse HTTP.Response
getWriter ∷ ∀ req res c m rw.
Monad m ⇒
Middleware
m
(Conn req { writer ∷ rw | res } c)
(Conn req { writer ∷ rw | res } c)
rw
getWriter = _.response.writer <$> getConn
setStatus ∷ ∀ req res c m e.
MonadEff (http ∷ HTTP | e) m
⇒ Status
→ HTTP.Response
→ Middleware m (Conn req res c) (Conn req res c) Unit
setStatus (Status { code, reasonPhrase }) r = liftEff do
HTTP.setStatusCode r code
HTTP.setStatusMessage r reasonPhrase
writeHeader' ∷ ∀ req res c m e.
MonadEff (http ∷ HTTP | e) m
⇒ (Tuple String String)
→ HTTP.Response
→ Middleware m (Conn req res c) (Conn req res c) Unit
writeHeader' (Tuple name value) r =
liftEff $ HTTP.setHeader r name value
writeResponse ∷ ∀ req res c m e.
MonadAff (http ∷ HTTP | e) m
⇒ HTTP.Response
→ NodeResponse m (http :: HTTP | e)
→ Middleware m (Conn req res c) (Conn req res c) Unit
writeResponse r (NodeResponse f) =
lift' (f (HTTP.responseAsStream r))
endResponse ∷ ∀ req res c m e.
MonadEff (http ∷ HTTP | e) m
⇒ HTTP.Response
→ Middleware m (Conn req res c) (Conn req res c) Unit
endResponse r =
liftEff (Stream.end (HTTP.responseAsStream r) (pure unit))
instance responseWriterHttpResponse :: MonadAff (http ∷ HTTP | e) m
=> Response HttpResponse m (NodeResponse m (http :: HTTP | e)) where
writeStatus status =
getConn :>>= \{ response: HttpResponse r } ->
setStatus status r
:*> modifyConn (_ { response = HttpResponse r })
writeHeader header =
getConn :>>= \{ response: HttpResponse r } ->
writeHeader' header r
:*> modifyConn (_ { response = HttpResponse r })
closeHeaders =
getConn :>>= \{ response: HttpResponse r } ->
modifyConn (_ { response = HttpResponse r })
send f =
getConn :>>= \{ response: HttpResponse r } ->
writeResponse r f
:*> modifyConn (_ { response = HttpResponse r })
end =
getConn :>>= \{ response: HttpResponse r } ->
endResponse r
:*> modifyConn (_ { response = HttpResponse r })
type ServerOptions e =
{ hostname ∷ String
, port ∷ Port
, onListening ∷ Port → Eff (http ∷ HTTP | e) Unit
, onRequestError ∷ Error → Eff (http ∷ HTTP | e) Unit
}
defaultOptions ∷ ∀ e. ServerOptions e
defaultOptions =
{ hostname: "0.0.0.0"
, port: Port 3000
, onListening: const (pure unit)
, onRequestError: const (pure unit)
}
defaultOptionsWithLogging ∷ ∀ e. ServerOptions (console ∷ CONSOLE | e)
defaultOptionsWithLogging =
defaultOptions { onListening = onListening
, onRequestError = onRequestError
}
where
onListening (Port port) =
log ("Listening on http://localhost:" <> show port)
onRequestError err =
log ("Request failed: " <> show err)
mkHttpRequest :: HTTP.Request -> HttpRequest
mkHttpRequest request =
HttpRequest request requestData
where
headers = HTTP.requestHeaders request
requestData =
{ url: HTTP.requestURL request
, headers: headers
, method: Method.fromString (HTTP.requestMethod request)
, contentLength: StrMap.lookup "content-length" headers
>>= Int.fromString
}
runServer'
:: forall m e c c'
. Functor m
=> ServerOptions e
-> c
-> (forall a. m a -> Aff (http :: HTTP | e) a)
-> Middleware
m
(Conn HttpRequest (HttpResponse StatusLineOpen) c)
(Conn HttpRequest (HttpResponse ResponseEnded) c')
Unit
-> Eff (http :: HTTP | e) Unit
runServer' options components runM middleware = do
server <- HTTP.createServer onRequest
let listenOptions = { port: unwrap options.port
, hostname: "0.0.0.0"
, backlog: Nothing
}
HTTP.listen server listenOptions (options.onListening options.port)
where
onRequest ∷ HTTP.Request → HTTP.Response → Eff (http :: HTTP | e) Unit
onRequest request response =
let conn = { request: mkHttpRequest request
, response: HttpResponse response
, components: components
}
in catchException options.onRequestError (void (launchAff (runM (evalMiddleware middleware conn))))
runServer
:: forall e c c'.
ServerOptions e
-> c
-> Middleware
(Aff (http :: HTTP | e))
(Conn HttpRequest (HttpResponse StatusLineOpen) c)
(Conn HttpRequest (HttpResponse ResponseEnded) c')
Unit
-> Eff (http :: HTTP | e) Unit
runServer options components middleware =
runServer' options components id middleware