Make ResponseM async (#71)

This commit is contained in:
Connor Prussin 2017-09-27 06:55:36 -07:00 committed by GitHub
parent 2329c6eed8
commit d50be71fdc
15 changed files with 112 additions and 55 deletions

View File

@ -27,6 +27,7 @@
"devDependencies": { "devDependencies": {
"purescript-psci-support": "^3.0.0", "purescript-psci-support": "^3.0.0",
"purescript-spec": "^1.0.0", "purescript-spec": "^1.0.0",
"purescript-unsafe-coerce": "^3.0.0" "purescript-unsafe-coerce": "^3.0.0",
"purescript-node-fs-aff": "^4.0.0"
} }
} }

View File

@ -0,0 +1 @@
hello world!

View File

@ -0,0 +1,35 @@
module AsyncResponse where
import Prelude
import Control.Monad.Eff.Console as Console
import HTTPure as HTTPure
import Node.Encoding as Encoding
import Node.FS as FS
import Node.FS.Aff as FSAff
-- | Serve the example server on this port
port :: Int
port = 8088
-- | Shortcut for `show port`
portS :: String
portS = show port
-- | The path to the file containing the response to send
filePath :: String
filePath = "./docs/Examples/AsyncResponse/Hello"
-- | Say 'hello world!' when run
sayHello :: forall e. HTTPure.Request -> HTTPure.ResponseM (fs :: FS.FS | e)
sayHello _ = FSAff.readTextFile Encoding.UTF8 filePath >>= HTTPure.ok
-- | Boot up the server
main :: forall e. HTTPure.ServerM (console :: Console.CONSOLE, fs :: FS.FS | e)
main = HTTPure.serve port sayHello do
Console.log $ " ┌────────────────────────────────────────────┐"
Console.log $ " │ Server now up on port " <> portS <> ""
Console.log $ " │ │"
Console.log $ " │ To test, run: │"
Console.log $ " │ > curl localhost:" <> portS <> " # => hello world! │"
Console.log $ " └────────────────────────────────────────────┘"

View File

@ -0,0 +1,12 @@
# Async Response Example
This is a basic 'hello world' example, that responds by asynchronously reading a
file off the filesystem. It simply returns 'hello world!' when making any
request, but the 'hello world!' text is fetched by reading the contents of the
file [Hello](./Hello).
To run the example server, run:
```bash
make example EXAMPLE=AsyncResponse
```

View File

@ -13,14 +13,14 @@ import Node.Encoding as Encoding
import Node.HTTP as HTTP import Node.HTTP as HTTP
import Node.Stream as Stream import Node.Stream as Stream
import HTTPure.HTTPureM as HTTPureM import HTTPure.HTTPureEffects as HTTPureEffects
-- | The `Body` type is just sugar for a `String`, that will be sent or received -- | The `Body` type is just sugar for a `String`, that will be sent or received
-- | in the HTTP body. -- | in the HTTP body.
type Body = String type Body = String
-- | Extract the contents of the body of the HTTP `Request`. -- | Extract the contents of the body of the HTTP `Request`.
read :: forall e. HTTP.Request -> Aff.Aff (HTTPureM.HTTPureEffects e) Body read :: forall e. HTTP.Request -> Aff.Aff (HTTPureEffects.HTTPureEffects e) Body
read request = Aff.makeAff \_ success -> do read request = Aff.makeAff \_ success -> do
let stream = HTTP.requestAsStream request let stream = HTTP.requestAsStream request
buf <- ST.newSTRef "" buf <- ST.newSTRef ""

View File

@ -0,0 +1,15 @@
module HTTPure.HTTPureEffects
( HTTPureEffects
) where
import Control.Monad.Eff.Exception as Exception
import Control.Monad.ST as ST
import Node.HTTP as HTTP
-- | A row of types that are used by an HTTPure server.
type HTTPureEffects e =
( http :: HTTP.HTTP
, st :: ST.ST String
, exception :: Exception.EXCEPTION
| e
)

View File

