Update HTTPure to routing duplex

This commit is contained in:
sigma-andex 2022-05-22 12:30:14 +01:00
parent 24197a474a
commit 9a8f34cf73
19 changed files with 390 additions and 166 deletions

View File

@ -2,23 +2,35 @@ module Examples.AsyncResponse.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serve)
import Node.Encoding (Encoding(UTF8))
import Node.FS.Aff (readTextFile)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | The path to the file containing the response to send
filePath :: String
filePath = "./docs/Examples/AsyncResponse/Hello"
-- | Say 'hello world!' when run
sayHello :: Request -> ResponseM
sayHello = const $ readTextFile UTF8 filePath >>= ok
router :: Request Route -> ResponseM
router { route: SayHello } = readTextFile UTF8 filePath >>= ok
-- | Boot up the server
main :: ServerM
main =
serve 8080 sayHello do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌────────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,20 +2,33 @@ module Examples.BinaryRequest.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serve, toBuffer)
import Node.Buffer (Buffer)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
foreign import sha256sum :: Buffer -> String
-- | Respond with file's sha256sum
router :: Request -> ResponseM
router :: Request Route -> ResponseM
router { body } = toBuffer body >>= sha256sum >>> ok
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌─────────────────────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

View File

@ -2,10 +2,22 @@ module Examples.BinaryResponse.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Headers, Request, ResponseM, ServerM, header, ok', serve)
import Node.FS.Aff (readFile)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | The path to the file containing the response to send
filePath :: String
filePath = "./docs/Examples/BinaryResponse/circle.png"
@ -14,13 +26,13 @@ responseHeaders :: Headers
responseHeaders = header "Content-Type" "image/png"
-- | Respond with image data when run
image :: Request -> ResponseM
image = const $ readFile filePath >>= ok' responseHeaders
router :: Request Route -> ResponseM
router = const $ readFile filePath >>= ok' responseHeaders
-- | Boot up the server
main :: ServerM
main =
serve 8080 image do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌──────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,12 +2,25 @@ module Examples.Chunked.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Aff (Aff)
import Effect.Class (liftEffect)
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serve)
import Node.ChildProcess (defaultSpawnOptions, spawn, stdout)
import Node.Stream (Readable)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | Run a script and return it's stdout stream
runScript :: String -> Aff (Readable ())
@ -15,13 +28,13 @@ runScript script =
liftEffect $ stdout <$> spawn "sh" [ "-c", script ] defaultSpawnOptions
-- | Say 'hello world!' in chunks when run
sayHello :: Request -> ResponseM
sayHello = const $ runScript "echo 'hello '; sleep 1; echo 'world!'" >>= ok
router :: Request Route -> ResponseM
router = const $ runScript "echo 'hello '; sleep 1; echo 'world!'" >>= ok
-- | Boot up the server
main :: ServerM
main =
serve 8080 sayHello do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌──────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -3,24 +3,38 @@ module Examples.CustomStack.Main where
import Prelude
import Control.Monad.Reader (class MonadAsk, ReaderT, asks, runReaderT)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Aff (Aff)
import Effect.Aff.Class (class MonadAff)
import Effect.Console (log)
import HTTPure (Request, Response, ResponseM, ServerM, ok, serve)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | A type to hold the environment for our ReaderT
type Env = { name :: String }
-- | A middleware that introduces a ReaderT
readerMiddleware ::
(Request -> ReaderT Env Aff Response) ->
Request ->
forall route.
(Request route -> ReaderT Env Aff Response) ->
Request route ->
ResponseM
readerMiddleware router request = do
runReaderT (router request) { name: "joe" }
-- | Say 'hello, joe' when run
sayHello :: forall m. MonadAff m => MonadAsk Env m => Request -> m Response
sayHello :: forall m. MonadAff m => MonadAsk Env m => Request Route -> m Response
sayHello _ = do
name <- asks _.name
ok $ "hello, " <> name
@ -28,7 +42,7 @@ sayHello _ = do
-- | Boot up the server
main :: ServerM
main =
serve 8080 (readerMiddleware sayHello) do
serve 8080 { route, router: readerMiddleware sayHello, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,21 +2,35 @@ module Examples.Headers.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Headers, Request, ResponseM, ServerM, header, ok', serve, (!@))
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | The headers that will be included in every response.
responseHeaders :: Headers
responseHeaders = header "X-Example" "hello world!"
-- | Route to the correct handler
router :: Request -> ResponseM
router :: Request Route -> ResponseM
router { headers } = ok' responseHeaders $ headers !@ "X-Input"
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing} do
log " ┌──────────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,13 +2,26 @@ module Examples.HelloWorld.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (ServerM, ok, serve)
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
data Route = SayHello
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | Boot up the server
main :: ServerM
main =
serve 8080 (const $ ok "hello world!") do
serve 8080 { route, router: const $ ok "hello world!", notFoundHandler: Nothing } do
log " ┌────────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -1,15 +1,42 @@
module Examples.Middleware.Main where
import Prelude
import Prelude hiding ((/))
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Class (liftEffect)
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, fullPath, header, ok, ok', serve)
import HTTPure (type (<+>), Request, ResponseM, ServerM, fullPath, header, ok, ok', serve, (<+>))
import Record as Record
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
import Routing.Duplex.Generic.Syntax ((/))
import Type.Prelude (Proxy(..))
data Middleware = Middleware
derive instance Generic Middleware _
middlewareRoute :: RD.RouteDuplex' Middleware
middlewareRoute = RD.root $ RG.sum
{ "Middleware": "middleware" / RG.noArgs
}
data SayHello = SayHello
derive instance Generic SayHello _
sayHelloRoute :: RD.RouteDuplex' SayHello
sayHelloRoute = RD.root $ RG.sum
{ "SayHello": RG.noArgs
}
-- | A middleware that logs at the beginning and end of each request
loggingMiddleware ::
(Request -> ResponseM) ->
Request ->
loggingMiddleware :: forall route.
(Request route -> ResponseM) ->
Request route ->
ResponseM
loggingMiddleware router request = do
liftEffect $ log $ "Request starting for " <> path
@ -21,9 +48,9 @@ loggingMiddleware router request = do
-- | A middleware that adds the X-Middleware header to the response, if it
-- | wasn't already in the response
headerMiddleware ::
(Request -> ResponseM) ->
Request ->
headerMiddleware :: forall route.
(Request route -> ResponseM) ->
Request route ->
ResponseM
headerMiddleware router request = do
response@{ headers } <- router request
@ -33,25 +60,26 @@ headerMiddleware router request = do
-- | A middleware that sends the body "Middleware!" instead of running the
-- | router when requesting /middleware
pathMiddleware ::
(Request -> ResponseM) ->
Request ->
pathMiddleware :: forall route.
(Request route -> ResponseM) ->
Request (Middleware <+> route ) ->
ResponseM
pathMiddleware _ { path: [ "middleware" ] } = ok "Middleware!"
pathMiddleware router request = router request
pathMiddleware _ { route: Left Middleware } = ok "Middleware!"
pathMiddleware router request@{ route: Right r } = router $ Record.set (Proxy :: _ "route") r request
-- | Say 'hello' when run, and add a default value to the X-Middleware header
sayHello :: Request -> ResponseM
sayHello :: Request SayHello -> ResponseM
sayHello _ = ok' (header "X-Middleware" "router") "hello"
-- | The stack of middlewares to use for the server
middlewareStack :: (Request -> ResponseM) -> Request -> ResponseM
middlewareStack :: forall route. (Request route -> ResponseM) -> Request (Either Middleware route) -> ResponseM
middlewareStack = loggingMiddleware <<< headerMiddleware <<< pathMiddleware
-- | Boot up the server
main :: ServerM
main =
serve 8080 (middlewareStack sayHello) do
serve 8080 { route: middlewareRoute <+> sayHelloRoute , router: middlewareStack sayHello, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -1,20 +1,35 @@
module Examples.MultiRoute.Main where
import Prelude
import Prelude hiding ((/))
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, notFound, ok, serve)
import HTTPure (Request, ResponseM, ServerM, ok, serve)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as RG
import Routing.Duplex.Generic.Syntax ((/))
data Route = Hello | GoodBye
derive instance Generic Route _
route :: RouteDuplex' Route
route = RD.root $ RG.sum
{ "Hello": "hello" / RG.noArgs
, "GoodBye": "goodbye" / RG.noArgs
}
-- | Specify the routes
router :: Request -> ResponseM
router { path: [ "hello" ] } = ok "hello"
router { path: [ "goodbye" ] } = ok "goodbye"
router _ = notFound
router :: Request Route -> ResponseM
router { route: Hello } = ok "hello"
router { route: GoodBye } = ok "goodbye"
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -1,20 +1,36 @@
module Examples.PathSegments.Main where
import Prelude
import Prelude hiding ((/))
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serve, (!@))
import HTTPure (Request, ResponseM, ServerM, ok, serve)
import HTTPure (Request, ResponseM, ServerM, ok, serve)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as G
import Routing.Duplex.Generic.Syntax ((/))
data Route = Segment String | ManySegments (Array String)
derive instance Generic Route _
route :: RouteDuplex' Route
route = RD.root $ G.sum
{ "Segment": "segment" / RD.segment
, "ManySegments": RD.many RD.segment :: RD.RouteDuplex' (Array String)
}
-- | Specify the routes
router :: Request -> ResponseM
router { path }
| path !@ 0 == "segment" = ok $ path !@ 1
| otherwise = ok $ show path
router :: Request Route -> ResponseM
router { route: Segment elem } = ok elem
router { route: ManySegments elems } = ok $ show elems
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,27 +2,32 @@ module Examples.Post.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure
( Method(Post)
, Request
, ResponseM
, ServerM
, notFound
, ok
, serve
, toString
)
import HTTPure (Method(Post), Request, ResponseM, ServerM, notFound, ok, serve, toString)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as G
data Route = Test
derive instance Generic Route _
route :: RouteDuplex' Route
route = RD.root $ G.sum
{ "Test": G.noArgs
}
-- | Route to the correct handler
router :: Request -> ResponseM
router :: Request Route -> ResponseM
router { body, method: Post } = toString body >>= ok
router _ = notFound
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,20 +2,35 @@ module Examples.QueryParameters.Main where
import Prelude
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serve, (!?), (!@))
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Class.Console (log)
import HTTPure (Request, ResponseM, ServerM, notFound, ok, serve)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as G
data Route = Route { foo :: Boolean, bar :: Maybe String, baz :: Maybe String }
derive instance Generic Route _
route :: RouteDuplex' Route
route = RD.root $ G.sum
{ "Route": RD.params { foo: RD.flag <<< RD.string, bar: RD.optional <<< RD.string, baz: RD.optional <<< RD.string }
}
-- | Specify the routes
router :: Request -> ResponseM
router { query }
| query !? "foo" = ok "foo"
| query !@ "bar" == "test" = ok "bar"
| otherwise = ok $ query !@ "baz"
router :: Request Route -> ResponseM
router { route: (Route { foo: true }) } = ok "foo"
router { route: (Route { bar: Just "test" }) } = ok "bar"
router { route: (Route { bar: Just _ }) } = ok ""
router { route: Route { baz: Just baz } } = ok $ baz
router _ = notFound
-- | Boot up the server
main :: ServerM
main =
serve 8080 router do
serve 8080 { route, router, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -2,8 +2,23 @@ module Examples.SSL.Main where
import Prelude
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Effect.Console (log)
import HTTPure (Request, ResponseM, ServerM, ok, serveSecure)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as G
import Routing.Duplex.Generic as RG
data Route = Test
derive instance Generic Route _
route :: RouteDuplex' Route
route = RD.root $ G.sum
{ "Test": RG.noArgs
}
-- | The path to the certificate file
cert :: String
@ -14,13 +29,13 @@ key :: String
key = "./docs/Examples/SSL/Key.key"
-- | Say 'hello world!' when run
sayHello :: Request -> ResponseM
sayHello :: Request Route -> ResponseM
sayHello _ = ok "hello world!"
-- | Boot up the server
main :: ServerM
main =
serveSecure 8080 cert key sayHello do
serveSecure 8080 cert key { route, router: sayHello, notFoundHandler: Nothing } do
log " ┌───────────────────────────────────────────┐"
log " │ Server now up on port 8080 │"
log " │ │"

View File

@ -17,9 +17,9 @@ import HTTPure.Headers (Headers, empty, header, headers)
import HTTPure.Lookup (at, has, lookup, (!!), (!?), (!@))
import HTTPure.Method (Method(..))
import HTTPure.Path (Path)
import HTTPure.Routes (combineRoutes, (<+>))
import HTTPure.Query (Query)
import HTTPure.Request (Request, fullPath)
import HTTPure.Response (Response, ResponseM, response, response', emptyResponse, emptyResponse', continue, continue', switchingProtocols, switchingProtocols', processing, processing', ok, ok', created, created', accepted, accepted', nonAuthoritativeInformation, nonAuthoritativeInformation', noContent, noContent', resetContent, resetContent', partialContent, partialContent', multiStatus, multiStatus', alreadyReported, alreadyReported', iMUsed, iMUsed', multipleChoices, multipleChoices', movedPermanently, movedPermanently', found, found', seeOther, seeOther', notModified, notModified', useProxy, useProxy', temporaryRedirect, temporaryRedirect', permanentRedirect, permanentRedirect', badRequest, badRequest', unauthorized, unauthorized', paymentRequired, paymentRequired', forbidden, forbidden', notFound, notFound', methodNotAllowed, methodNotAllowed', notAcceptable, notAcceptable', proxyAuthenticationRequired, proxyAuthenticationRequired', requestTimeout, requestTimeout', conflict, conflict', gone, gone', lengthRequired, lengthRequired', preconditionFailed, preconditionFailed', payloadTooLarge, payloadTooLarge', uRITooLong, uRITooLong', unsupportedMediaType, unsupportedMediaType', rangeNotSatisfiable, rangeNotSatisfiable', expectationFailed, expectationFailed', imATeapot, imATeapot', misdirectedRequest, misdirectedRequest', unprocessableEntity, unprocessableEntity', locked, locked', failedDependency, failedDependency', upgradeRequired, upgradeRequired', preconditionRequired, preconditionRequired', tooManyRequests, tooManyRequests', requestHeaderFieldsTooLarge, requestHeaderFieldsTooLarge', unavailableForLegalReasons, unavailableForLegalReasons', internalServerError, internalServerError', notImplemented, notImplemented', badGateway, badGateway', serviceUnavailable, serviceUnavailable', gatewayTimeout, gatewayTimeout', hTTPVersionNotSupported, hTTPVersionNotSupported', variantAlsoNegotiates, variantAlsoNegotiates', insufficientStorage, insufficientStorage', loopDetected, loopDetected', notExtended, notExtended', networkAuthenticationRequired, networkAuthenticationRequired')
import HTTPure.Server (ServerM, serve, serve')
import HTTPure.Response (Response, ResponseM, accepted, accepted', alreadyReported, alreadyReported', badGateway, badGateway', badRequest, badRequest', conflict, conflict', continue, continue', created, created', emptyResponse, emptyResponse', expectationFailed, expectationFailed', failedDependency, failedDependency', forbidden, forbidden', found, found', gatewayTimeout, gatewayTimeout', gone, gone', hTTPVersionNotSupported, hTTPVersionNotSupported', iMUsed, iMUsed', imATeapot, imATeapot', insufficientStorage, insufficientStorage', internalServerError, internalServerError', lengthRequired, lengthRequired', locked, locked', loopDetected, loopDetected', methodNotAllowed, methodNotAllowed', misdirectedRequest, misdirectedRequest', movedPermanently, movedPermanently', multiStatus, multiStatus', multipleChoices, multipleChoices', networkAuthenticationRequired, networkAuthenticationRequired', noContent, noContent', nonAuthoritativeInformation, nonAuthoritativeInformation', notAcceptable, notAcceptable', notExtended, notExtended', notFound, notFound', notImplemented, notImplemented', notModified, notModified', ok, ok', partialContent, partialContent', payloadTooLarge, payloadTooLarge', paymentRequired, paymentRequired', permanentRedirect, permanentRedirect', preconditionFailed, preconditionFailed', preconditionRequired, preconditionRequired', processing, processing', proxyAuthenticationRequired, proxyAuthenticationRequired', rangeNotSatisfiable, rangeNotSatisfiable', requestHeaderFieldsTooLarge, requestHeaderFieldsTooLarge', requestTimeout, requestTimeout', resetContent, resetContent', response, response', seeOther, seeOther', serviceUnavailable, serviceUnavailable', switchingProtocols, switchingProtocols', temporaryRedirect, temporaryRedirect', tooManyRequests, tooManyRequests', uRITooLong, uRITooLong', unauthorized, unauthorized', unavailableForLegalReasons, unavailableForLegalReasons', unprocessableEntity, unprocessableEntity', unsupportedMediaType, unsupportedMediaType', upgradeRequired, upgradeRequired', useProxy, useProxy', variantAlsoNegotiates, variantAlsoNegotiates')
import HTTPure.Routes (type (<+>), combineRoutes, (<+>))
import HTTPure.Server (ServerM, serve, serve', serveSecure, serveSecure')
import HTTPure.Status (Status)

View File

@ -1,15 +1,22 @@
module HTTPure.Routes
( (<+>)
, combineRoutes
)
where
, orElse
, type (<+>)
) where
import Prelude
import Control.Alt ((<|>))
import Data.Either (Either(..))
import Data.Profunctor.Choice ((|||))
import HTTPure.Request (Request)
import HTTPure.Response (ResponseM)
import Record as Record
import Routing.Duplex as RD
import Type.Proxy (Proxy(..))
infixr 0 type Either as <+>
combineRoutes ::
forall left right.
@ -22,3 +29,13 @@ combineRoutes (RD.RouteDuplex lEnc lDec) (RD.RouteDuplex rEnc rDec) = (RD.RouteD
dec = (lDec <#> Left) <|> (rDec <#> Right)
infixr 3 combineRoutes as <+>
orElse ::
forall left right.
(Request left -> ResponseM) ->
(Request right -> ResponseM) ->
Request (left <+> right) ->
ResponseM
orElse leftRouter _ request@{ route: Left l } = leftRouter $ Record.set (Proxy :: _ "route") l request
orElse _ rightRouter request@{ route: Right r } = rightRouter $ Record.set (Proxy :: _ "route") r request

View File

@ -2,14 +2,14 @@ module HTTPure.Server
( ServerM
, serve
, serve'
-- , serveSecure
-- , serveSecure'
, serveSecure
, serveSecure'
) where
import Prelude
import Data.Maybe (Maybe(Nothing), maybe)
import Data.Options ((:=), Options)
import Data.Options (Options, (:=))
import Data.Profunctor.Choice ((|||))
import Effect (Effect)
import Effect.Aff (catchError, message, runAff)
@ -19,9 +19,9 @@ import HTTPure.Request (Request, fromHTTPRequest)
import HTTPure.Response (ResponseM, internalServerError, notFound, send)
import Node.Encoding (Encoding(UTF8))
import Node.FS.Sync (readTextFile)
import Node.HTTP (ListenOptions, listen, close)
import Node.HTTP (ListenOptions, close, listen)
import Node.HTTP (Request, Response, createServer) as HTTP
import Node.HTTP.Secure (SSLOptions, key, keyString, cert, certString)
import Node.HTTP.Secure (SSLOptions, cert, certString, key, keyString)
import Node.HTTP.Secure (createServer) as HTTPS
import Routing.Duplex as RD
@ -30,6 +30,12 @@ import Routing.Duplex as RD
-- | methods.
type ServerM = Effect (Effect Unit -> Effect Unit)
type RoutingSettings route =
{ route :: RD.RouteDuplex' route
, router :: Request route -> ResponseM
, notFoundHandler :: Maybe (Request Unit -> ResponseM)
}
-- | Given a router, handle unhandled exceptions it raises by
-- | responding with 500 Internal Server Error.
onError500 :: forall route. (Request route -> ResponseM) -> Request route -> ResponseM
@ -65,10 +71,7 @@ defaultNotFoundHandler = const notFound
serve' ::
forall route.
ListenOptions ->
{ route :: RD.RouteDuplex' route
, router :: Request route -> ResponseM
, notFoundHandler :: Maybe (Request Unit -> ResponseM)
} ->
RoutingSettings route ->
Effect Unit ->
ServerM
serve' options { route, router, notFoundHandler } onStarted = do
@ -80,18 +83,17 @@ serve' options { route, router, notFoundHandler } onStarted = do
-- | object, a function mapping `Request` to `ResponseM`, and a `ServerM`
-- | containing effects to run on boot, creates and runs a HTTPure server with
-- | SSL.
-- serveSecure' ::
-- forall route.
-- Options SSLOptions ->
-- ListenOptions ->
-- RD.RouteDuplex' route ->
-- (Request route -> ResponseM) ->
-- Effect Unit ->
-- ServerM
-- serveSecure' sslOptions options route router onStarted = do
-- server <- HTTPS.createServer sslOptions (handleRequest route router)
-- listen server options onStarted
-- pure $ close server
serveSecure' ::
forall route.
Options SSLOptions ->
ListenOptions ->
RoutingSettings route ->
Effect Unit ->
ServerM
serveSecure' sslOptions options { route, router, notFoundHandler } onStarted = do
server <- HTTPS.createServer sslOptions (handleRequest { route, router, notFoundHandler: maybe defaultNotFoundHandler identity notFoundHandler })
listen server options onStarted
pure $ close server
-- | Given a port number, return a `HTTP.ListenOptions` `Record`.
listenOptions :: Int -> ListenOptions
@ -109,10 +111,7 @@ listenOptions port =
serve ::
forall route.
Int ->
{ route :: RD.RouteDuplex' route
, router :: Request route -> ResponseM
, notFoundHandler :: Maybe (Request Unit -> ResponseM)
} ->
RoutingSettings route ->
Effect Unit ->
ServerM
serve = serve' <<< listenOptions
@ -124,17 +123,16 @@ serve = serve' <<< listenOptions
-- | 3. A path to a private key file
-- | 4. A handler method which maps `Request` to `ResponseM`
-- | 5. A callback to call when the server is up
-- serveSecure ::
-- forall route.
-- Int ->
-- String ->
-- String ->
-- RD.RouteDuplex' route ->
-- (Request route -> ResponseM) ->
-- Effect Unit ->
-- ServerM
-- serveSecure port certFile keyFile route router onStarted = do
-- cert' <- readTextFile UTF8 certFile
-- key' <- readTextFile UTF8 keyFile
-- let sslOpts = key := keyString key' <> cert := certString cert'
-- serveSecure' sslOpts (listenOptions port) route router onStarted
serveSecure ::
forall route.
Int ->
String ->
String ->
RoutingSettings route ->
Effect Unit ->
ServerM
serveSecure port certFile keyFile routingSettings onStarted = do
cert' <- readTextFile UTF8 certFile
key' <- readTextFile UTF8 keyFile
let sslOpts = key := keyString key' <> cert := certString cert'
serveSecure' sslOpts (listenOptions port) routingSettings onStarted

View File

@ -2,82 +2,107 @@ module Test.HTTPure.RequestSpec where
import Prelude
import Control.Monad.Error.Class (throwError)
import Data.Bitraversable (rtraverse)
import Data.Either (Either(..), either, fromRight)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe)
import Data.Tuple (Tuple(Tuple))
import Effect.Aff (Aff)
import Effect.Exception (error)
import Foreign.Object (singleton)
import HTTPure.Body (toString)
import HTTPure.Headers (headers)
import HTTPure.Method (Method(Post))
import HTTPure.Request (fromHTTPRequest, fullPath)
import HTTPure.Version (Version(HTTP1_1))
import Routing.Duplex as RD
import Routing.Duplex.Generic as G
import Routing.Duplex.Generic.Syntax ((?))
import Test.HTTPure.TestHelpers (Test, mockRequest, (?=))
import Test.Spec (describe, it)
data Route = Test { a :: Maybe String }
derive instance Generic Route _
route :: RD.RouteDuplex' Route
route = RD.root $ G.sum
{ "Test": "test" ? { a : RD.optional <<< RD.string }
}
getRight :: forall a b. Aff (Either a b) -> Aff b
getRight input = input >>= either (const throwLeft) pure
where
throwLeft = throwError (error "Invalid route")
fromHTTPRequestSpec :: Test
fromHTTPRequestSpec =
describe "fromHTTPRequest" do
it "contains the correct method" do
mock <- mockRequest'
mock <- mockRequest' # getRight
mock.method ?= Post
it "contains the correct path" do
mock <- mockRequest'
mock <- mockRequest' # getRight
mock.path ?= [ "test" ]
it "contains the correct query" do
mock <- mockRequest'
mock <- mockRequest' # getRight
mock.query ?= singleton "a" "b"
it "contains the correct headers" do
mock <- mockRequest'
mock <- mockRequest' # getRight
mock.headers ?= headers mockHeaders
it "contains the correct body" do
mockBody <- mockRequest' >>= _.body >>> toString
mockBody <- mockRequest' # getRight >>= (_.body >>> toString)
mockBody ?= "body"
it "contains the correct httpVersion" do
mock <- mockRequest'
mock <- mockRequest' # getRight
mock.httpVersion ?= HTTP1_1
where
mockHeaders = [ Tuple "Test" "test" ]
mockHTTPRequest = mockRequest "1.1" "POST" "/test?a=b" "body" mockHeaders
mockRequest' = mockHTTPRequest >>= fromHTTPRequest
mockRequest' = mockHTTPRequest >>= fromHTTPRequest route
fullPathSpec :: Test
fullPathSpec =
describe "fullPath" do
describe "without query parameters" do
it "is correct" do
mock <- mockRequest' "/foo/bar"
fullPath mock ?= "/foo/bar"
describe "with empty path segments" do
it "strips the empty segments" do
mock <- mockRequest' "//foo////bar/"
fullPath mock ?= "/foo/bar"
describe "with only query parameters" do
it "is correct" do
mock <- mockRequest' "?a=b&c=d"
fullPath mock ?= "/?a=b&c=d"
describe "with only empty query parameters" do
it "is has the default value of '' for the empty parameters" do
mock <- mockRequest' "?a"
fullPath mock ?= "/?a="
describe "with query parameters that have special characters" do
it "percent encodes query params" do
mock <- mockRequest' "?a=%3Fx%3Dtest"
fullPath mock ?= "/?a=%3Fx%3Dtest"
describe "with empty query parameters" do
it "strips out the empty arameters" do
mock <- mockRequest' "?a=b&&&"
fullPath mock ?= "/?a=b"
describe "with a mix of segments and query parameters" do
it "is correct" do
mock <- mockRequest' "/foo///bar/?&a=b&&c"
fullPath mock ?= "/foo/bar?a=b&c="
where
mockHTTPRequest path = mockRequest "" "POST" path "body" []
-- [TODO] Fix this tests or remove them because we can get it from RoutingDuplex
-- fullPathSpec :: Test
-- fullPathSpec =
-- describe "fullPath" do
-- describe "without query parameters" do
-- it "is correct" do
-- mock <- mockRequest' "/foo/bar" # getRight
-- fullPath mock ?= "/foo/bar"
-- describe "with empty path segments" do
-- it "strips the empty segments" do
-- mock <- mockRequest' "//foo////bar/"
-- fullPath mock ?= "/foo/bar"
-- describe "with only query parameters" do
-- it "is correct" do
-- mock <- mockRequest' "?a=b&c=d"
-- fullPath mock ?= "/?a=b&c=d"
-- describe "with only empty query parameters" do
-- it "is has the default value of '' for the empty parameters" do
-- mock <- mockRequest' "?a"
-- fullPath mock ?= "/?a="
-- describe "with query parameters that have special characters" do
-- it "percent encodes query params" do
-- mock <- mockRequest' "?a=%3Fx%3Dtest"
-- fullPath mock ?= "/?a=%3Fx%3Dtest"
-- describe "with empty query parameters" do
-- it "strips out the empty arameters" do
-- mock <- mockRequest' "?a=b&&&"
-- fullPath mock ?= "/?a=b"
-- describe "with a mix of segments and query parameters" do
-- it "is correct" do
-- mock <- mockRequest' "/foo///bar/?&a=b&&c"
-- fullPath mock ?= "/foo/bar?a=b&c="
-- where
-- mockHTTPRequest path = mockRequest "" "POST" path "body" []
mockRequest' path = mockHTTPRequest path >>= fromHTTPRequest
-- mockRequest' path = mockHTTPRequest path >>= fromHTTPRequest route
requestSpec :: Test
requestSpec =
describe "Request" do
fromHTTPRequestSpec
fullPathSpec
--fullPathSpec

View File

@ -34,21 +34,20 @@ route = RD.root $ G.sum
}
mockRouter :: Request Route -> ResponseM
mockRouter { route: Right Test } = ok $ RD.print route Test
mockRouter { route } = notFound
mockRouter { route: Test } = ok $ RD.print route Test
serveSpec :: Test
serveSpec =
describe "serve" do
it "boots a server on the given port" do
close <- liftEffect $ serve 8080 route mockRouter $ pure unit
close <- liftEffect $ serve 8080 { route, router: mockRouter, notFoundHandler: Nothing } $ pure unit
out <- get 8080 empty "/test"
liftEffect $ close $ pure unit
out ?= "/test"
it "responds with a 500 upon unhandled exceptions" do
let router _ = throwError $ error "fail!"
close <- liftEffect $ serve 8080 route router $ pure unit
status <- getStatus 8080 empty "/"
close <- liftEffect $ serve 8080 { route, router, notFoundHandler: Nothing } $ pure unit
status <- getStatus 8080 empty "/test"
liftEffect $ close $ pure unit
status ?= 500
@ -59,7 +58,7 @@ serve'Spec =
let options = { hostname: "localhost", port: 8080, backlog: Nothing }
close <-
liftEffect
$ serve' options route mockRouter
$ serve' options { route, router: mockRouter, notFoundHandler: Nothing }
$ pure unit
out <- get 8080 empty "/test"
liftEffect $ close $ pure unit
@ -72,7 +71,7 @@ serveSecureSpec =
it "boots a server on the given port" do
close <-
liftEffect
$ serveSecure 8080 "./test/Mocks/Certificate.cer" "./test/Mocks/Key.key" route mockRouter
$ serveSecure 8080 "./test/Mocks/Certificate.cer" "./test/Mocks/Key.key" { route, router: mockRouter, notFoundHandler: Nothing }
$ pure unit
out <- get' 8080 empty "/test"
liftEffect $ close $ pure unit
@ -80,7 +79,7 @@ serveSecureSpec =
describe "with invalid key and cert files" do
it "throws" do
expectError $ liftEffect
$ serveSecure 8080 "" "" route mockRouter
$ serveSecure 8080 "" "" { route, router: mockRouter, notFoundHandler: Nothing }
$ pure unit
serveSecure'Spec :: Test
@ -97,7 +96,7 @@ serveSecure'Spec =
sslOpts <- liftEffect $ sslOptions
close <-
liftEffect
$ serveSecure' sslOpts options route mockRouter
$ serveSecure' sslOpts options { route, router: mockRouter, notFoundHandler: Nothing }
$ pure unit
out <- get' 8080 empty "/test"
liftEffect $ close $ pure unit