Skip to content

Commit 0929ea2

Browse files
committed
Add streamBody for requests
1 parent 60bde7a commit 0929ea2

File tree

3 files changed

+97
-5
lines changed

3 files changed

+97
-5
lines changed

examples/NodeStreamRequest.purs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
-- This example shows how you can stream request body data. It
2+
-- logs the size of each chunk it receives from the POST body.
3+
--
4+
-- Test it out by first running the server,
5+
--
6+
-- $ pulp run -I examples -m Examples.NodeStreamRequest
7+
--
8+
-- and then, POST a large file with something like this command:
9+
--
10+
-- $ curl -X POST --data-binary @/your/large/file localhost:3000
11+
--
12+
module Examples.NodeStreamRequest where
13+
14+
import Prelude
15+
import Node.Buffer as Buffer
16+
import Node.Stream as Stream
17+
import Control.IxMonad (ibind, (:>>=))
18+
import Control.Monad.Eff (Eff)
19+
import Control.Monad.Eff.Class (class MonadEff, liftEff)
20+
import Control.Monad.Eff.Console (CONSOLE, log)
21+
import Control.Monad.Eff.Exception (EXCEPTION, catchException, message)
22+
import Data.Either (Either(..), either)
23+
import Data.HTTP.Method (Method(..))
24+
import Hyper.Node.Server (defaultOptionsWithLogging, runServer)
25+
import Hyper.Request (getRequestData, streamBody)
26+
import Hyper.Response (closeHeaders, respond, writeStatus)
27+
import Hyper.Status (statusMethodNotAllowed, statusOK)
28+
import Node.Buffer (BUFFER)
29+
import Node.HTTP (HTTP)
30+
31+
type ExampleEffects e = (http :: HTTP, console :: CONSOLE, buffer :: BUFFER | e)
32+
33+
logRequestBodyChunks
34+
:: forall m e
35+
. MonadEff (ExampleEffects e) m
36+
=> Stream.Readable () (ExampleEffects (exception :: EXCEPTION | e))
37+
-> m Unit
38+
logRequestBodyChunks body =
39+
Stream.onData body (Buffer.size >=> (log <<< ("Got chunk of size: " <> _) <<< show))
40+
# catchException (log <<< ("Error: " <> _) <<< message)
41+
# liftEff
42+
43+
main :: forall e. Eff (ExampleEffects e) Unit
44+
main =
45+
let
46+
app =
47+
getRequestData :>>=
48+
case _ of
49+
50+
-- Only handle POST requests:
51+
{ method: Left POST } -> do
52+
body <- streamBody
53+
logRequestBodyChunks body
54+
writeStatus statusOK
55+
closeHeaders
56+
respond "OK"
57+
58+
-- Non-POST requests are not allowed:
59+
{ method } -> do
60+
writeStatus statusMethodNotAllowed
61+
closeHeaders
62+
respond ("Method not allowed: " <> either show show method)
63+
64+
where
65+
bind = ibind
66+
discard = ibind
67+
in runServer defaultOptionsWithLogging {} app

src/Hyper/Node/Server.purs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import Control.Monad.Aff.Class (class MonadAff, liftAff)
2424
import Control.Monad.Eff (Eff)
2525
import Control.Monad.Eff.Class (class MonadEff, liftEff)
2626
import Control.Monad.Eff.Console (CONSOLE, log)
27-
import Control.Monad.Eff.Exception (Error, catchException, error)
27+
import Control.Monad.Eff.Exception (EXCEPTION, Error, catchException, error)
2828
import Control.Monad.Error.Class (throwError)
2929
import Data.Either (Either(..), either)
3030
import Data.Maybe (Maybe(..))
@@ -34,13 +34,13 @@ import Hyper.Conn (Conn)
3434
import Hyper.Middleware (Middleware, evalMiddleware, lift')
3535
import Hyper.Middleware.Class (getConn, modifyConn)
3636
import Hyper.Port (Port(..))
37-
import Hyper.Request (class ReadableBody, class Request, RequestData, readBody)
37+
import Hyper.Request (class ReadableBody, class Request, class StreamableBody, RequestData, readBody)
3838
import Hyper.Response (class ResponseWritable, class Response, ResponseEnded, StatusLineOpen)
3939
import Hyper.Status (Status(..))
4040
import Node.Buffer (BUFFER, Buffer)
4141
import Node.Encoding (Encoding(..))
4242
import Node.HTTP (HTTP)
43-
import Node.Stream (Writable)
43+
import Node.Stream (Stream, Writable)
4444

4545

4646
data HttpRequest
@@ -128,6 +128,16 @@ instance readableBodyHttpRequestBuffer :: (Monad m, MonadAff (http :: HTTP, avar
128128
case _ of
129129
r -> liftAff (readBodyAsBuffer r)
130130

131+
instance streamableBodyHttpRequestReadable :: MonadAff (http :: HTTP | e) m
132+
=> StreamableBody
133+
HttpRequest
134+
m
135+
(Stream (read :: Stream.Read) (http :: HTTP, exception :: EXCEPTION | e)) where
136+
streamBody =
137+
_.request <$> getConn :>>=
138+
case _ of
139+
HttpRequest request _ -> ipure (HTTP.requestAsStream request)
140+
131141
-- TODO: Make a newtype
132142
data HttpResponse state = HttpResponse HTTP.Response
133143

src/Hyper/Request.purs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module Hyper.Request
55
, class BaseRequest
66
, class ReadableBody
77
, readBody
8+
, class StreamableBody
9+
, streamBody
810
) where
911

1012
import Data.Either (Either)
@@ -32,8 +34,9 @@ class Request req m where
3234

3335
class Request req m <= BaseRequest req m
3436

35-
-- | A ReadableBody instance reads the request body for a specific body
36-
-- | type.
37+
-- | A `ReadableBody` instance reads the complete request body as a
38+
-- | value of type `b`. For streaming the request body, see the
39+
-- | [StreamableBody](#streamablebody) class.
3740
class ReadableBody req m b where
3841
readBody
3942
:: forall res c
@@ -42,3 +45,15 @@ class ReadableBody req m b where
4245
(Conn req res c)
4346
(Conn req res c)
4447
b
48+
49+
-- | A `StreamableBody` instance returns a stream of the request body,
50+
-- | of type `stream`. To read the whole body as a value, without
51+
-- | streaming, see the [ReadableBody](#readablebody) class.
52+
class StreamableBody req m stream | req -> stream where
53+
streamBody
54+
:: forall res c
55+
. Middleware
56+
m
57+
(Conn req res c)
58+
(Conn req res c)
59+
stream

0 commit comments

Comments
 (0)