@ -1,22 +0,0 @@
module HTTPure.HTTPureM
( HTTPureM
, HTTPureEffects
) where
import Control.Monad.Eff as Eff
import Control.Monad.Eff.Exception as Exception
import Control.Monad.ST as ST
import Node.HTTP as HTTP
-- | A row of types that are used by an HTTPure server.
type HTTPureEffects e =
( http :: HTTP.HTTP
, st :: ST.ST String
, exception :: Exception.EXCEPTION
| e
)
-- | The `HTTPureM` monad represents effects run by an HTTPure server. It takes
-- | an effects row parameter which enumerates all other side-effects performed
-- | while carrying out the server actions.
type HTTPureM e t = Eff.Eff (HTTPureEffects e) t

View File

@ -10,7 +10,7 @@ import Node.HTTP as HTTP
import HTTPure.Body as Body import HTTPure.Body as Body
import HTTPure.Headers as Headers import HTTPure.Headers as Headers
import HTTPure.HTTPureM as HTTPureM import HTTPure.HTTPureEffects as HTTPureEffects
import HTTPure.Method as Method import HTTPure.Method as Method
import HTTPure.Path as Path import HTTPure.Path as Path
import HTTPure.Query as Query import HTTPure.Query as Query
@ -29,7 +29,7 @@ type Request =
-- | `Request` object. -- | `Request` object.
fromHTTPRequest :: forall e. fromHTTPRequest :: forall e.
HTTP.Request -> HTTP.Request ->
Aff.Aff (HTTPureM.HTTPureEffects e) Request Aff.Aff (HTTPureEffects.HTTPureEffects e) Request
fromHTTPRequest request = do fromHTTPRequest request = do
body <- Body.read request body <- Body.read request
pure $ pure $

View File

@ -77,17 +77,19 @@ module HTTPure.Response
import Prelude import Prelude
import Control.Monad.Eff as Eff
import Control.Monad.Aff as Aff
import Node.HTTP as HTTP import Node.HTTP as HTTP
import HTTPure.Body as Body import HTTPure.Body as Body
import HTTPure.Headers as Headers import HTTPure.Headers as Headers
import HTTPure.HTTPureM as HTTPureM import HTTPure.HTTPureEffects as HTTPureEffects
import HTTPure.Status as Status import HTTPure.Status as Status
-- | The `ResponseM` type simply conveniently wraps up an HTTPure monad that -- | The `ResponseM` type simply conveniently wraps up an HTTPure monad that
-- | returns a response. This type is the return type of all router/route -- | returns a response. This type is the return type of all router/route
-- | methods. -- | methods.
type ResponseM e = HTTPureM.HTTPureM e Response type ResponseM e = Aff.Aff (HTTPureEffects.HTTPureEffects e) Response
-- | A `Response` is a status code, headers, and a body. -- | A `Response` is a status code, headers, and a body.
data Response = Response Status.Status Headers.Headers Body.Body data Response = Response Status.Status Headers.Headers Body.Body
@ -95,7 +97,10 @@ data Response = Response Status.Status Headers.Headers Body.Body
-- | Given an HTTP `Response` and a HTTPure `Response`, this method will return -- | Given an HTTP `Response` and a HTTPure `Response`, this method will return
-- | a monad encapsulating writing the HTTPure `Response` to the HTTP `Response` -- | a monad encapsulating writing the HTTPure `Response` to the HTTP `Response`
-- | and closing the HTTP `Response`. -- | and closing the HTTP `Response`.
send :: forall e. HTTP.Response -> Response -> HTTPureM.HTTPureM e Unit send :: forall e.
HTTP.Response ->
Response ->
Eff.Eff (HTTPureEffects.HTTPureEffects e) Unit
send httpresponse (Response status headers body) = do send httpresponse (Response status headers body) = do
Status.write httpresponse $ status Status.write httpresponse $ status
Headers.write httpresponse $ headers Headers.write httpresponse $ headers

View File

