Skip to content

Commit 8bc2c73

Browse files
Fix upgrade test; correct/add APIs (#50)
* Update socket types to Socket TCP * ClientRequest: add 'close' event handler * Re-enable upgrade test * Add changelog entry
1 parent bdc4a82 commit 8bc2c73

File tree

6 files changed

+101
-51
lines changed

6 files changed

+101
-51
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ Notable changes to this project are documented in this file. The format is based
66

77
Breaking changes:
88
- Update node libraries to latest releases (#48 by @JordanMartinez)
9-
- Reimplement `http`/`https` bindings (#49 by @JordanMartinez)
9+
- Reimplement `http`/`https` bindings (#49, #50 by @JordanMartinez)
1010

1111
New features:
1212

1313
Bugfixes:
1414

1515
Other improvements:
16+
- Fix flaky `upgrade` test (#50 by @JordanMartinez)
1617

1718
## [v8.0.0](https://github.com/purescript-node/purescript-node-http/releases/tag/v8.0.0) - 2022-04-29
1819

src/Node/HTTP/ClientRequest.purs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Node.HTTP.ClientRequest
22
( toOutgoingMessage
3+
, closeH
34
, connectH
45
, continueH
56
, finishH
@@ -29,13 +30,16 @@ import Node.Buffer (Buffer)
2930
import Node.EventEmitter (EventHandle(..))
3031
import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle3, EventHandle1)
3132
import Node.HTTP.Types (ClientRequest, IMClientRequest, IncomingMessage, OutgoingMessage)
32-
import Node.Stream (Duplex)
33+
import Node.Net.Types (Socket, TCP)
3334
import Unsafe.Coerce (unsafeCoerce)
3435

3536
toOutgoingMessage :: ClientRequest -> OutgoingMessage
3637
toOutgoingMessage = unsafeCoerce
3738

38-
connectH :: EventHandle3 ClientRequest (IncomingMessage IMClientRequest) Duplex Buffer
39+
closeH :: EventHandle0 ClientRequest
40+
closeH = EventHandle "close" identity
41+
42+
connectH :: EventHandle3 ClientRequest (IncomingMessage IMClientRequest) (Socket TCP) Buffer
3943
connectH = EventHandle "connect" \cb -> mkEffectFn3 \a b c -> cb a b c
4044

4145
continueH :: EventHandle0 ClientRequest
@@ -59,13 +63,13 @@ informationH = EventHandle "information" mkEffectFn1
5963
responseH :: EventHandle1 ClientRequest (IncomingMessage IMClientRequest)
6064
responseH = EventHandle "response" mkEffectFn1
6165

62-
socketH :: EventHandle1 ClientRequest Duplex
66+
socketH :: EventHandle1 ClientRequest (Socket TCP)
6367
socketH = EventHandle "socket" mkEffectFn1
6468

6569
timeoutH :: EventHandle0 ClientRequest
6670
timeoutH = EventHandle "timeout" identity
6771

68-
upgradeH :: EventHandle3 ClientRequest (IncomingMessage IMClientRequest) Duplex Buffer
72+
upgradeH :: EventHandle3 ClientRequest (IncomingMessage IMClientRequest) (Socket TCP) Buffer
6973
upgradeH = EventHandle "upgrade" \cb -> mkEffectFn3 \a b c -> cb a b c
7074

7175
foreign import path :: ClientRequest -> String

src/Node/HTTP/IncomingMessage.purs

+4-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import Foreign.Object as Object
3030
import Node.EventEmitter (EventHandle(..))
3131
import Node.EventEmitter.UtilTypes (EventHandle0)
3232
import Node.HTTP.Types (IMClientRequest, IMServer, IncomingMessage)
33-
import Node.Stream (Readable, Duplex)
33+
import Node.Net.Types (Socket, TCP)
34+
import Node.Stream (Readable)
3435
import Unsafe.Coerce (unsafeCoerce)
3536

3637
toReadable :: forall messageType. IncomingMessage messageType -> Readable ()
@@ -65,10 +66,10 @@ rawTrailers im = toMaybe $ rawTrailersImpl im
6566

6667
foreign import rawTrailersImpl :: forall messageType. IncomingMessage messageType -> (Nullable (Array String))
6768

68-
socket :: forall messageType. IncomingMessage messageType -> Effect (Maybe Duplex)
69+
socket :: forall messageType. IncomingMessage messageType -> Effect (Maybe (Socket TCP))
6970
socket im = map toMaybe $ runEffectFn1 socketImpl im
7071

71-
foreign import socketImpl :: forall messageType. EffectFn1 (IncomingMessage messageType) (Nullable Duplex)
72+
foreign import socketImpl :: forall messageType. EffectFn1 (IncomingMessage messageType) (Nullable (Socket TCP))
7273

7374
foreign import statusCode :: IncomingMessage IMClientRequest -> Int
7475

src/Node/HTTP/OutgoingMessage.purs

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import Foreign.Object (Object)
3131
import Node.EventEmitter (EventHandle(..))
3232
import Node.EventEmitter.UtilTypes (EventHandle0)
3333
import Node.HTTP.Types (OutgoingMessage)
34+
import Node.Net.Types (Socket, TCP)
3435
import Node.Stream (Writable)
3536
import Unsafe.Coerce (unsafeCoerce)
3637

@@ -111,7 +112,7 @@ setTimeout msecs msg = runEffectFn2 setTimeoutImpl msecs msg
111112

112113
foreign import setTimeoutImpl :: EffectFn2 (Milliseconds) (OutgoingMessage) (Unit)
113114

114-
socket :: OutgoingMessage -> Effect (Maybe (Writable ()))
115+
socket :: OutgoingMessage -> Effect (Maybe (Socket TCP))
115116
socket msg = map toMaybe $ runEffectFn1 socketImpl msg
116117

117-
foreign import socketImpl :: EffectFn1 (OutgoingMessage) (Nullable (Writable ()))
118+
foreign import socketImpl :: EffectFn1 (OutgoingMessage) (Nullable (Socket TCP))

src/Node/HTTP/Server.purs

+3-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import Node.Buffer (Buffer)
4545
import Node.EventEmitter (EventHandle(..))
4646
import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle2, EventHandle3, EventHandle1)
4747
import Node.HTTP.Types (Encrypted, HttpServer', IMServer, IncomingMessage, ServerResponse)
48-
import Node.Net.Types (Server, TCP)
48+
import Node.Net.Types (Server, Socket, TCP)
4949
import Node.Stream (Duplex)
5050
import Node.TLS.Types (TlsServer)
5151
import Unsafe.Coerce (unsafeCoerce)
@@ -76,7 +76,7 @@ clientErrorH = EventHandle "clientError" \cb -> mkEffectFn2 \a b -> cb a b
7676
closeH :: forall transmissionType. EventHandle0 (HttpServer' transmissionType)
7777
closeH = EventHandle "close" identity
7878

79-
connectH :: forall transmissionType. EventHandle3 (HttpServer' transmissionType) (IncomingMessage IMServer) Duplex Buffer
79+
connectH :: forall transmissionType. EventHandle3 (HttpServer' transmissionType) (IncomingMessage IMServer) (Socket TCP) Buffer
8080
connectH = EventHandle "connect" \cb -> mkEffectFn3 \a b c -> cb a b c
8181

8282
connectionH :: forall transmissionType. EventHandle1 (HttpServer' transmissionType) Duplex
@@ -88,7 +88,7 @@ dropRequestH = EventHandle "dropRequest" \cb -> mkEffectFn2 \a b -> cb a b
8888
requestH :: forall transmissionType. EventHandle2 (HttpServer' transmissionType) (IncomingMessage IMServer) ServerResponse
8989
requestH = EventHandle "request" \cb -> mkEffectFn2 \a b -> cb a b
9090

91-
upgradeH :: forall transmissionType. EventHandle3 (HttpServer' transmissionType) (IncomingMessage IMServer) Duplex Buffer
91+
upgradeH :: forall transmissionType. EventHandle3 (HttpServer' transmissionType) (IncomingMessage IMServer) (Socket TCP) Buffer
9292
upgradeH = EventHandle "upgrade" \cb -> mkEffectFn3 \a b c -> cb a b c
9393

9494
closeAllConnections :: forall transmissionType. (HttpServer' transmissionType) -> Effect Unit

test/Main.purs

+81-38
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ module Test.Main where
22

33
import Prelude
44

5-
import Data.Foldable (foldMap)
5+
import Data.Either (Either(..))
6+
import Data.Foldable (foldMap, for_)
67
import Data.Maybe (fromMaybe)
78
import Effect (Effect)
9+
import Effect.Aff (launchAff_, makeAff, nonCanceler)
10+
import Effect.Class (liftEffect)
811
import Effect.Console (log, logShow)
912
import Effect.Uncurried (EffectFn2)
1013
import Foreign.Object (lookup)
11-
import Node.Buffer (Buffer)
1214
import Node.Buffer as Buffer
1315
import Node.Encoding (Encoding(..))
14-
import Node.EventEmitter (once_)
16+
import Node.EventEmitter (once, once_)
1517
import Node.HTTP as HTTP
1618
import Node.HTTP.ClientRequest as Client
1719
import Node.HTTP.IncomingMessage as IM
@@ -23,7 +25,8 @@ import Node.HTTP.Types (HttpServer', IMServer, IncomingMessage, ServerResponse)
2325
import Node.HTTPS as HTTPS
2426
import Node.Net.Server (listenTcp)
2527
import Node.Net.Server as NetServer
26-
import Node.Stream (Duplex, Writable, end, pipe)
28+
import Node.Net.Socket as Socket
29+
import Node.Stream (Writable, end, pipe)
2730
import Node.Stream as Stream
2831
import Partial.Unsafe (unsafeCrashWith)
2932
import Unsafe.Coerce (unsafeCoerce)
@@ -35,7 +38,7 @@ foreign import stdout :: forall r. Writable r
3538
main :: Effect Unit
3639
main = do
3740
testBasic
38-
-- testUpgrade
41+
testUpgrade
3942
testHttpsServer
4043
testHttps
4144
testCookies
@@ -195,44 +198,60 @@ logResponse response = void do
195198
pipe (IM.toReadable response) stdout
196199

197200
testUpgrade :: Effect Unit
198-
testUpgrade = do
199-
server <- HTTP.createServer
200-
server # once_ Server.upgradeH handleUpgrade
201-
202-
server # once_ Server.requestH (respond (mempty))
201+
testUpgrade = launchAff_ do
202+
server <- liftEffect HTTP.createServer
203203
let netServer = Server.toNetServer server
204-
netServer # once_ NetServer.listeningH do
205-
log $ "Listening on port " <> show httpPort <> "."
206-
sendRequests
207-
listenTcp netServer { host: "localhost", port: httpPort }
204+
waitUntilListening netServer
205+
206+
-- This tests that the upgrade callback is not called when the request is not an HTTP upgrade
207+
doRegularRequest server
208+
209+
-- These two requests test that the upgrade callback is called and that it has
210+
-- access to the original request and can write to the underlying TCP socket
211+
checkUpgradeRequest server
212+
checkWebSocketUpgrade server
213+
214+
liftEffect do
215+
closeAllConnections server
216+
NetServer.close netServer
208217
where
209218
httpPort = 3000
210219

211-
handleUpgrade :: IncomingMessage IMServer -> Duplex -> Buffer -> Effect Unit
212-
handleUpgrade req socket _ = do
213-
let upgradeHeader = fromMaybe "" $ lookup "upgrade" $ IM.headers req
214-
if upgradeHeader == "websocket" then
215-
void $ Stream.writeString socket UTF8
216-
"HTTP/1.1 101 Switching Protocols\r\nContent-Length: 0\r\n\r\n"
217-
else
218-
void $ Stream.writeString socket UTF8
219-
"HTTP/1.1 426 Upgrade Required\r\nContent-Length: 0\r\n\r\n"
220+
waitUntilListening netServer = makeAff \done -> do
221+
netServer # once_ NetServer.listeningH do
222+
liftEffect $ log $ "Listening on port " <> show httpPort <> "."
223+
done $ Right unit
224+
listenTcp netServer { host: "localhost", port: httpPort }
225+
pure nonCanceler
226+
227+
doRegularRequest server = makeAff \done -> do
228+
rmListener <- server # once Server.upgradeH \_ _ _ -> do
229+
unsafeCrashWith "testUpgrade - regularRequest - got an upgrade request when expected simple request"
230+
server # once_ Server.requestH (respond mempty)
220231

221-
sendRequests :: Effect Unit
222-
sendRequests = do
223-
-- This tests that the upgrade callback is not called when the request is not an HTTP upgrade
224232
reqSimple <- HTTP.requestOpts { port: httpPort }
225233
reqSimple # once_ Client.responseH \response -> do
226234
if (IM.statusCode response /= 200) then
227-
unsafeCrashWith "Unexpected response to simple request on `testUpgrade`"
228-
else
229-
pure unit
235+
unsafeCrashWith $ "testUpgrade - regularRequest - unexpected response to simple request: " <> show (IM.statusCode response)
236+
else do
237+
rmListener
238+
log "testUpgrade - regularRequest - Got regular response."
239+
done $ Right unit
230240
end (OM.toWriteable $ Client.toOutgoingMessage reqSimple)
241+
pure nonCanceler
242+
243+
checkUpgradeRequest server = makeAff \done -> do
244+
rmListener <- server # once Server.requestH \_ -> do
245+
unsafeCrashWith "testUpgrade - checkUpgradeRequest - request handler fired instead of upgrade handler"
246+
server # once_ Server.upgradeH \req socket _ -> do
247+
case fromMaybe "" $ lookup "upgrade" $ IM.headers req of
248+
"websocket" ->
249+
unsafeCrashWith "testUpgrade - checkUpgradeRequest - expected non-websocket upgrade but got websocket upgrade"
250+
_ -> do
251+
void $ Stream.writeString (Socket.toDuplex socket) UTF8
252+
"HTTP/1.1 426 Upgrade Required\r\nContent-Length: 0\r\n\r\n"
253+
void $ Stream.end (Socket.toDuplex socket)
231254

232-
{-
233-
These two requests test that the upgrade callback is called and that it has
234-
access to the original request and can write to the underlying TCP socket
235-
-}
236255
reqUpgrade <- HTTP.requestOpts
237256
{ port: httpPort
238257
, headers: unsafeCoerce
@@ -242,10 +261,25 @@ testUpgrade = do
242261
}
243262
reqUpgrade # once_ Client.responseH \response -> do
244263
if (IM.statusCode response /= 426) then
245-
unsafeCrashWith "Unexpected response to upgrade request on `testUpgrade`"
246-
else
247-
pure unit
264+
unsafeCrashWith $ "Unexpected response to upgrade request on `testUpgrade`: " <> show (IM.statusCode response)
265+
else do
266+
rmListener
267+
log "testUpgrade - checkUpgradeRequest - Got upgrade required response."
268+
done $ Right unit
248269
end (OM.toWriteable $ Client.toOutgoingMessage reqUpgrade)
270+
pure nonCanceler
271+
272+
checkWebSocketUpgrade server = makeAff \done -> do
273+
rmListener <- server # once Server.requestH \_ -> do
274+
unsafeCrashWith "testUpgrade - checkWebSocketUpgrade - request handler fired instead of upgrade handler"
275+
server # once_ Server.upgradeH \req socket _ -> do
276+
case fromMaybe "" $ lookup "upgrade" $ IM.headers req of
277+
"websocket" -> do
278+
void $ Stream.writeString (Socket.toDuplex socket) UTF8
279+
"HTTP/1.1 101 Switching Protocols\r\nContent-Length: 0\r\n\r\n"
280+
void $ Stream.end (Socket.toDuplex socket)
281+
_ ->
282+
unsafeCrashWith "testUpgrade - checkWebSocketUpgrade - expected websocket upgrade but got non-websocket upgrade"
249283

250284
reqWSUpgrade <- HTTP.requestOpts
251285
{ port: httpPort
@@ -257,6 +291,15 @@ testUpgrade = do
257291
reqWSUpgrade # once_ Client.responseH \response -> do
258292
if (IM.statusCode response /= 101) then
259293
unsafeCrashWith "Unexpected response to websocket upgrade request on `testUpgrade`"
260-
else
261-
pure unit
294+
else do
295+
rmListener
296+
mbSocket <- IM.socket response
297+
for_ mbSocket \socket -> do
298+
log "Destroying socket"
299+
Stream.destroy (Socket.toDuplex socket)
300+
log "testUpgrade - checkWebSocketUpgrade - Successfully upgraded to websocket."
301+
done $ Right unit
302+
262303
end (OM.toWriteable $ Client.toOutgoingMessage reqWSUpgrade)
304+
pure nonCanceler
305+

0 commit comments

Comments
 (0)