Update node libs (tests do not pass)

This commit is contained in:
Jordan Martinez 2023-08-07 09:37:29 -05:00 committed by Jantxu
parent 791a18c749
commit 4667feefa4
19 changed files with 176 additions and 182 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"purescript.buildCommand": "spago -x test.dhall build --purs-args --json-errors"
}

View File

@ -10,6 +10,7 @@
, "either"
, "exceptions"
, "foldable-traversable"
, "foreign"
, "foreign-object"
, "functions"
, "js-uri"
@ -19,7 +20,9 @@
, "maybe"
, "newtype"
, "node-buffer"
, "node-event-emitter"
, "node-fs"
, "node-net"
, "node-http"
, "node-process"
, "node-streams"

View File

@ -18,14 +18,19 @@ import Effect.Aff (Aff, makeAff, nonCanceler)
import Effect.Aff.Class (class MonadAff, liftAff)
import Effect.Class (liftEffect)
import Effect.Ref (Ref)
import Effect.Ref (modify, new, read, write) as Ref
import Effect.Ref (new, read, write) as Ref
import HTTPurple.Headers (RequestHeaders, mkRequestHeader)
import Node.Buffer (Buffer, concat, fromString, size)
import Node.Buffer (toString) as Buffer
import Node.Buffer (Buffer, fromString, size)
import Node.Buffer (concat, toString) as Buffer
import Node.Encoding (Encoding(UTF8))
import Node.HTTP (Request, Response, requestAsStream, responseAsStream)
import Node.Stream (Readable, Stream, end, onData, onEnd, pipe, writeString)
import Node.Stream (write) as Stream
import Node.EventEmitter (once_)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.OutgoingMessage as OM
import Node.HTTP.ServerResponse as SR
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Node.Stream (Readable, Stream, end', pipe, writeString')
import Node.Stream (endH, write') as Stream
import Node.Stream.Aff (readableToBuffers)
import Type.Equality (class TypeEquals, to)
type RequestBody =
@ -35,13 +40,13 @@ type RequestBody =
}
-- | Read the body `Readable` stream out of the incoming request
read :: Request -> Effect RequestBody
read :: IncomingMessage IMServer -> Effect RequestBody
read request = do
buffer <- Ref.new Nothing
string <- Ref.new Nothing
pure
{ buffer
, stream: requestAsStream request
, stream: IM.toReadable request
, string
}
@ -75,22 +80,12 @@ toBuffer requestBody = do
$ Ref.read requestBody.buffer
case maybeBuffer of
Nothing -> do
buffer <- streamToBuffer requestBody.stream
liftEffect
$ Ref.write (Just buffer) requestBody.buffer
pure buffer
buffers <- liftAff $ readableToBuffers requestBody.stream
liftEffect do
buffer <- Buffer.concat buffers
Ref.write (Just buffer) requestBody.buffer
pure buffer
Just buffer -> pure buffer
where
-- | Slurp the entire `Readable` stream into a `Buffer`
streamToBuffer :: MonadAff m => Readable () -> m Buffer
streamToBuffer stream =
liftAff $ makeAff \done -> do
bufs <- Ref.new []
onData stream \buf -> void $ Ref.modify (_ <> [ buf ]) bufs
onEnd stream do
body <- Ref.read bufs >>= concat
done $ Right body
pure nonCanceler
-- | Return the `Readable` stream directly from `RequestBody`
toStream :: RequestBody -> Readable ()
@ -106,7 +101,7 @@ class Body b where
defaultHeaders :: b -> Effect RequestHeaders
-- | Given a body value and a Node HTTP `Response` value, write the body value
-- | to the Node response.
write :: b -> Response -> Aff Unit
write :: b -> ServerResponse -> Aff Unit
-- | The instance for `String` will convert the string to a buffer first in
-- | order to determine it's additional headers. This is to ensure that the
@ -118,8 +113,8 @@ instance Body String where
buf :: Buffer <- fromString body UTF8
defaultHeaders buf
write body response = makeAff \done -> do
let stream = responseAsStream response
void $ writeString stream UTF8 body $ const $ end stream $ const $ done $ Right unit
let stream = OM.toWriteable $ SR.toOutgoingMessage response
void $ writeString' stream UTF8 body $ const $ end' stream $ const $ done $ Right unit
pure nonCanceler
-- | The instance for `Buffer` is trivial--we add a `Content-Length` header
@ -128,8 +123,8 @@ instance Body String where
instance Body Buffer where
defaultHeaders buf = mkRequestHeader "Content-Length" <$> show <$> size buf
write body response = makeAff \done -> do
let stream = responseAsStream response
void $ Stream.write stream body $ const $ end stream $ const $ done $ Right unit
let stream = OM.toWriteable $ SR.toOutgoingMessage response
void $ Stream.write' stream body $ const $ end' stream $ const $ done $ Right unit
pure nonCanceler
-- | This instance can be used to send chunked data. Here, we add a
@ -141,6 +136,6 @@ instance
defaultHeaders _ = pure $ mkRequestHeader "Transfer-Encoding" "chunked"
write body response = makeAff \done -> do
let stream = to body
void $ pipe stream $ responseAsStream response
onEnd stream $ done $ Right unit
void $ pipe stream $ OM.toWriteable $ SR.toOutgoingMessage response
stream # once_ Stream.endH (done $ Right unit)
pure nonCanceler