@ -8,6 +8,7 @@ module HTTPure.Server
import Prelude import Prelude
import Control.Monad.Aff as Aff import Control.Monad.Aff as Aff
import Control.Monad.Eff as Eff
import Control.Monad.Eff.Class as EffClass import Control.Monad.Eff.Class as EffClass
import Data.Maybe as Maybe import Data.Maybe as Maybe
import Data.Options ((:=)) import Data.Options ((:=))
@ -17,14 +18,14 @@ import Node.FS.Sync as FSSync
import Node.HTTP as HTTP import Node.HTTP as HTTP
import Node.HTTP.Secure as HTTPS import Node.HTTP.Secure as HTTPS
import HTTPure.HTTPureM as HTTPureM import HTTPure.HTTPureEffects as HTTPureEffects
import HTTPure.Request as Request import HTTPure.Request as Request
import HTTPure.Response as Response import HTTPure.Response as Response
-- | The `ServerM` type simply conveniently wraps up an HTTPure monad that -- | The `ServerM` type simply conveniently wraps up an HTTPure monad that
-- | returns a `Unit`. This type is the return type of the HTTPure serve and -- | returns a `Unit`. This type is the return type of the HTTPure serve and
-- | related methods. -- | related methods.
type ServerM e = HTTPureM.HTTPureM e Unit type ServerM e = Eff.Eff (HTTPureEffects.HTTPureEffects e) Unit
-- | The `SecureServerM` type is the same as the `ServerM` type, but it includes -- | The `SecureServerM` type is the same as the `ServerM` type, but it includes
-- | effects for working with the filesystem (to load the key and certificate). -- | effects for working with the filesystem (to load the key and certificate).
@ -42,11 +43,11 @@ handleRequest :: forall e.
handleRequest router request response = handleRequest router request response =
void $ Aff.runAff (\_ -> pure unit) (\_ -> pure unit) do void $ Aff.runAff (\_ -> pure unit) (\_ -> pure unit) do
req <- Request.fromHTTPRequest request req <- Request.fromHTTPRequest request
EffClass.liftEff $ router req >>= Response.send response router req >>= Response.send response >>> EffClass.liftEff
-- | Given a `ListenOptions` object, a function mapping `Request` to -- | Given a `ListenOptions` object, a function mapping `Request` to
-- | `ResponseM`, and an `HTTPureM` containing effects to run on boot, creates -- | `ResponseM`, and a `ServerM` containing effects to run on boot, creates and
-- | and runs a HTTPure server without SSL. -- | runs a HTTPure server without SSL.
bootHTTP :: forall e. bootHTTP :: forall e.
HTTP.ListenOptions -> HTTP.ListenOptions ->
(Request.Request -> Response.ResponseM e) -> (Request.Request -> Response.ResponseM e) ->
@ -57,9 +58,9 @@ bootHTTP options router onStarted =
HTTP.listen server options onStarted HTTP.listen server options onStarted
-- | Given a `ListenOptions` object, a path to a cert file, a path to a private -- | Given a `ListenOptions` object, a path to a cert file, a path to a private
-- | key file, a function mapping `Request` to `ResponseM`, and an `HTTPureM` -- | key file, a function mapping `Request` to `ResponseM`, and a
-- | containing effects to run on boot, creates and runs a HTTPure server with -- | `SecureServerM` containing effects to run on boot, creates and runs a
-- | SSL. -- | HTTPure server with SSL.
bootHTTPS :: forall e. bootHTTPS :: forall e.
HTTP.ListenOptions -> HTTP.ListenOptions ->
String -> String ->
@ -87,8 +88,8 @@ listenOptions port =
-- | Create and start a server. This is the main entry point for HTTPure. Takes -- | Create and start a server. This is the main entry point for HTTPure. Takes
-- | a port number on which to listen, a function mapping `Request` to -- | a port number on which to listen, a function mapping `Request` to
-- | `ResponseM`, and an `HTTPureM` containing effects to run after the server -- | `ResponseM`, and a `ServerM` containing effects to run after the server has
-- | has booted (usually logging). Returns an `HTTPureM` containing the server's -- | booted (usually logging). Returns an `ServerM` containing the server's
-- | effects. -- | effects.
serve :: forall e. serve :: forall e.
Int -> Int ->

View File

@ -0,0 +1,11 @@
module HTTPure.HTTPureEffectsSpec where
import Prelude
import Test.Spec as Spec
import HTTPure.SpecHelpers as SpecHelpers
httpureEffectsSpec :: SpecHelpers.Test
httpureEffectsSpec = Spec.describe "HTTPureEffects" do
pure unit

View File

@ -1,11 +0,0 @@
module HTTPure.HTTPureMSpec where
import Prelude
import Test.Spec as Spec
import HTTPure.SpecHelpers as SpecHelpers
httpureMSpec :: SpecHelpers.Test
httpureMSpec = Spec.describe "HTTPureM" do
pure unit

View File

