Skip to content

Commit 0b2e6cd

Browse files
committed
re-writing tests
1 parent 56b649f commit 0b2e6cd

File tree

10 files changed

+298
-357
lines changed

10 files changed

+298
-357
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ Breaking changes:
88

99
New features:
1010

11+
- New module `HTTP2`. Solves #44. (#45 by @jamesdbrock)
12+
- Use __spec__ for tests. Upgraded `ci.yml`. Solves #35. (#45 by @jamesdbrock)
13+
- New function `HTTP.onRequest`. Solves #46. (#45 by @jamesdbrock)
14+
1115
Bugfixes:
1216

1317
Other improvements:
@@ -32,7 +36,7 @@ New features:
3236
Other improvements:
3337
- Migrated CI to GitHub Actions, updated installation instructions to use Spago, and migrated from `jshint` to `eslint` (#30)
3438
- Added a changelog and pull request template (#34)
35-
39+
3640
## [v5.0.2](https://github.com/purescript-node/purescript-node-http/releases/tag/v5.0.2) - 2019-07-24
3741

3842
- Relaxed upper bounds on `node-buffer`

spago.dhall

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
, "newtype"
1616
, "node-buffer"
1717
, "node-net"
18+
, "node-process"
1819
, "node-streams"
1920
, "node-streams-aff"
2021
, "node-url"

src/Node/HTTP.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ export function onUpgrade(server) {
6464
};
6565
}
6666

67+
export function onRequest(server) {
68+
return function (cb) {
69+
return function () {
70+
server.on("request", function (req, res) {
71+
return cb(req)(res)();
72+
});
73+
};
74+
};
75+
}
76+
6777
export function setHeader(res) {
6878
return function (key) {
6979
return function (value) {

src/Node/HTTP.purs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module Node.HTTP
1212
, listenSocket
1313
, onConnect
1414
, onUpgrade
15+
, onRequest
1516

1617
, httpVersion
1718
, requestHeaders
@@ -77,6 +78,9 @@ foreign import onConnect :: Server -> (Request -> Socket -> Buffer -> Effect Uni
7778
-- | Listen to `upgrade` events on the server
7879
foreign import onUpgrade :: Server -> (Request -> Socket -> Buffer -> Effect Unit) -> Effect Unit
7980

81+
-- | Listen to `request` events on the server
82+
foreign import onRequest :: Server -> (Request -> Response -> Effect Unit) -> Effect Unit
83+
8084
-- | Get the request HTTP version
8185
httpVersion :: Request -> String
8286
httpVersion = _.httpVersion <<< unsafeCoerce

test/HTTP.purs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
module Test.HTTP where
2+
3+
import Prelude
4+
5+
import Data.Either (Either(..))
6+
import Data.Foldable (foldMap)
7+
import Data.Maybe (Maybe(..), fromMaybe)
8+
import Data.Options (Options, options, (:=))
9+
import Data.Tuple (Tuple(..))
10+
import Effect (Effect)
11+
import Effect.Console (log, logShow)
12+
import Effect.Exception (Error)
13+
import Foreign.Object (fromFoldable, lookup)
14+
import Node.Encoding (Encoding(..))
15+
import Node.HTTP (Request, Response, close, createServer, listen, onRequest, onUpgrade, requestAsStream, requestHeaders, requestMethod, requestURL, responseAsStream, setHeader, setStatusCode)
16+
import Node.HTTP.Client as Client
17+
import Node.HTTP.Secure as HTTPS
18+
import Node.Net.Socket as Socket
19+
import Node.Process as Node.Process
20+
import Node.Stream (end, pipe, writeString)
21+
import Partial.Unsafe (unsafeCrashWith)
22+
import Test.MockCert (cert, key)
23+
import Unsafe.Coerce (unsafeCoerce)
24+
25+
respond :: Request -> Response -> Effect Unit
26+
respond req res = do
27+
setStatusCode res 200
28+
let
29+
inputStream = requestAsStream req
30+
outputStream = responseAsStream res
31+
log (requestMethod req <> " " <> requestURL req)
32+
case requestMethod req of
33+
"GET" -> do
34+
let
35+
html = foldMap (_ <> "\n")
36+
[ "<form method='POST' action='/'>"
37+
, " <input name='text' type='text'>"
38+
, " <input type='submit'>"
39+
, "</form>"
40+
]
41+
setHeader res "Content-Type" "text/html"
42+
_ <- writeString outputStream UTF8 html mempty
43+
end outputStream (const $ pure unit)
44+
"POST" -> void $ pipe inputStream outputStream
45+
_ -> unsafeCrashWith "Unexpected HTTP method"
46+
47+
testBasic :: (Either Error Unit -> Effect Unit) -> Effect Unit
48+
testBasic complete = do
49+
server <- createServer \_ _ -> pure unit
50+
onRequest server \req res -> do
51+
respond req res
52+
close server $ complete (Right unit)
53+
listen server { hostname: "localhost", port: 8080, backlog: Nothing } do
54+
log "Listening on port 8080."
55+
simpleReq "http://localhost:8080"
56+
57+
testHttpsServer :: Effect Unit
58+
testHttpsServer = do
59+
server <- HTTPS.createServer sslOpts respond
60+
listen server { hostname: "localhost", port: 8081, backlog: Nothing } $ void do
61+
log "Listening on port 8081."
62+
complexReq $
63+
Client.protocol := "https:"
64+
<> Client.method := "GET"
65+
<> Client.hostname := "localhost"
66+
<> Client.port := 8081
67+
<> Client.path := "/"
68+
<>
69+
Client.rejectUnauthorized := false
70+
where
71+
sslOpts =
72+
HTTPS.key := HTTPS.keyString key <>
73+
HTTPS.cert := HTTPS.certString cert
74+
75+
testHttps :: Effect Unit
76+
testHttps =
77+
simpleReq "https://pursuit.purescript.org/packages/purescript-node-http/badge"
78+
79+
testCookies :: Effect Unit
80+
testCookies =
81+
simpleReq
82+
"https://httpbin.org/cookies/set?cookie1=firstcookie&cookie2=secondcookie"
83+
84+
simpleReq :: String -> Effect Unit
85+
simpleReq uri = do
86+
log ("GET " <> uri <> ":")
87+
req <- Client.requestFromURI uri logResponse
88+
end (Client.requestAsStream req) (const $ pure unit)
89+
90+
complexReq :: Options Client.RequestOptions -> Effect Unit
91+
complexReq opts = do
92+
log $ optsR.method <> " " <> optsR.protocol <> "//" <> optsR.hostname <> ":" <> optsR.port <> optsR.path <> ":"
93+
req <- Client.request opts logResponse
94+
end (Client.requestAsStream req) (const $ pure unit)
95+
where
96+
optsR = unsafeCoerce $ options opts
97+
98+
logResponse :: Client.Response -> Effect Unit
99+
logResponse response = void do
100+
log "Headers:"
101+
logShow $ Client.responseHeaders response
102+
log "Cookies:"
103+
logShow $ Client.responseCookies response
104+
log "Response:"
105+
let responseStream = Client.responseAsStream response
106+
pipe responseStream Node.Process.stdout
107+
108+
testUpgrade :: Effect Unit
109+
testUpgrade = do
110+
server <- createServer respond
111+
onUpgrade server handleUpgrade
112+
listen server { hostname: "localhost", port: 3000, backlog: Nothing }
113+
$ void do
114+
log "Listening on port 3000."
115+
sendRequests
116+
where
117+
handleUpgrade req socket _ = do
118+
let upgradeHeader = fromMaybe "" $ lookup "upgrade" $ requestHeaders req
119+
if upgradeHeader == "websocket" then
120+
void
121+
$ Socket.writeString
122+
socket
123+
"HTTP/1.1 101 Switching Protocols\r\nContent-Length: 0\r\n\r\n"
124+
UTF8
125+
$ pure unit
126+
else
127+
void
128+
$ Socket.writeString
129+
socket
130+
"HTTP/1.1 426 Upgrade Required\r\nContent-Length: 0\r\n\r\n"
131+
UTF8
132+
$ pure unit
133+
134+
sendRequests = do
135+
-- This tests that the upgrade callback is not called when the request is not an HTTP upgrade
136+
reqSimple <- Client.request (Client.port := 3000) \response -> do
137+
if (Client.statusCode response /= 200) then
138+
unsafeCrashWith "Unexpected response to simple request on `testUpgrade`"
139+
else
140+
pure unit
141+
end (Client.requestAsStream reqSimple) (const $ pure unit)
142+
{-
143+
These two requests test that the upgrade callback is called and that it has
144+
access to the original request and can write to the underlying TCP socket
145+
-}
146+
let
147+
headers = Client.RequestHeaders $ fromFoldable
148+
[ Tuple "Connection" "upgrade"
149+
, Tuple "Upgrade" "something"
150+
]
151+
reqUpgrade <- Client.request
152+
(Client.port := 3000 <> Client.headers := headers)
153+
\response -> do
154+
if (Client.statusCode response /= 426) then
155+
unsafeCrashWith "Unexpected response to upgrade request on `testUpgrade`"
156+
else
157+
pure unit
158+
end (Client.requestAsStream reqUpgrade) (const $ pure unit)
159+
160+
let
161+
wsHeaders = Client.RequestHeaders $ fromFoldable
162+
[ Tuple "Connection" "upgrade"
163+
, Tuple "Upgrade" "websocket"
164+
]
165+
166+
reqWSUpgrade <- Client.request
167+
(Client.port := 3000 <> Client.headers := wsHeaders)
168+
\response -> do
169+
if (Client.statusCode response /= 101) then
170+
unsafeCrashWith "Unexpected response to websocket upgrade request on `testUpgrade`"
171+
else
172+
pure unit
173+
end (Client.requestAsStream reqWSUpgrade) (const $ pure unit)
174+
pure unit

test/HTTP2.purs

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,26 @@ import Prelude
44

55
import Control.Monad.ST.Class (liftST)
66
import Control.Monad.ST.Ref as ST.Ref
7+
import Data.Either (Either(..))
78
import Data.Foldable (for_)
89
import Data.Maybe (Maybe(..), fromMaybe)
910
import Effect (Effect)
1011
import Effect.Console as Console
11-
import Effect.Exception (throwException)
12+
import Effect.Exception (Error, throwException)
1213
import Node.Encoding as Node.Encoding
1314
import Node.HTTP2 (headerKeys, headerString, sensitiveHeaders, toHeaders, toOptions)
1415
import Node.HTTP2.Client as HTTP2.Client
1516
import Node.HTTP2.Server as HTTP2.Server
1617
import Node.Stream as Node.Stream
1718
import Node.URL as URL
19+
import Test.MockCert (cert, key)
1820
import Unsafe.Coerce (unsafeCoerce)
1921

20-
basic_serverSecure :: Effect Unit
21-
basic_serverSecure = do
22+
basic_serverSecure :: (Either Error Unit -> Effect Unit) -> Effect Unit
23+
basic_serverSecure complete = do
2224

2325
server <- HTTP2.Server.createSecureServer
24-
(toOptions { key: mockKey, cert: mockCert })
26+
(toOptions { key: key, cert: cert })
2527

2628
void $ HTTP2.Server.onceStreamSecure server \stream _ _ -> do
2729
HTTP2.Server.respond stream
@@ -41,7 +43,9 @@ basic_serverSecure = do
4143
Node.Stream.end (HTTP2.Server.toDuplex stream)
4244
$ case _ of
4345
Just err -> throwException err
44-
Nothing -> HTTP2.Server.closeServerSecure server (pure unit)
46+
Nothing -> do
47+
HTTP2.Server.closeServerSecure server (pure unit)
48+
complete (Right unit)
4549

4650
HTTP2.Server.listenSecure server
4751
(toOptions { port: 8443 })
@@ -52,7 +56,7 @@ basic_client = do
5256

5357
clientsession <- HTTP2.Client.connect
5458
(URL.parse "https://localhost:8443")
55-
(toOptions { ca: mockCert })
59+
(toOptions { ca: cert })
5660
(\_ _ -> pure unit)
5761

5862
clientstream <- HTTP2.Client.request clientsession
@@ -88,63 +92,3 @@ headers_sensitive = do
8892
{ "cookie": "some-cookie"
8993
, "other-sensitive-header": "very secret data"
9094
}
91-
92-
-- https://letsencrypt.org/docs/certificates-for-localhost/#making-and-trusting-your-own-certificates
93-
--
94-
-- Generate localhost.crt and localhost.key with 10 year expiration:
95-
--
96-
-- 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")
97-
--
98-
99-
mockCert :: String
100-
mockCert =
101-
"""-----BEGIN CERTIFICATE-----
102-
MIIDDzCCAfegAwIBAgIUUyn89RHpZC9irOiqJpcBqFRw2HgwDQYJKoZIhvcNAQEL
103-
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMTExODAyMTkyN1oXDTMyMTEx
104-
NTAyMTkyN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
105-
AAOCAQ8AMIIBCgKCAQEA0REkgizCB39n47Z3JXcW+GPPym4MXBb9HAHBJbH1+m/R
106-
0EkdunDyXr8cKveABgq3/kazWjXlGwNXUklKYCydcnmtNVBub4s1wXAsegRaPMmo
107-
RzisW7FWaqcLcBMAuwrub2NTVsX0HtO5qZiEKNx6AAbWFizFmMQ9K/9VprT1OLWy
108-
vtIOlR/YK+PKruNWeNpvhx91zmwb69lgrqUcwMHguLWgoz0JJgzh7cerexbT+eKC
109-
CuA9Ub8ctQD8SIl3eF7OzsvmQHSr+yABo3TJj7UZLh0B3j1uB8RLQvenVilc4YPz
110-
MK/R6Jf8RjRssGommbUqVaXRjJfYQ2As2tkzRS90cwIDAQABo1kwVzAUBgNVHREE
111-
DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
112-
MB0GA1UdDgQWBBS5+ngK++/FbHQ4Uf8qMZDK6tSNlDANBgkqhkiG9w0BAQsFAAOC
113-
AQEAj5nTUka4P/hWkV+Wa9Rp/ijqv2ah2ukU1u73QyprG2/gHmFpYvNFJ7lG9O9r
114-
Wuvsz4g4moX9kgt/9GnpUbZBUE7zPau74P06lFcXhKAhiZcpsS+CZbMIsbfilWS0
115-
SBbs8OTLvexOqPP4pTvlc67zPkuB3tjOnHhPar8VSAiBp2s0l6UF2vWZ69Xj3ice
116-
DadE6thrH41GN/OSROKWL6dEueNTuQaU1Rx9Nxh8hvKiDJZ7l8oiHGYERoGwJJro
117-
tWBqRvX/C4TpnS+ckhOyqrHUXN66lVaact9GaBd7n6oCKzDY/GtENCLJnNKte5VI
118-
SATt1Hpnw3S/zwX9imqABqneAA==
119-
-----END CERTIFICATE-----"""
120-
121-
mockKey :: String
122-
mockKey =
123-
"""-----BEGIN PRIVATE KEY-----
124-
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRESSCLMIHf2fj
125-
tncldxb4Y8/KbgxcFv0cAcElsfX6b9HQSR26cPJevxwq94AGCrf+RrNaNeUbA1dS
126-
SUpgLJ1yea01UG5vizXBcCx6BFo8yahHOKxbsVZqpwtwEwC7Cu5vY1NWxfQe07mp
127-
mIQo3HoABtYWLMWYxD0r/1WmtPU4tbK+0g6VH9gr48qu41Z42m+HH3XObBvr2WCu
128-
pRzAweC4taCjPQkmDOHtx6t7FtP54oIK4D1Rvxy1APxIiXd4Xs7Oy+ZAdKv7IAGj
129-
dMmPtRkuHQHePW4HxEtC96dWKVzhg/Mwr9Hol/xGNGywaiaZtSpVpdGMl9hDYCza
130-
2TNFL3RzAgMBAAECggEAJggqTgv6WAbTTVdaIVSitxjhKgAO+4mrDbc7/bF7/8zr
131-
rCpA4DO/w4CcjSxs+6xjgDw4UEbRoLJg5jUy9H/pPHPqEHLLRDtc0g2n6aJ1D+3X
132-
UO18XUnLYKd2qzKpxVzdtyGofXaRTDJT6gg2soA5KVwVAf+vCnVYc3KFkEgG/AOt
133-
jhvbxK+xA4CGjGPYxASO5K3IVJxb419hi8dizgtdJaotysvfspth5WOOoiBtVhuB
134-
6ORZt9DbN1AK9U3nV76NsjHeQWcMsDqt8w/KRkok4X9rkQ86pylZcDUyoqkf+aYB
135-
09FgDiw2iSj9k6kkR0y1o/sRsCN7PoRmJgEhrRWPcQKBgQDlflzyoaVUCIAZQAMo
136-
O3vJE/AEOnvB+eHmqGSi6nGHUxJavxm8dxJRqY2fzA/VeVvp19Nxs1Eh8pskHav4
137-
n+syRtGzkKIE0x9/KThhgzbqZl+NT5afHMhUHvmepMf8J+71giMC2v2yQC0aFVi7
138-
3frv3YNuBbC69FWkeYOjq/MJZwKBgQDpNs6nYmtR3bLBWKoLSOTLGV8Bhhhzt+tu
139-
nm6LVA464ib039m5BoWne890InxgaDNHfuL++n473JFXuwQMBBY3YLD3OPa5uW4a
140-
gt+oYUJKh+qGio395GnZ0W/Sf5GBpdPJ+pTMqGqlo/NWSPuwCdMd5T6RfvnEJzzv
141-
0/jZCAJ5FQKBgCE2yaMADBp+ZHPDFPHksgSnEwy5niGz1aL5ah8+CRJJzpU9pS7m
142-
mMsi2/Ftqjj+KHROnTaOekaMgzGV7ca89mA/aagwXZKPL7bKs3NBd1gzWs7r3uPG
143-
WaP7G6t/M8ZlzSrRG9oU8bSznxNwVXhTJzdB+vyYbDySkjaMs6WjhDgvAoGBAJj0
144-
mE8R7r9Pv1it9UDXey91oWkXcNwciW4QvQHmjDq0bsZ2No7ypyA0xNgvchGs5c0D
145-
fI+s7LQIMs8uWjYjTArgAND0bGVdJ8h9g4Ek4NyPDhNVtlEJyR7SDRwrDNzSTPiQ
146-
v50G7INc51D1JxXLK8rUutekRt4Ouhm1leWKKk0NAoGBALvc9wF7XcgGHZa1RRk9
147-
jH0vOkrn632Epzml1mXg0//2mw+7iQP3q5KtRruaIk6ifLSHznzqAkowhKFH+iCH
148-
wnecLhsl5FnL0JAipIxBHdX0iTttJf4UR/2wTo3RalGjEcMjMCUrSdkhBjRH4Gdc
149-
fBuXFtwhIuiggNR7UlHxbYpq
150-
-----END PRIVATE KEY-----"""

0 commit comments

Comments
 (0)