@@ -9,20 +9,17 @@ structure Result where
99 data : ByteArray
1010
1111/-- Convert an HTTP request to a byte array, optionally using chunked encoding. -/
12- def toByteArray (req : Request (Array Chunk)) (chunked := false ) : IO ByteArray := Async.block do
12+ def requestToByteArray (req : Request (Array Chunk)) (chunked := false ) : IO ByteArray := Async.block do
1313 let mut data := String.toUTF8 <| toString req.head
1414 let toByteArray (part : Chunk) := Internal.Encode.encode .v11 .empty part |>.toByteArray
1515 for part in req.body do data := data ++ (if chunked then toByteArray part else part.data)
1616 if chunked then data := data ++ toByteArray (Chunk.mk .empty .empty)
1717 return data
1818
19- /-- Send multiple requests through a mock connection and return the response data. -/
20- def sendRequests (pair : Mock.Client × Mock.Server) (reqs : Array (Request (Array Chunk)))
21- (onRequest : Request Body → Async (Response Body))
22- (chunked : Bool := false ) : IO ByteArray := Async.block do
19+ /-- Send raw byte stream through a mock connection and return the response data. -/
20+ def sendRawBytes (pair : Mock.Client × Mock.Server) (data : ByteArray)
21+ (onRequest : Request Body → Async (Response Body)) : IO ByteArray := Async.block do
2322 let (client, server) := pair
24- let mut data := .empty
25- for req in reqs do data := data ++ (← toByteArray req chunked)
2623
2724 client.send data
2825 Std.Http.Server.serveConnection server onRequest (config := { lingeringTimeout := 3000 })
@@ -45,24 +42,29 @@ def testSizeLimit (pair : Mock.Client × Mock.Server) : IO Unit := do
4542 |>.status .ok
4643 |>.body "hello robert"
4744
48- let response ← sendRequests pair #[
45+ -- Manually construct the byte stream
46+ let mut data := .empty
47+ data := data ++ (← requestToByteArray (
4948 Request.new
5049 |>.uri! "/ata/po"
5150 |>.header! "Content-Length" "4"
5251 |>.header! "Host" "."
53- |>.body #[.mk "test" .toUTF8 #[]],
52+ |>.body #[.mk "test" .toUTF8 #[]]))
53+ data := data ++ (← requestToByteArray (
5454 Request.new
5555 |>.uri! "/ata/po"
5656 |>.header! "Content-Length" "13"
5757 |>.header! "Connection" "close"
5858 |>.header! "Host" "."
59- |>.body #[.mk "testtesttests" .toUTF8 #[]],
59+ |>.body #[.mk "testtesttests" .toUTF8 #[]]))
60+ data := data ++ (← requestToByteArray (
6061 Request.new
6162 |>.uri! "/ata/po"
6263 |>.header! "Content-Length" "4"
6364 |>.header! "Host" "."
64- |>.body #[.mk "test" .toUTF8 #[]],
65- ] handler
65+ |>.body #[.mk "test" .toUTF8 #[]]))
66+
67+ let response ← sendRawBytes pair data handler
6668
6769 let responseData := String.fromUTF8! response
6870 IO.println <| String.quote responseData
@@ -83,16 +85,17 @@ def testBasicRequest : IO Unit := do
8385 |>.header! "Connection" "close"
8486 |>.body "Hello World"
8587
86- let response ← sendRequests pair #[
88+ let data ← requestToByteArray (
8789 Request.new
8890 |>.uri! "/hello"
8991 |>.header! "Host" "localhost"
9092 |>.header! "User-Agent" "TestClient/1.0"
9193 |>.header! "Connection" "close"
9294 |>.header! "Content-Length" "0"
9395 |>.method .get
94- |>.body #[.mk "" .toUTF8 #[]]
95- ] handler
96+ |>.body #[.mk "" .toUTF8 #[]])
97+
98+ let response ← sendRawBytes pair data handler
9699
97100 let responseData := String.fromUTF8! response
98101 IO.println s! "{ responseData.quote} "
@@ -117,16 +120,17 @@ def testPostRequest : IO Unit := do
117120 |>.header! "Connection" "close"
118121 |>.body s! "Received: { body} "
119122
120- let response ← sendRequests pair #[
123+ let data ← requestToByteArray (
121124 Request.new
122125 |>.uri! "/api/data"
123126 |>.method .post
124127 |>.header! "Host" "localhost"
125128 |>.header! "Content-Type" "application/json"
126129 |>.header! "Content-Length" "25"
127130 |>.header! "Connection" "close"
128- |>.body #[.mk "{\" name\" : \" test\" , \" id\" : 1}" .toUTF8 #[]]
129- ] handler
131+ |>.body #[.mk "{\" name\" : \" test\" , \" id\" : 1}" .toUTF8 #[]])
132+
133+ let response ← sendRawBytes pair data handler
130134
131135 let responseData := String.fromUTF8! response
132136 IO.println s! "{ responseData.quote} "
@@ -146,16 +150,17 @@ def test100Continue : IO Unit := do
146150 |>.status .ok
147151 |>.body "Request processed"
148152
149- let response ← sendRequests pair #[
153+ let data ← requestToByteArray (
150154 Request.new
151155 |>.uri! "/"
152156 |>.method .get
153157 |>.header! "Host" "example.com"
154158 |>.header! "Content-Length" "1"
155159 |>.header! "Expect" "100-continue"
156160 |>.header! "Connection" "close"
157- |>.body #[.mk "a" .toUTF8 #[]]
158- ] handler
161+ |>.body #[.mk "a" .toUTF8 #[]])
162+
163+ let response ← sendRawBytes pair data handler
159164
160165 let responseData := String.fromUTF8! response
161166 IO.println s! "{ responseData.quote} "
@@ -189,16 +194,17 @@ def testMaxRequestSize : IO Unit := do
189194 |> ByteArray.mk
190195 |> String.fromUTF8!
191196
192- let response ← sendRequests pair #[
197+ let data ← requestToByteArray (
193198 Request.new
194199 |>.uri! "/upload"
195200 |>.method .post
196201 |>.header! "Host" "localhost"
197202 |>.header! "Content-Type" "text/plain"
198203 |>.header! "Content-Length" s! "{ largeData.length} "
199204 |>.header! "Connection" "close"
200- |>.body #[.mk largeData.toUTF8 #[]]
201- ] handler
205+ |>.body #[.mk largeData.toUTF8 #[]])
206+
207+ let response ← sendRawBytes pair data handler
202208
203209 let responseData := String.fromUTF8! response
204210 IO.println s! "{ responseData.quote} "
@@ -209,60 +215,37 @@ info: "HTTP/1.1 413 Request Entity Too Large\x0d\nContent-Length: 48\x0d\nConnec
209215#guard_msgs in
210216#eval show IO _ from do testMaxRequestSize
211217
212- def testCut : IO Unit := do
218+ def testChunkedWithTrailer : IO Unit := do
213219 let pair ← Mock.new
214220
215221 let handler := fun (req : Request Body) => do
216- if req.head.headers.hasEntry (.new "Accept" ) "application/json" then
217- return Response.new
218- |>.status .accepted
219- |>.header! "Content-Type" "application/json"
220- |>.body "{\" message\" : \" JSON response\" , \" status\" : \" accepted\" }"
221- else if req.head.headers.hasEntry (.new "Accept" ) "text/xml" then
222- return Response.new
223- |>.status .ok
224- |>.header! "Content-Type" "application/xml"
225- |>.body "<?xml version=\" 1.0\" ?><response><message>XML response</message></response>"
226- else
227- return Response.new
228- |>.status .ok
229- |>.header! "Content-Type" "text/plain"
230- |>.body "Plain text response"
222+ let mut body := ""
223+ for chunk in req.body do
224+ body := body ++ String.fromUTF8! chunk.data
231225
232- let response ← sendRequests pair #[
233- Request.new
234- |>.uri! "/api/content"
235- |>.method .post
236- |>.header! "Host" "localhost"
237- |>.header! "Accept" "application/json"
238- |>.header! "Content-Type" "application/json"
239- |>.header! "Content-Length" "18"
240- |>.body #[.mk "{\" request\" : \" data\" }" .toUTF8 #[]],
241- Request.new
242- |>.uri! "/api/content"
243- |>.method .get
244- |>.header! "Host" "localhost"
245- |>.header! "Accept" "text/xml"
246- |>.header! "Content-Length" "1"
247- |>.body #[.mk "a" .toUTF8 #[]]
248- ] handler
226+ let checksum := req.head.headers.getLast? (.new "X-Checksum" ) |>.map (·.value) |>.getD ""
227+
228+ return Response.new
229+ |>.status .ok
230+ |>.header! "Content-Type" "text/plain"
231+ |>.header! "Connection" "close"
232+ |>.body s! "hello: { body.quote} ."
233+
234+ -- Construct raw ByteArray for chunked request with trailer
235+ -- This cannot be done with Request builder since it doesn't support trailers
236+ let rawRequest := "POST / HTTP/1.1\r\n Host: localhost\r\n User-Agent: TestClient/1.0\r\n Transfer-Encoding: chunked\r\n Trailer: X-Checksum\r\n Accept: */*\r\n\r\n 5\r\n Hello\r\n 6\r\n World\r\n 0\r\n X-Checksum: abcd\r\n\r\n "
237+ let data : ByteArray := rawRequest.toUTF8
238+
239+ let response ← sendRawBytes pair data handler
249240
250241 let responseData := String.fromUTF8! response
251242 IO.println s! "{ responseData.quote} "
252243
253- /-
254244/--
255- info: "HTTP/1.1 202 Accepted \x0d\nContent-Length: 50 \x0d\nServer: LeanHTTP/1.1\x0d\nContent-Type: application/json \x0d\n\x0d\n{\"message\" : \"JSON response\", \"status\": \"accepted\"}HTTP/1.1 400 Bad Request\x0d\nConnection: close\x0d\nServer: LeanHTTP/1.1\x0d\n\x0d\n "
245+ info: "HTTP/1.1 200 OK \x0d\nContent-Length: 21 \x0d\nConnection: close\x0d\ nServer: LeanHTTP/1.1\x0d\nContent-Type: text/plain \x0d\n\x0d\nhello : \"Hello World\". "
256246-/
257247#guard_msgs in
258- #eval show IO _ from do testCut
259-
260- /--
261- info: "HTTP/1.1 200 OK\x0d\nContent-Length: 35\x0d\nConnection: close\x0d\nServer: LeanHTTP/1.1\x0d\nContent-Type: application/json\x0d\n\x0d\nReceived: {\"name\": \"test\", \"id\": 1}"
262- -/
263- #guard_msgs in
264- #eval show IO _ from do testPostRequest
265- -/
248+ #eval show IO _ from do testChunkedWithTrailer
266249
267250def testContentNegotiation : IO Unit := do
268251 let pair ← Mock.new
@@ -284,27 +267,33 @@ def testContentNegotiation : IO Unit := do
284267 |>.header! "Content-Type" "text/plain"
285268 |>.body "Plain text response"
286269
287- let response ← sendRequests pair #[
270+ let mut data := .empty
271+
272+ data := data ++ (← requestToByteArray (
288273 Request.new
289274 |>.uri! "/api/content"
290275 |>.method .post
291276 |>.header! "Host" "localhost"
292277 |>.header! "Accept" "application/json"
293278 |>.header! "Content-Type" "application/json"
294279 |>.header! "Content-Length" "19"
295- |>.body #[.mk "{\" request\" : \" data\" }" .toUTF8 #[]],
280+ |>.body #[.mk "{\" request\" : \" data\" }" .toUTF8 #[]]))
281+
282+ data := data ++ (← requestToByteArray (
296283 Request.new
297284 |>.uri! "/api/content"
298285 |>.method .get
299286 |>.header! "Host" "localhost"
300287 |>.header! "Accept" "text/xml"
301288 |>.header! "Content-Length" "1"
302- |>.body #[.mk "a" .toUTF8 #[]]
303- ] handler
289+ |>.body #[.mk "a" .toUTF8 #[]]))
290+
291+ let response ← sendRawBytes pair data handler
304292
305293 let responseData := String.fromUTF8! response
306294 IO.println s! "{ responseData.quote} "
307295
296+
308297/--
309298info: "HTTP/1.1 202 Accepted\x0d\nContent-Length: 50\x0d\nServer: LeanHTTP/1.1\x0d\nContent-Type: application/json\x0d\n\x0d\n{\"message\": \"JSON response\", \"status\": \"accepted\"}HTTP/1.1 200 OK\x0d\nContent-Length: 73\x0d\nServer: LeanHTTP/1.1\x0d\nContent-Type: application/xml\x0d\n\x0d\n<?xml version=\"1.0\"?><response><message>XML response</message></response>"
310299-/
@@ -331,24 +320,27 @@ def testContentNegotiationError : IO Unit := do
331320 |>.header! "Content-Type" "text/plain"
332321 |>.body "Plain text response"
333322
334- -- Size is 19 of the first so it's wrong.
335- let response ← sendRequests pair #[
323+ -- Size is 19 of the first so it's wrong (Content-Length says 18).
324+ let mut data := .empty
325+ data := data ++ (← requestToByteArray (
336326 Request.new
337327 |>.uri! "/api/content"
338328 |>.method .post
339329 |>.header! "Host" "localhost"
340330 |>.header! "Accept" "application/json"
341331 |>.header! "Content-Type" "application/json"
342332 |>.header! "Content-Length" "18"
343- |>.body #[.mk "{\" request\" : \" data\" }" .toUTF8 #[]],
333+ |>.body #[.mk "{\" request\" : \" data\" }" .toUTF8 #[]]))
334+ data := data ++ (← requestToByteArray (
344335 Request.new
345336 |>.uri! "/api/content"
346337 |>.method .get
347338 |>.header! "Host" "localhost"
348339 |>.header! "Accept" "text/xml"
349340 |>.header! "Content-Length" "1"
350- |>.body #[.mk "a" .toUTF8 #[]]
351- ] handler
341+ |>.body #[.mk "a" .toUTF8 #[]]))
342+
343+ let response ← sendRawBytes pair data handler
352344
353345 let responseData := String.fromUTF8! response
354346 IO.println s! "{ responseData.quote} "
0 commit comments