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\n Content-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\n Content-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
0 commit comments