@ -9,6 +9,7 @@ import Test.Spec as Spec
import HTTPure.SpecHelpers as SpecHelpers import HTTPure.SpecHelpers as SpecHelpers
import HTTPure.SpecHelpers ((?=)) import HTTPure.SpecHelpers ((?=))
import AsyncResponse as AsyncResponse
import Headers as Headers import Headers as Headers
import HelloWorld as HelloWorld import HelloWorld as HelloWorld
import MultiRoute as MultiRoute import MultiRoute as MultiRoute
@ -17,6 +18,13 @@ import QueryParameters as QueryParameters
import Post as Post import Post as Post
import SSL as SSL import SSL as SSL
asyncResponseSpec :: SpecHelpers.Test
asyncResponseSpec = Spec.it "runs the async response example" do
EffClass.liftEff AsyncResponse.main
response <- SpecHelpers.get port StrMap.empty "/"
response ?= "hello world!"
where port = AsyncResponse.port
headersSpec :: SpecHelpers.Test headersSpec :: SpecHelpers.Test
headersSpec = Spec.it "runs the headers example" do headersSpec = Spec.it "runs the headers example" do
EffClass.liftEff Headers.main EffClass.liftEff Headers.main
@ -80,6 +88,7 @@ sslSpec = Spec.it "runs the ssl example" do
integrationSpec :: SpecHelpers.Test integrationSpec :: SpecHelpers.Test
integrationSpec = Spec.describe "Integration" do integrationSpec = Spec.describe "Integration" do
asyncResponseSpec
headersSpec headersSpec
helloWorldSpec helloWorldSpec
multiRouteSpec multiRouteSpec

View File

@ -50,7 +50,7 @@ responseFunctionSpec = Spec.describe "response" do
case resp of (Response.Response _ _ body) -> body ?= "test" case resp of (Response.Response _ _ body) -> body ?= "test"
where where
mockHeaders = Headers.headers [ Tuple.Tuple "Test" "test" ] mockHeaders = Headers.headers [ Tuple.Tuple "Test" "test" ]
mockResponse = EffClass.liftEff $ Response.response 123 mockHeaders "test" mockResponse = Response.response 123 mockHeaders "test"
response'Spec :: SpecHelpers.Test response'Spec :: SpecHelpers.Test
response'Spec = Spec.describe "response'" do response'Spec = Spec.describe "response'" do
@ -65,7 +65,7 @@ response'Spec = Spec.describe "response'" do
case resp of (Response.Response _ _ body) -> body ?= "" case resp of (Response.Response _ _ body) -> body ?= ""
where where
mockHeaders = Headers.headers [ Tuple.Tuple "Test" "test" ] mockHeaders = Headers.headers [ Tuple.Tuple "Test" "test" ]
mockResponse = EffClass.liftEff $ Response.response' 123 mockHeaders mockResponse = Response.response' 123 mockHeaders
responseSpec :: SpecHelpers.Test responseSpec :: SpecHelpers.Test
responseSpec = Spec.describe "Response" do responseSpec = Spec.describe "Response" do

View File

@ -8,7 +8,7 @@ import Test.Spec.Runner as Runner
import HTTPure.BodySpec as BodySpec import HTTPure.BodySpec as BodySpec
import HTTPure.HeadersSpec as HeadersSpec import HTTPure.HeadersSpec as HeadersSpec
import HTTPure.HTTPureMSpec as HTTPureMSpec import HTTPure.HTTPureEffectsSpec as HTTPureEffectsSpec
import HTTPure.LookupSpec as LookupSpec import HTTPure.LookupSpec as LookupSpec
import HTTPure.MethodSpec as MethodSpec import HTTPure.MethodSpec as MethodSpec
import HTTPure.PathSpec as PathSpec import HTTPure.PathSpec as PathSpec
@ -25,7 +25,7 @@ main :: SpecHelpers.TestSuite
main = Runner.run [ Reporter.specReporter ] $ Spec.describe "HTTPure" do main = Runner.run [ Reporter.specReporter ] $ Spec.describe "HTTPure" do
BodySpec.bodySpec BodySpec.bodySpec
HeadersSpec.headersSpec HeadersSpec.headersSpec
HTTPureMSpec.httpureMSpec HTTPureEffectsSpec.httpureEffectsSpec
LookupSpec.lookupSpec LookupSpec.lookupSpec
MethodSpec.methodSpec MethodSpec.methodSpec
PathSpec.pathSpec PathSpec.pathSpec