-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathServer.purs
More file actions
275 lines (242 loc) · 9.63 KB
/
Server.purs
File metadata and controls
275 lines (242 loc) · 9.63 KB
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
265
266
267
268
269
270
271
272
273
274
275
module Hyper.Node.Server
( HttpRequest
, HttpResponse
, NodeResponse
, writeString
, write
, module Hyper.Node.Server.Options
, runServer
, runServer'
) where
import Prelude
import Control.IxMonad (ipure, (:*>), (:>>=))
import Control.Monad.Aff (Aff, launchAff, launchAff_, makeAff, nonCanceler, runAff_)
import Control.Monad.Aff.AVar (AVAR, makeEmptyVar, makeVar, putVar, takeVar)
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.Exception (EXCEPTION, catchException)
import Control.Monad.Error.Class (throwError)
import Data.Either (Either(..), either)
import Data.HTTP.Method as Method
import Data.Int as Int
import Data.Lazy (defer)
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap)
import Data.StrMap as StrMap
import Data.Tuple (Tuple(..))
import Hyper.Conn (Conn)
import Hyper.Form.Urlencoded (Options) as Urlencoded
import Hyper.Middleware (Middleware, evalMiddleware, lift')
import Hyper.Middleware.Class (getConn, modifyConn)
import Hyper.Node.Server.Options (Options)
import Hyper.Node.Server.Options as Hyper.Node.Server.Options
import Hyper.Request (class ReadableBody, class Request, class StreamableBody, RequestData, parseUrl, readBody)
import Hyper.Response (class ResponseWritable, class Response, ResponseEnded, StatusLineOpen)
import Hyper.Status (Status(..))
import Node.Buffer (BUFFER, Buffer)
import Node.Buffer as Buffer
import Node.Encoding (Encoding(..))
import Node.HTTP (HTTP)
import Node.HTTP as HTTP
import Node.Stream (Stream, Writable)
import Node.Stream as Stream
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 (\k -> Stream.writeString w enc str (k (pure unit))
*> pure nonCanceler))
write :: forall m e. MonadAff e m => Buffer -> NodeResponse m e
write buffer = NodeResponse $ \w ->
liftAff (makeAff (\k -> Stream.write w buffer (k (pure unit))
*> pure nonCanceler))
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)
-- Helper function that reads a Stream into a Buffer, and throws error
-- in `Aff` when failed.
readBodyAsBuffer
:: forall e.
HttpRequest
-> Aff (http :: HTTP, avar :: AVAR, buffer :: BUFFER | e) Buffer
readBodyAsBuffer (HttpRequest request _) = do
let stream = HTTP.requestAsStream request
bodyResult <- makeEmptyVar
chunks <- makeVar []
fillResult <- liftEff $
catchException (pure <<< Left) (Right <$> fillBody stream chunks bodyResult)
-- Await the body, or an error.
body <- takeVar bodyResult
-- Return the body, if neither `fillResult` nor `body` is a `Left`.
either throwError pure (fillResult *> body)
where
fillBody stream chunks bodyResult = do
-- Append all chunks to the body buffer.
Stream.onData stream \chunk ->
let modification = do
v <- takeVar chunks
putVar (v <> [chunk]) chunks
in void (launchAff modification)
-- Complete with `Left` on error.
Stream.onError stream $
launchAff_ <<< flip putVar bodyResult <<< Left
-- Complete with `Right` on successful "end" event.
Stream.onEnd stream $ void $ launchAff $
takeVar chunks
>>= concat'
>>= (pure <<< Right)
>>= flip putVar bodyResult
concat' = liftEff <<< Buffer.concat
instance readableBodyHttpRequestString :: (Monad m, MonadAff (http :: HTTP, avar :: AVAR, buffer :: BUFFER | e) m)
=> ReadableBody HttpRequest m String where
readBody =
readBody :>>= (liftEff <<< Buffer.toString UTF8)
instance readableBodyHttpRequestBuffer :: (Monad m, MonadAff (http :: HTTP, avar :: AVAR, buffer :: BUFFER | e) m)
=> ReadableBody HttpRequest m Buffer where
readBody =
_.request <$> getConn :>>=
case _ of
r -> liftAff (readBodyAsBuffer r)
instance streamableBodyHttpRequestReadable :: MonadAff (http :: HTTP | e) m
=> StreamableBody
HttpRequest
m
(Stream (read :: Stream.Read) (http :: HTTP, exception :: EXCEPTION | e)) where
streamBody =
_.request <$> getConn :>>=
case _ of
HttpRequest request _ -> ipure (HTTP.requestAsStream request)
-- 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 })
mkHttpRequest :: Urlencoded.Options -> HTTP.Request -> HttpRequest
mkHttpRequest opts request =
HttpRequest request requestData
where
headers = HTTP.requestHeaders request
requestData =
{ url: HTTP.requestURL request
, parsedUrl: defer \_ -> parseUrl opts (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
=> Options 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: unwrap options.hostname
, backlog: Nothing
}
HTTP.listen server listenOptions (options.onListening options.hostname options.port)
where
onRequest ∷ HTTP.Request → HTTP.Response → Eff (http :: HTTP | e) Unit
onRequest request response =
let conn = { request: mkHttpRequest {replacePlus: options.replacePlus} request
, response: HttpResponse response
, components: components
}
callback =
case _ of
Left err -> options.onRequestError err
Right _ -> pure unit
in conn
# evalMiddleware middleware
# runM
# runAff_ callback
runServer
:: forall e c c'.
Options 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