View File

@ -29,7 +29,10 @@ import Data.Tuple (Tuple(Tuple))
import Effect (Effect)
import Foreign.Object (fold)
import HTTPurple.Lookup (class Lookup, (!!))
import Node.HTTP (Request, Response, requestHeaders, setHeaders)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.OutgoingMessage (setHeader')
import Node.HTTP.ServerResponse (toOutgoingMessage)
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Prim.Row as Row
import Prim.RowList (class RowToList, Cons, Nil)
import Record as Record
@ -84,17 +87,18 @@ instance Eq ResponseHeaders where
eq (ResponseHeaders a) (ResponseHeaders b) = eq a b
-- | Get the headers out of a HTTP `RequestHeaders` object.
read :: Request -> RequestHeaders
read = requestHeaders >>> fold insertField Map.empty >>> RequestHeaders
read :: IncomingMessage IMServer -> RequestHeaders
read = IM.headers >>> fold insertField Map.empty >>> RequestHeaders
where
insertField x key value = insert (CaseInsensitiveString key) value x
-- | Given an HTTP `Response` and a `ResponseHeaders` object, return an effect that will
-- | write the `ResponseHeaders` to the `Response`.
write :: Response -> ResponseHeaders -> Effect Unit
write :: ServerResponse -> ResponseHeaders -> Effect Unit
write response (ResponseHeaders headers') = void $ traverseWithIndex writeField headers'
where
writeField key values = setHeaders response (unwrap key) values
om = toOutgoingMessage response
writeField key values = om # setHeader' (unwrap key) values
-- | Return a `ResponseHeaders` containing no headers.
empty :: ResponseHeaders

View File

@ -5,7 +5,8 @@ module HTTPurple.Method
import Prelude
import Node.HTTP (Request, requestMethod)
import Node.HTTP.IncomingMessage (method)
import Node.HTTP.Types (IMServer, IncomingMessage)
-- | These are the HTTP methods that HTTPurple understands.
data Method
@ -35,8 +36,8 @@ instance showMethod :: Show Method where
show Patch = "Patch"
-- | Take an HTTP `Request` and extract the `Method` for that request.
read :: Request -> Method
read = requestMethod >>> case _ of
read :: IncomingMessage IMServer -> Method
read = method >>> case _ of
"POST" -> Post
"PUT" -> Put
"DELETE" -> Delete

View File

@ -24,20 +24,20 @@ import Effect.Exception (Error)
import Effect.Ref as Ref
import Effect.Uncurried (EffectFn1, EffectFn3, mkEffectFn1, runEffectFn1, runEffectFn3)
import Literals.Undefined (Undefined, undefined)
import Node.HTTP as HTTP
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Prim.Row (class Union)
import Untagged.Union (type (|+|), UndefinedOr, asOneOf, uorToMaybe)
newtype NodeMiddleware :: forall k. k -> Type
newtype NodeMiddleware extended =
NodeMiddleware (EffectFn3 HTTP.Request HTTP.Response (EffectFn1 (UndefinedOr Error) Unit) (Effect Unit))
NodeMiddleware (EffectFn3 (IncomingMessage IMServer) ServerResponse (EffectFn1 (UndefinedOr Error) Unit) (Effect Unit))
derive instance Newtype (NodeMiddleware extended) _
data NextInvocation = NotCalled | ProcessingFailed Error | ProcessingSucceeded
type MiddlewareResultR =
(request :: HTTP.Request, response :: HTTP.Response, middlewareResult :: NextInvocation)
(request :: IncomingMessage IMServer, response :: ServerResponse, middlewareResult :: NextInvocation)
newtype MiddlewareResult :: forall k. k -> Type
newtype MiddlewareResult input = MiddlewareResult { | MiddlewareResultR }

View File

@ -9,7 +9,8 @@ import Data.Array (filter, head)
import Data.Maybe (fromMaybe)
import Data.String (Pattern(Pattern), split)
import HTTPurple.Utils (urlDecode)
import Node.HTTP (Request, requestURL)
import Node.HTTP.IncomingMessage (url)
import Node.HTTP.Types (IMServer, IncomingMessage)
-- | The `Path` type is just sugar for an `Array` of `String` segments that are
-- | sent in a request and indicates the path of the resource being requested.
@ -20,8 +21,8 @@ import Node.HTTP (Request, requestURL)
type Path = Array String
-- | Given an HTTP `Request` object, extract the `Path`.
read :: Request -> Path
read = requestURL >>> split' "?" >>> first >>> split' "/" >>> nonempty >>> map urlDecode
read :: IncomingMessage IMServer -> Path
read = url >>> split' "?" >>> first >>> split' "/" >>> nonempty >>> map urlDecode
where
nonempty = filter ((/=) "")
split' = Pattern >>> split

View File

@ -12,7 +12,8 @@ import Data.String (Pattern(Pattern), joinWith, split)
import Data.Tuple (Tuple(Tuple))
import Foreign.Object (Object, fromFoldable)
import HTTPurple.Utils (replacePlus, urlDecode)
import Node.HTTP (Request, requestURL)
import Node.HTTP.IncomingMessage (url)
import Node.HTTP.Types (IMServer, IncomingMessage)
-- | The `Query` type is a `Object` of `Strings`, with one entry per query
-- | parameter in the request. For any query parameters that don't have values
@ -25,8 +26,8 @@ import Node.HTTP (Request, requestURL)
type Query = Object String
-- | The `Map` of query segments in the given HTTP `Request`.
read :: Request -> Query
read = requestURL >>> split' "?" >>> last >>> split' "&" >>> nonempty >>> toObject
read :: IncomingMessage IMServer -> Query
read = url >>> split' "?" >>> last >>> split' "&" >>> nonempty >>> toObject
where
toObject = map toTuple >>> fromFoldable
nonempty = filter ((/=) "")

View File

@ -32,8 +32,8 @@ import HTTPurple.Query (read) as Query
import HTTPurple.Utils (encodeURIComponent)
import HTTPurple.Version (Version)
import HTTPurple.Version (read) as Version
import Node.HTTP (Request) as HTTP
import Node.HTTP (requestURL)
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.Types (IMServer, IncomingMessage)
import Prim.Row (class Nub, class Union)
import Prim.RowList (class RowToList)
import Record (merge)
@ -81,7 +81,7 @@ fullPath { path: p, query } = "/" <> path <> questionMark <> queryParams
queryParamsArr = toArrayWithKey stringifyQueryParam query
stringifyQueryParam key value = encodeURIComponent key <> "=" <> encodeURIComponent value
mkRequest :: forall route m. MonadEffect m => HTTP.Request -> route -> m (Request route)
mkRequest :: forall route m. MonadEffect m => IncomingMessage IMServer -> route -> m (Request route)
mkRequest request route = do
body <- liftEffect $ Body.read request
pure
@ -92,17 +92,17 @@ mkRequest request route = do
, headers: Headers.read request
, body
, httpVersion: Version.read request
, url: requestURL request
, url: IM.url request
}
-- | Given an HTTP `Request` object, this method will convert it to an HTTPurple
-- | `Request` object.
fromHTTPRequest :: forall route. RD.RouteDuplex' route -> HTTP.Request -> Aff (Either (Request Unit) (Request route))
fromHTTPRequest :: forall route. RD.RouteDuplex' route -> IncomingMessage IMServer -> Aff (Either (Request Unit) (Request route))
fromHTTPRequest route request = do
RD.parse route (requestURL request) #
RD.parse route (IM.url request) #
bitraverse (const $ mkRequest request unit) (mkRequest request)
fromHTTPRequestUnit :: HTTP.Request -> Aff (Request Unit)
fromHTTPRequestUnit :: IncomingMessage IMServer -> Aff (Request Unit)
fromHTTPRequestUnit = flip mkRequest unit
fromHTTPRequestExt ::
@ -113,7 +113,7 @@ fromHTTPRequestExt ::
Keys ctx =>
RD.RouteDuplex' route ->
Proxy ctx ->
HTTP.Request ->
IncomingMessage IMServer ->
Aff (Either (Request Unit) (ExtRequestNT route ctx))
fromHTTPRequestExt route _ nodeRequest = do
let

View File

@ -143,7 +143,7 @@ import HTTPurple.Headers (ResponseHeaders, empty, toResponseHeaders)
import HTTPurple.Headers (write) as Headers
import HTTPurple.Status (Status)
import HTTPurple.Status (accepted, alreadyReported, badGateway, badRequest, conflict, continue, created, expectationFailed, failedDependency, forbidden, found, gatewayTimeout, gone, hTTPVersionNotSupported, iMUsed, imATeapot, insufficientStorage, internalServerError, lengthRequired, locked, loopDetected, methodNotAllowed, misdirectedRequest, movedPermanently, multiStatus, multipleChoices, networkAuthenticationRequired, noContent, nonAuthoritativeInformation, notAcceptable, notExtended, notFound, notImplemented, notModified, ok, partialContent, payloadTooLarge, paymentRequired, permanentRedirect, preconditionFailed, preconditionRequired, processing, proxyAuthenticationRequired, rangeNotSatisfiable, requestHeaderFieldsTooLarge, requestTimeout, resetContent, seeOther, serviceUnavailable, switchingProtocols, temporaryRedirect, tooManyRequests, uRITooLong, unauthorized, unavailableForLegalReasons, unprocessableEntity, unsupportedMediaType, upgradeRequired, useProxy, variantAlsoNegotiates, write) as Status
import Node.HTTP (Response) as HTTP
import Node.HTTP.Types (ServerResponse)
-- | The `ResponseM` type simply conveniently wraps up an HTTPurple monad that
-- | returns a response. This type is the return type of all router/route
@ -154,13 +154,13 @@ type ResponseM = Aff Response
type Response =
{ status :: Status
, headers :: ResponseHeaders
, writeBody :: HTTP.Response -> Aff Unit
, writeBody :: ServerResponse -> Aff Unit
}
-- | Given an HTTP `Response` and a HTTPurple `Response`, this method will return
-- | a monad encapsulating writing the HTTPurple `Response` to the HTTP `Response`
-- | and closing the HTTP `Response`.
send :: forall m. MonadEffect m => MonadAff m => HTTP.Response -> Response -> m Unit
send :: forall m. MonadEffect m => MonadAff m => ServerResponse -> Response -> m Unit
send httpresponse { status, headers, writeBody } = do
liftEffect $ Status.write httpresponse status
liftEffect $ Headers.write httpresponse headers

View File

@ -17,7 +17,6 @@ import Prelude
import Control.Monad.Cont (runContT)
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Newtype (unwrap)
import Data.Options ((:=))
import Data.Posix.Signal (Signal(..))
import Data.Profunctor (lcmap)
import Data.Profunctor.Choice ((|||))
@ -33,13 +32,16 @@ import HTTPurple.Response (Response, ResponseM, internalServerError, notFound, s
import Justifill (justifill)
import Justifill.Fillable (class FillableFields)
import Justifill.Justifiable (class JustifiableFields)
import Node.Encoding (Encoding(UTF8))
import Node.FS.Sync (readTextFile)
import Node.HTTP (ListenOptions, Request, Response, createServer) as HTTP
import Node.HTTP (close, listen)
import Node.HTTP.Secure (cert, certString, key, keyString)
import Node.HTTP.Secure (createServer) as HTTPS
import Node.Process (onSignal)
import Node.EventEmitter as EE
import Node.FS.Sync (readFile)
import Node.HTTP as HTTP
import Node.HTTP.Server as HServer
import Node.HTTP.Types (IMServer, IncomingMessage, ServerResponse)
import Node.HTTPS as HTTPS
import Node.Net.Server (listenTcp, listeningH)
import Node.Net.Server as NServer
import Node.Process (mkSignalH)
import Node.Process as Process
import Prim.Row (class Nub, class Union)
import Prim.RowList (class RowToList)
import Record (merge)
@ -101,8 +103,8 @@ defaultMiddlewareErrorHandler err _ = do
-- | handle requests without a routing adt.
handleRequestUnit ::
(Request Unit -> ResponseM) ->
HTTP.Request ->
HTTP.Response ->
IncomingMessage IMServer ->
ServerResponse ->
Effect Unit
handleRequestUnit router request httpresponse =
void $ runAff (\_ -> pure unit) $ fromHTTPRequestUnit request
@ -127,8 +129,8 @@ handleExtRequest ::
, router :: ExtRequestNT route ctx -> ResponseM
, notFoundHandler :: Request Unit -> ResponseM
} ->
HTTP.Request ->
HTTP.Response ->
IncomingMessage IMServer ->
ServerResponse ->
Aff Unit
handleExtRequest { route, router, notFoundHandler } req resp = do
httpurpleReq <- fromHTTPRequestExt route (Proxy :: Proxy ctx) req
@ -145,8 +147,8 @@ handleRequest ::
, router :: ExtRequestNT route ctx -> ResponseM
, notFoundHandler :: Request Unit -> ResponseM
} ->
HTTP.Request ->
HTTP.Response ->
IncomingMessage IMServer ->
ServerResponse ->
Effect Unit
handleRequest settings request response = void $ runAff (\_ -> pure unit) $ handleExtRequest settings request response
@ -161,8 +163,8 @@ handleExtRequestWithMiddleware ::
, router :: ExtRequestNT route output -> ResponseM
, notFoundHandler :: Request Unit -> ResponseM
} ->
HTTP.Request ->
HTTP.Response ->
IncomingMessage IMServer ->
ServerResponse ->
Effect Unit
handleExtRequestWithMiddleware { route, nodeMiddleware: NodeMiddlewareStack middleware, router, notFoundHandler } req resp = void $ runAff (\_ -> pure unit) $ do
eff <- liftEffect $ flip runContT (coerce >>> pure) $ middleware (MiddlewareResult { request: req, response: resp, middlewareResult: NotCalled })
@ -209,15 +211,14 @@ serveInternal inputOptions maybeNodeMiddleware settings = do
filledOptions :: ListenOptions
filledOptions = justifill inputOptions
hostname = fromMaybe defaultHostname filledOptions.hostname
host = fromMaybe defaultHostname filledOptions.hostname
port = fromMaybe defaultPort filledOptions.port
onStarted = fromMaybe (defaultOnStart hostname port) filledOptions.onStarted
onStarted = fromMaybe (defaultOnStart host port) filledOptions.onStarted
options :: HTTP.ListenOptions
options =
{ hostname
{ host
, port
, backlog: filledOptions.backlog
, backlog: fromMaybe 511 filledOptions.backlog
}
routingSettings = merge settings { notFoundHandler: fromMaybe defaultNotFoundHandler filledOptions.notFoundHandler }
@ -226,17 +227,24 @@ serveInternal inputOptions maybeNodeMiddleware settings = do
Just nodeMiddleware -> handleExtRequestWithMiddleware $ merge routingSettings { nodeMiddleware }
Nothing -> handleRequest routingSettings
sslOptions = { certFile: _, keyFile: _ } <$> filledOptions.certFile <*> filledOptions.keyFile
server <- case sslOptions of
Just { certFile, keyFile } ->
do
cert' <- readTextFile UTF8 certFile
key' <- readTextFile UTF8 keyFile
let sslOpts = key := keyString key' <> cert := certString cert'
HTTPS.createServer sslOpts handler
Nothing -> HTTP.createServer handler
listen server options onStarted
let closingHandler = close server
registerClosingHandler filledOptions.closingHandler closingHandler
netServer <- case sslOptions of
Just { certFile, keyFile } -> do
cert' <- readFile certFile
key' <- readFile keyFile
server <- HTTPS.createSecureServer'
{ key: [ key' ]
, cert: [ cert' ]
}
server # EE.on_ HServer.requestH handler
pure $ HServer.toNetServer server
Nothing -> do
server <- HTTP.createServer
server # EE.on_ HServer.requestH handler
pure $ HServer.toNetServer server
netServer # EE.on_ listeningH onStarted
listenTcp netServer options
let closingHandler = NServer.close netServer
liftEffect $ registerClosingHandler filledOptions.closingHandler (\eff -> eff *> closingHandler)
serve ::
forall route from fromRL via missing missingList.
@ -277,8 +285,8 @@ serveNodeMiddleware inputOptions { route, router, nodeMiddleware } = do
registerClosingHandler :: Maybe ClosingHandler -> (Effect Unit -> Effect Unit) -> ServerM
registerClosingHandler (Just NoClosingHandler) closingHandler = pure closingHandler
registerClosingHandler _ closingHandler = do
onSignal SIGINT $ closingHandler $ log "Aye, stopping service now. Goodbye!"
onSignal SIGTERM $ closingHandler $ log "Arrgghh I got stabbed in the back 🗡 ... good...bye..."
Process.process # EE.on_ (mkSignalH SIGINT) (closingHandler $ log "Aye, stopping service now. Goodbye!")
Process.process # EE.on_ (mkSignalH SIGTERM) (closingHandler $ log "Arrgghh I got stabbed in the back 🗡 ... good...bye...")
pure closingHandler
defaultHostname :: String

View File

@ -71,14 +71,15 @@ module HTTPurple.Status
import Prelude
import Effect (Effect)
import Node.HTTP (Response, setStatusCode)
import Node.HTTP.ServerResponse (setStatusCode)
import Node.HTTP.Types (ServerResponse)
-- | The `Status` type enumerates all valid HTTP response status codes.
type Status = Int
-- | Write a status to a given HTTP `Response`.
write :: Response -> Status -> Effect Unit
write = setStatusCode
write :: ServerResponse -> Status -> Effect Unit
write = flip setStatusCode
---------
-- 1xx --

View File

@ -5,7 +5,8 @@ module HTTPurple.Version
import Prelude
import Node.HTTP (Request, httpVersion)
import Node.HTTP.IncomingMessage (httpVersion)
import Node.HTTP.Types (IMServer, IncomingMessage)
-- | These are the HTTP versions that HTTPurple understands. There are five
-- | commonly known versions which are explicitly named.
@ -31,7 +32,7 @@ instance showVersion :: Show Version where
show (Other version) = "HTTP/" <> version
-- | Take an HTTP `Request` and extract the `Version` for that request.
read :: Request -> Version
read :: IncomingMessage IMServer -> Version
read = httpVersion >>> case _ of
"0.9" -> HTTP0_9
"1.0" -> HTTP1_0

View File

@ -6,7 +6,6 @@ in conf // {
, "exceptions"
, "lists"
, "node-child-process"
, "node-fs-aff"
, "spec"
, "debug"
, "transformers"

View File

@ -31,7 +31,7 @@ readSpec =
describe "read" do
it "is the body of the Request" do
body <- (liftEffect <<< read) =<< mockRequest "" "GET" "" "test" []
string <- liftEffect $ fromMaybe "" <$> readString (toStream body) Nothing UTF8
string <- liftEffect $ fromMaybe "" <$> readString (toStream body) UTF8
string ?= "test"
toStringSpec :: Test

View File

@ -2,8 +2,6 @@ module Test.HTTPurple.IntegrationSpec where
import Prelude
import Control.Monad.Trans.Class (lift)
import Effect.Aff (Milliseconds(..), delay)
import Effect.Class (liftEffect)
import Examples.AsyncResponse.Main as AsyncResponse
import Examples.BinaryRequest.Main as BinaryRequest
@ -26,7 +24,7 @@ import Foreign.Object as Object
import Node.Buffer (toArray)
import Node.FS.Aff (readFile)
import Test.HTTPurple.TestHelpers (Test, get, get', getBinary, getHeader, post, postBinary, (?=))
import Test.Spec (Tree(..), describe, it)
import Test.Spec (describe, it)
import Test.Spec.Assertions.String (shouldStartWith)
asyncResponseSpec :: Test

View File

@ -3,7 +3,6 @@ module Test.HTTPurple.ResponseSpec where
import Prelude
import Data.Either (Either(Right))
import Debug (spy)
import Effect.Aff (makeAff, nonCanceler)
import Effect.Class (liftEffect)
import HTTPurple.Body (defaultHeaders)
@ -11,8 +10,9 @@ import HTTPurple.Headers (toResponseHeaders)
import HTTPurple.Headers as Headers
import HTTPurple.Response (emptyResponse, emptyResponse', response, response', send)
import Node.Encoding (Encoding(UTF8))
import Node.HTTP (responseAsStream)
import Node.Stream (end, writeString)
import Node.HTTP.OutgoingMessage as OM
import Node.HTTP.ServerResponse as SR
import Node.Stream (end', writeString')
import Test.HTTPurple.TestHelpers (Test, getResponseBody, getResponseHeader, getResponseStatus, mockResponse, (?=))
import Test.Spec (describe, it)
@ -25,8 +25,8 @@ sendSpec =
, headers: Headers.header "Test" "test"
, writeBody:
\response -> makeAff \done -> do
stream <- pure $ responseAsStream response
void $ writeString stream UTF8 "test" $ const $ end stream $ const $ done $ Right unit
stream <- pure $ OM.toWriteable $ SR.toOutgoingMessage response
void $ writeString' stream UTF8 "test" $ const $ end' stream $ const $ done $ Right unit
pure nonCanceler
}
it "writes the headers" do

View File

@ -3,20 +3,13 @@ module Test.HTTPurple.ServerSpec where
import Prelude
import Control.Monad.Except (throwError)
import Data.Either (Either(..))
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(Nothing))
import Data.Options ((:=))
import Effect.Class (liftEffect)
import Effect.Exception (error)
import Foreign.Object (empty)
import HTTPurple.Request (Request)
import HTTPurple.Response (ResponseM, notFound, ok)
import HTTPurple.Response (ResponseM, ok)
import HTTPurple.Server (serve)
import HTTPurple.Server as Server
import Node.Encoding (Encoding(UTF8))
import Node.FS.Sync (readTextFile)
import Node.HTTP.Secure (cert, certString, key, keyString)
import Routing.Duplex (RouteDuplex')
import Routing.Duplex as RD
import Routing.Duplex.Generic as G

View File

@ -2,40 +2,29 @@ module Test.HTTPurple.TestHelpers where
import Prelude
import Data.Array (fromFoldable) as Array
import Data.Either (Either(Right))
import Data.List (List(Nil, Cons), reverse)
import Data.Maybe (fromMaybe)
import Data.Options ((:=))
import Data.String (toLower)
import Data.Tuple (Tuple)
import Effect (Effect)
import Effect.Aff (Aff, makeAff, nonCanceler)
import Effect.Class (liftEffect)
import Effect.Ref (modify_, new, read)
import Foreign (Foreign)
import Foreign.Object (Object, lookup)
import Foreign.Object (fromFoldable) as Object
import Node.Buffer (Buffer, concat, create, fromString)
import Node.Buffer (toString) as Buffer
import Node.Buffer (Buffer, create, fromString)
import Node.Buffer (concat, toString) as Buffer
import Node.Encoding (Encoding(UTF8))
import Node.HTTP (Request)
import Node.HTTP (Response) as HTTP
import Node.HTTP.Client
( RequestHeaders(RequestHeaders)
, headers
, hostname
, method
, path
, port
, protocol
, rejectUnauthorized
, requestAsStream
, responseAsStream
, responseHeaders
, statusCode
)
import Node.HTTP.Client (Response, request) as HTTPClient
import Node.Stream (Readable, end, onData, onEnd, write)
import Node.EventEmitter (once_)
import Node.HTTP as HTTP
import Node.HTTP.ClientRequest as HTTPClient
import Node.HTTP.IncomingMessage as IM
import Node.HTTP.OutgoingMessage as OM
import Node.HTTP.Types (IMClientRequest, IncomingMessage, ServerResponse)
import Node.HTTPS as HTTPS
import Node.Stream (Readable)
import Node.Stream as Stream
import Node.Stream.Aff (readableToBuffers)
import Test.Spec (Spec)
import Test.Spec.Assertions (shouldEqual)
import Unsafe.Coerce (unsafeCoerce)
@ -57,27 +46,32 @@ request ::
Object String ->
String ->
Buffer ->
Aff HTTPClient.Response
Aff (IncomingMessage IMClientRequest)
request secure port' method' headers' path' body =
makeAff \done -> do
req <- HTTPClient.request options $ Right >>> done
let stream = requestAsStream req
req <- case secure of
true -> HTTPS.requestOpts
{ method: method'
, host: "localhost"
, port: port'
, path: path'
, headers: unsafeCoerce headers' :: Object Foreign
, rejectUnauthorized: false
}
false -> HTTP.requestOpts
{ method: method'
, host: "localhost"
, port: port'
, path: path'
, headers: unsafeCoerce headers' :: Object Foreign
}
req # once_ HTTPClient.responseH (Right >>> done)
let stream = OM.toWriteable $ HTTPClient.toOutgoingMessage req
void
$ write stream body
$ Stream.write' stream body
$ const
$ end stream
$ const
$ pure unit
$ Stream.end stream
pure nonCanceler
where
options =
protocol := (if secure then "https:" else "http:")
<> method := method'
<> hostname := "localhost"
<> port := port'
<> path := path'
<> headers := RequestHeaders headers'
<> rejectUnauthorized := false
-- | Same as `request` but without.
request' ::
@ -86,7 +80,7 @@ request' ::
String ->
Object String ->
String ->
Aff HTTPClient.Response
Aff (IncomingMessage IMClientRequest)
request' secure port method headers path =
liftEffect (create 0)
>>= request secure port method headers path
@ -99,29 +93,19 @@ requestString ::
Object String ->
String ->
String ->
Aff HTTPClient.Response
Aff (IncomingMessage IMClientRequest)
requestString secure port method headers path body = do
liftEffect (fromString body UTF8)
>>= request secure port method headers path
-- | Convert a request to an Aff containing the `Buffer with the response body.
toBuffer :: HTTPClient.Response -> Aff Buffer
toBuffer response =
makeAff \done -> do
let
stream = responseAsStream response
chunks <- new Nil
onData stream $ \new -> modify_ (Cons new) chunks
onEnd stream $ read chunks
>>= reverse
>>> Array.fromFoldable
>>> concat
>>= Right
>>> done
pure nonCanceler
toBuffer :: IncomingMessage IMClientRequest -> Aff Buffer
toBuffer response = do
bufs <- readableToBuffers $ IM.toReadable response
liftEffect $ Buffer.concat bufs
-- | Convert a request to an Aff containing the string with the response body.
toString :: HTTPClient.Response -> Aff String
toString :: IncomingMessage IMClientRequest -> Aff String
toString resp = do
buf <- toBuffer resp
liftEffect $ Buffer.toString UTF8 buf
@ -174,8 +158,8 @@ postBinary port headers path = request false port "POST" headers path >=> toStri
-- | Convert a request to an Aff containing the string with the given header
-- | value.
extractHeader :: String -> HTTPClient.Response -> String
extractHeader header = unmaybe <<< lookup' <<< responseHeaders
extractHeader :: String -> IncomingMessage IMClientRequest -> String
extractHeader header = unmaybe <<< lookup' <<< IM.headers
where
unmaybe = fromMaybe ""
@ -196,45 +180,47 @@ getStatus ::
Object String ->
String ->
Aff Int
getStatus port headers path = statusCode <$> request' false port "GET" headers path
getStatus port headers path = IM.statusCode <$> request' false port "GET" headers path
-- | Mock an HTTP Request object
foreign import mockRequestImpl ::
forall a.
String ->
String ->
String ->
String ->
Object String ->
Effect Request
Effect (IncomingMessage a)
-- | Mock an HTTP Request object
mockRequest ::
forall a.
String ->
String ->
String ->
String ->
Array (Tuple String String) ->
Aff Request
Aff (IncomingMessage a)
mockRequest httpVersion method url body = liftEffect <<< mockRequestImpl httpVersion method url body <<< Object.fromFoldable
-- | Mock an HTTP Response object
foreign import mockResponse :: Effect HTTP.Response
foreign import mockResponse :: Effect ServerResponse
-- | Get the current body from an HTTP Response object (note this will only work
-- | with an object returned from mockResponse).
getResponseBody :: HTTP.Response -> String
getResponseBody :: ServerResponse -> String
getResponseBody = _.body <<< unsafeCoerce
-- | Get the currently set status from an HTTP Response object.
getResponseStatus :: HTTP.Response -> Int
getResponseStatus :: ServerResponse -> Int
getResponseStatus = _.statusCode <<< unsafeCoerce
-- | Get all current headers on the HTTP Response object.
getResponseHeaders :: HTTP.Response -> Object (Array String)
getResponseHeaders :: ServerResponse -> Object (Array String)
getResponseHeaders = unsafeCoerce <<< _.headers <<< unsafeCoerce
-- | Get the current value for the header on the HTTP Response object.
getResponseHeader :: String -> HTTP.Response -> Array String
getResponseHeader :: String -> ServerResponse -> Array String
getResponseHeader header = fromMaybe [ "" ] <<< lookup header <<< getResponseHeaders
-- | Create a stream out of a string.