Skip to content

Commit d1c8e2e

Browse files
committed
WIP tests
1 parent e2b739d commit d1c8e2e

File tree

9 files changed

+145
-41
lines changed

9 files changed

+145
-41
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
/node_modules/
77
/output/
88
package-lock.json
9+
generated-docs/

spago.dhall

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ to generate this file without the comments in this block.
1313
{ name = "node-http"
1414
, dependencies =
1515
[ "aff"
16-
, "arrays"
1716
, "arraybuffer-types"
17+
, "arrays"
1818
, "console"
1919
, "contravariant"
20+
, "control"
2021
, "effect"
2122
, "either"
2223
, "exceptions"
@@ -34,6 +35,7 @@ to generate this file without the comments in this block.
3435
, "partial"
3536
, "prelude"
3637
, "st"
38+
, "strings"
3739
, "transformers"
3840
, "tuples"
3941
, "unsafe-coerce"

src/Node/HTTP2.purs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
-- | Low-level bindings to the *Node.js* [HTTP/2](https://nodejs.org/docs/latest/api/http2.html) API.
1+
-- | Bindings to the [*Node.js* HTTP/2](https://nodejs.org/docs/latest/api/http2.html) API.
2+
-- |
3+
-- | The __Node.HTTP2.Client__ and __Node.HTTP2.Server__ modules provide a
4+
-- | low-level `Effect` callback API.
5+
-- |
6+
-- | The __Node.HTTP2.Client.Aff__ and __Node.HTTP2.Server.Aff__ modules provide
7+
-- | a high-level `Aff` API so that
8+
-- | [“you don’t even have to think about callbacks.”](https://github.com/purescript-contrib/purescript-aff/tree/main/docs#escaping-callback-hell).
29
module Node.HTTP2
310
( Headers
411
, toHeaders

src/Node/HTTP2/Client.Aff.purs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
-- | Bindings to the *Node.js* HTTP/2 Client Core API.
22
-- |
3-
-- | This module provides an `Aff` API so that
4-
-- | “you don’t even have to think about callbacks.”
5-
-- | See [__Escaping Callback Hell__](https://github.com/purescript-contrib/purescript-aff/tree/main/docs#escaping-callback-hell).
6-
-- |
73
-- | ## Client-side example
84
-- |
95
-- | Equivalent to
@@ -14,23 +10,23 @@
1410
-- |
1511
-- | ca <- liftEffect $ Node.FS.Sync.readFile "localhost-cert.pem"
1612
-- |
17-
-- | clientsession <- connect
13+
-- | client <- connect
1814
-- | (toOptions {ca})
1915
-- | (URL.parse "https://localhost:8443")
2016
-- |
21-
-- | clientstream <- request clientsession
17+
-- | stream <- request client
2218
-- | (toOptions {})
2319
-- | (toHeaders {":path": "/"})
2420
-- |
25-
-- | headers <- waitResponse clientstream
21+
-- | headers <- waitResponse stream
2622
-- | liftEffect $ for_ (headerKeys headers) \name ->
2723
-- | Effect.Console.log $
2824
-- | name <> ": " <> fromMaybe "" (headerString headers name)
2925
-- |
30-
-- | dataString <- toStringUTF8 =<< (fst <$> readAll (toDuplex clientstream))
31-
-- | liftEffect $ Effect.Console.log $ "\n" <> dataString
26+
-- | body <- toStringUTF8 =<< (fst <$> readAll (toDuplex stream))
27+
-- | liftEffect $ Effect.Console.log $ "\n" <> body
3228
-- |
33-
-- | close clientsession
29+
-- | close client
3430
-- | ```
3531
module Node.HTTP2.Client.Aff
3632
( connect

src/Node/HTTP2/Client.purs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@
88
-- | ```
99
-- | ca <- Node.FS.Sync.readFile "localhost-cert.pem"
1010
-- |
11-
-- | clientsession <- connect
11+
-- | client <- connect
1212
-- | (URL.parse "https://localhost:8443")
1313
-- | (toOptions {ca})
1414
-- | (\_ _ -> pure unit)
1515
-- |
16-
-- | clientstream <- request clientsession
16+
-- | stream <- request client
1717
-- | (toHeaders {":path": "/"})
1818
-- | (toOptions {})
1919
-- |
20-
-- | onceResponse clientstream
20+
-- | onceResponse stream
2121
-- | \headers flags ->
2222
-- | for_ (headerKeys headers) \name ->
2323
-- | Effect.Console.log $
2424
-- | name <> ": " <> fromMaybe "" (headerValueString headers name)
2525
-- |
26-
-- | let req = toDuplex clientstream
26+
-- | let req = toDuplex stream
2727
-- |
2828
-- | dataRef <- liftST $ Control.Monad.ST.Ref.new ""
2929
-- | Node.Stream.onDataString req Node.Encoding.UTF8
3030
-- | \chunk -> void $ liftST $ Control.Monad.ST.Ref.modify (_ <> chunk) dataRef
3131
-- | Node.Stream.onEnd req do
3232
-- | dataString <- liftST $ Control.Monad.ST.Ref.read dataRef
3333
-- | Effect.Console.log $ "\n" <> dataString
34-
-- | close clientsession
34+
-- | close client
3535
-- | ```
3636
module Node.HTTP2.Client
3737
( ClientHttp2Session

src/Node/HTTP2/Server.Aff.purs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
-- | key <- Node.FS.Sync.readFile "localhost-privkey.pem"
1212
-- | cert <- Node.FS.Sync.readFile "localhost-cert.pem"
1313
-- |
14-
-- | void $ listenSecure (toOptions {key, cert}) (toOptions {port:8443 })
15-
-- | \server _headers stream -> do
14+
-- | void $ listenSecure (toOptions {key, cert}) (toOptions {port:8443})
15+
-- | (\server err -> liftEffect $ Console.errorShow err)
16+
-- | \server headers stream -> do
1617
-- | respond stream
1718
-- | (toOptions {})
1819
-- | (toHeaders
1920
-- | { "content-type": "text/html; charset=utf-8"
2021
-- | , ":status": 200
2122
-- | }
2223
-- | )
23-
-- | write (toDuplex stream) =<< fromStringUTF8 ("<html>Hello World</html>")
24+
-- | write (toDuplex stream) =<< fromStringUTF8 ("<h1>Hello World<hl>")
2425
-- | end (toDuplex stream)
2526
-- | closeSecure server
2627
-- | ```
@@ -153,9 +154,9 @@ closeSecure server = makeAff \complete -> do
153154
-- | Push a stream to the client, with the client request headers for a
154155
-- | request which the client did not send but to which the server will respond.
155156
-- |
156-
-- | On the new pushed stream, call
157-
-- | `respond`
158-
-- | and
157+
-- | On the new pushed stream, it is mandatory to first call `respond`.
158+
-- |
159+
-- | Then call
159160
-- | [`Node.Stream.Aff.write`](https://pursuit.purescript.org/packages/purescript-node-streams-aff/docs/Node.Stream.Aff#v:write)
160161
-- | and
161162
-- | [`Node.Stream.Aff.end`](https://pursuit.purescript.org/packages/purescript-node-streams-aff/docs/Node.Stream.Aff#v:end).
@@ -164,8 +165,8 @@ closeSecure server = makeAff \complete -> do
164165
-- |
165166
-- | > Calling `http2stream.pushStream()` from within a pushed stream is not permitted and will throw an error.
166167
pushStream :: ServerHttp2Stream -> OptionsObject -> Headers -> Aff ServerHttp2Stream
167-
pushStream stream optionsobject headers = makeAff \complete -> do
168-
Server.pushStream stream headers optionsobject \nerr pushedstream _ -> do
168+
pushStream stream optionsobject headersRequest = makeAff \complete -> do
169+
Server.pushStream stream headersRequest optionsobject \nerr pushedstream _ -> do
169170
case toMaybe nerr of
170171
Just err -> complete (Left err)
171172
Nothing -> complete (Right pushedstream)

test/HTTP2.purs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import Node.Stream as Node.Stream
1717
import Node.URL as URL
1818

1919

20-
testHttp2ServerSecure :: Effect Unit
21-
testHttp2ServerSecure = do
20+
basic_serverSecure :: Effect Unit
21+
basic_serverSecure = do
2222

2323
server <- HTTP2.Server.createSecureServer
2424
(toOptions {key: mockKey, cert: mockCert})
@@ -46,8 +46,8 @@ testHttp2ServerSecure = do
4646
(toOptions { port:8443 })
4747
(pure unit)
4848

49-
testHttp2Client :: Effect Unit
50-
testHttp2Client = do
49+
basic_client :: Effect Unit
50+
basic_client = do
5151

5252
clientsession <- HTTP2.Client.connect
5353
(URL.parse "https://localhost:8443")

test/HTTP2Aff.purs

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ module Test.HTTP2Aff where
22

33
import Prelude
44

5+
import Control.Alternative ((<|>))
6+
import Data.Foldable (traverse_)
7+
import Data.Maybe (fromMaybe)
8+
import Data.String as String
59
import Data.Tuple (fst)
6-
import Effect.Aff (Aff, throwError)
10+
import Effect.Aff (Aff, parallel, sequential, throwError)
711
import Effect.Class (liftEffect)
812
import Effect.Console as Console
9-
import Node.HTTP2 (fromStringUTF8, toHeaders, toOptions, toStringUTF8)
10-
import Node.HTTP2.Client.Aff (waitResponse)
13+
import Node.HTTP2 (Headers, fromStringUTF8, headerArray, headerKeys, headerString, toHeaders, toOptions, toStringUTF8)
1114
import Node.HTTP2.Client.Aff as Client.Aff
1215
import Node.HTTP2.Server.Aff as Server.Aff
1316
import Node.Stream.Aff (end, readAll, readSome, write)
1417
import Node.URL as URL
1518

16-
testHttp2ServerSecureAff :: Aff Unit
17-
testHttp2ServerSecureAff = do
19+
push1_serverSecure :: Aff Unit
20+
push1_serverSecure = do
1821

1922
-- 1. Start the server, wait for a connection.
2023
void $ Server.Aff.listenSecure
@@ -34,6 +37,7 @@ testHttp2ServerSecureAff = do
3437

3538
-- 4. Push a new stream.
3639
stream2 <- Server.Aff.pushStream stream (toOptions {}) (toHeaders {})
40+
Server.Aff.respond stream2 (toOptions {}) (toHeaders {})
3741
let s2 = Server.Aff.toDuplex stream2
3842
write s2 =<< fromStringUTF8 "HTTP/2 secure push body Aff"
3943
end s2
@@ -42,11 +46,11 @@ testHttp2ServerSecureAff = do
4246
end s
4347

4448
-- 5. After one session, stop the server.
45-
Server.Aff.closeServerSecure server
49+
Server.Aff.closeSecure server
4650

4751

48-
testHttp2ClientAff :: Aff Unit
49-
testHttp2ClientAff = do
52+
push1_client :: Aff Unit
53+
push1_client = do
5054

5155
-- 1. Begin the session, open a connection.
5256
session <- Client.Aff.connect
@@ -80,6 +84,87 @@ testHttp2ClientAff = do
8084

8185

8286

87+
headers_serverSecure :: Aff Unit
88+
headers_serverSecure = do
89+
90+
-- 1. Start the server, wait for a connection.
91+
void $ Server.Aff.listenSecure
92+
(toOptions {key: mockKey, cert: mockCert})
93+
(toOptions {port: 8444})
94+
(\_server err -> throwError err)
95+
\server headers stream -> do
96+
liftEffect $ Console.log $ "SERVER " <> headersShow headers
97+
98+
-- 2. Receive a request.
99+
let s = Server.Aff.toDuplex stream
100+
101+
-- 3. Send a response.
102+
Server.Aff.respond stream (toOptions {}) $ toHeaders
103+
{ "normal": "server normal header"
104+
}
105+
-- Error [ERR_HTTP2_HEADERS_AFTER_RESPOND]: Cannot specify additional headers after response initiated
106+
-- Server.Aff.sendHeadersAdditional stream $ toHeaders
107+
-- { "additional": "server additional header"
108+
-- }
109+
110+
-- 4. Push a new stream.
111+
stream2 <- Server.Aff.pushStream stream (toOptions {}) (toHeaders {})
112+
Server.Aff.respond stream2 (toOptions {})
113+
( toHeaders
114+
{ "pushnormal": "server normal pushed header"
115+
}
116+
)
117+
let s2 = Server.Aff.toDuplex stream2
118+
end s2
119+
120+
-- 5. Close the connection.
121+
end s
122+
123+
-- 5. After one session, stop the server.
124+
Server.Aff.closeSecure server
125+
126+
127+
128+
headers_client :: Aff Unit
129+
headers_client = do
130+
131+
-- 1. Begin the session, open a connection.
132+
session <- Client.Aff.connect
133+
(toOptions {ca: mockCert})
134+
(URL.parse "https://localhost:8444")
135+
136+
-- 2. Send a request.
137+
stream <- Client.Aff.request session (toOptions {}) $ toHeaders
138+
{ "normal": "client normal header"
139+
}
140+
let s = Client.Aff.toDuplex stream
141+
end s
142+
143+
144+
145+
sequential $ traverse_ parallel
146+
[ do
147+
-- 4. Receive a pushed stream.
148+
{headersRequest, headersResponse} <- Client.Aff.waitPush session
149+
liftEffect $ Console.log $ "CLIENT Request " <> headersShow headersRequest
150+
liftEffect $ Console.log $ "CLIENT Response " <> headersShow headersResponse
151+
, do
152+
-- 3. Wait for the response.
153+
headers <- Client.Aff.waitResponse stream
154+
liftEffect $ Console.log $ "CLIENT " <> headersShow headers
155+
]
156+
157+
158+
-- 6. Wait for the end of the session then close the connection.
159+
-- Client.Aff.waitEnd stream
160+
Client.Aff.close session
161+
162+
163+
164+
165+
166+
167+
83168

84169
-- https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates
85170
--
@@ -88,6 +173,15 @@ testHttp2ClientAff = do
88173
-- openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -days 3650 -subj '/CN=localhost' -extensions EXT -config <( printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
89174
--
90175

176+
headersShow :: Headers -> String
177+
headersShow headers = String.joinWith ", " $ headerKeys headers <#> \key ->
178+
key <> ": " <>
179+
( fromMaybe "" $
180+
(headerString headers key)
181+
<|>
182+
(String.joinWith " " <$> headerArray headers key)
183+
)
184+
91185
mockCert :: String
92186
mockCert =
93187
"""-----BEGIN CERTIFICATE-----

test/Main.purs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Node.HTTP.Secure as HTTPS
1717
import Node.Net.Socket as Socket
1818
import Node.Stream (Writable, end, pipe, writeString)
1919
import Partial.Unsafe (unsafeCrashWith)
20+
import Test.HTTP2 as HTTP2
2021
import Test.HTTP2Aff as HTTP2Aff
2122
import Unsafe.Coerce (unsafeCoerce)
2223

@@ -29,11 +30,13 @@ main = do
2930
-- testHttpsServer -- TODO this test prevents node event loop from exiting
3031
-- testHttps -- TODO this test prevents node event loop from exiting
3132
-- testCookies
32-
-- HTTP2.testHttp2ServerSecure
33-
-- HTTP2.testHttp2Client
3433
launchAff_ do
35-
HTTP2Aff.testHttp2ServerSecureAff
36-
HTTP2Aff.testHttp2ClientAff
34+
HTTP2Aff.push1_serverSecure
35+
HTTP2Aff.push1_client
36+
HTTP2Aff.headers_serverSecure
37+
HTTP2Aff.headers_client
38+
-- HTTP2.basic_serverSecure
39+
-- HTTP2.basic_client
3740

3841
respond :: Request -> Response -> Effect Unit
3942
respond req res = do

0 commit comments

Comments
 (0)