Skip to content

Commit 602942a

Browse files
committed
feat: contextual fixes
1 parent 0a27f94 commit 602942a

File tree

8 files changed

+364
-30
lines changed

8 files changed

+364
-30
lines changed

src/Std/Internal/Http/Data/Body.lean

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Authors: Sofia Rodrigues
66
module
77

88
prelude
9+
public import Std.Internal.Async.Context
910
public import Std.Internal.Http.Data.Body.Length
1011
public import Std.Internal.Http.Data.Body.ByteStream
1112

@@ -74,21 +75,19 @@ instance : Coe ByteArray Body where
7475
instance : Coe Body.ByteStream Body where
7576
coe := .stream
7677

77-
/--
78-
Iterate over the body content in chunks, processing each ByteArray chunk with the given step function.
79-
-/
80-
@[inline]
81-
protected partial def forIn
82-
{β : Type} (body : Body) (acc : β)
83-
(step : Chunk → β → Async (ForInStep β)) :
84-
Async β := do
78+
instance : ForIn Async Body Chunk where
79+
forIn body acc step :=
8580
match body with
8681
| .zero => pure acc
8782
| .bytes data => return (← step (Chunk.mk data #[]) acc).value
8883
| .stream stream' => ByteStream.forIn stream' acc step
8984

90-
instance : ForIn Async Body Chunk where
91-
forIn := Body.forIn
85+
instance : ForIn ContextAsync Body Chunk where
86+
forIn body acc step :=
87+
match body with
88+
| .zero => pure acc
89+
| .bytes data => return (← step (Chunk.mk data #[]) acc).value
90+
| .stream stream' => ByteStream.forIn' stream' acc step
9291

9392
/--
9493
Collect all data from the body into a single `ByteArray`. This reads the entire body content into memory,

src/Std/Internal/Http/Data/Body/ByteStream.lean

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,30 @@ protected partial def forIn
161161
instance : ForIn Async ByteStream Chunk where
162162
forIn := Std.Http.Body.ByteStream.forIn
163163

164+
/--
165+
Iterate over the body content in chunks, processing each chunk with the given step function.
166+
-/
167+
@[inline]
168+
protected partial def forIn'
169+
{β : Type} (stream : ByteStream) (acc : β)
170+
(step : Chunk → β → ContextAsync (ForInStep β)) : ContextAsync β := do
171+
172+
let rec @[specialize] loop (stream : ByteStream) (acc : β) : ContextAsync β := do
173+
if let some chunk ← stream.recv then
174+
match ← step chunk acc with
175+
| .done res => return res
176+
| .yield res => loop stream res
177+
else
178+
return acc
179+
180+
loop stream acc
181+
182+
instance : ForIn Async ByteStream Chunk where
183+
forIn := Std.Http.Body.ByteStream.forIn
184+
185+
instance : ForIn ContextAsync ByteStream Chunk where
186+
forIn := Std.Http.Body.ByteStream.forIn'
187+
164188
instance : IO.AsyncRead ByteStream (Option Chunk) where
165189
read stream := stream.recv
166190

src/Std/Internal/Http/Data/Response.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def body (builder : Builder) (body : t) : Response t :=
136136
/--
137137
Builds and returns the final HTTP Response with a stream builder
138138
-/
139-
def stream (builder : Builder) (body : Body.ByteStream → Async Unit) : Async (Response Body) := do
139+
def stream (builder : Builder) (body : Body.ByteStream → ContextAsync Unit) : ContextAsync (Response Body) := do
140140
let stream ← Body.ByteStream.empty
141141
background (body stream)
142142
return { head := builder.head, body := stream }

tests/lean/run/async_http_connection.lean

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Std.Internal.Http
22
import Std.Internal.Async
3+
import Std.Internal.Async.Timer
34

4-
open Std.Internal.IO.Async
5+
open Std.Internal.IO Async
56
open Std Http
67

78
structure TestCase where
@@ -10,7 +11,7 @@ structure TestCase where
1011
/-- The HTTP request to send -/
1112
request : Request (Array Chunk)
1213
/-- Handler function to process the request -/
13-
handler : Request Body → Async (Response Body)
14+
handler : Request Body → ContextAsync (Response Body)
1415
/-- Expected response string -/
1516
expected : String
1617
/-- Whether to use chunked encoding -/
@@ -27,20 +28,21 @@ def toByteArray (req : Request (Array Chunk)) (chunked := false) : IO ByteArray
2728

2829
/-- Send multiple requests through a mock connection and return the response data. -/
2930
def sendRequests (client : Mock.Client) (server : Mock.Server) (reqs : Array (Request (Array Chunk)))
30-
(onRequest : Request Body → Async (Response Body))
31+
(onRequest : Request Body → ContextAsync (Response Body))
3132
(chunked : Bool := false) : IO ByteArray := Async.block do
3233
let mut data := .empty
3334
for req in reqs do data := data ++ (← toByteArray req chunked)
3435

3536
client.send data
3637
Std.Http.Server.serveConnection server onRequest (config := { lingeringTimeout := 3000 })
38+
|>.run (← Context.new)
3739

3840
let res ← client.recv?
3941
pure <| res.getD .empty
4042

4143
/-- Run a single test case, comparing actual response against expected response. -/
4244
def runTest (name : String) (client : Mock.Client) (server : Mock.Server) (req : Request (Array Chunk))
43-
(handler : Request Body → Async (Response Body)) (expected : String) (chunked : Bool := false) :
45+
(handler : Request Body → ContextAsync (Response Body)) (expected : String) (chunked : Bool := false) :
4446
IO Unit := do
4547
let response ← sendRequests client server #[req] handler chunked
4648
let responseData := String.fromUTF8! response
@@ -270,10 +272,10 @@ def hasUri (req : Request Body) (uri : String) : Bool :=
270272
|>.body #[]
271273

272274
handler := fun _ => do
273-
let largeBody := String.mk (List.replicate 1000 'X')
275+
let largeBody := String.ofList (List.replicate 1000 'X')
274276
return Response.ok largeBody
275277

276-
expected := s!"HTTP/1.1 200 OK\x0d\nContent-Length: 1000\x0d\nConnection: close\x0d\nServer: LeanHTTP/1.1\x0d\n\x0d\n{String.mk (List.replicate 1000 'X')}"
278+
expected := s!"HTTP/1.1 200 OK\x0d\nContent-Length: 1000\x0d\nConnection: close\x0d\nServer: LeanHTTP/1.1\x0d\n\x0d\n{String.ofList (List.replicate 1000 'X')}"
277279
}
278280

279281
#eval runTestCase {

tests/lean/run/async_http_connection_limits.lean

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ structure TestCase where
1010
/-- The HTTP request to send -/
1111
request : Request (Array Chunk)
1212
/-- Handler function to process the request -/
13-
handler : Request Body → Async (Response Body)
13+
handler : Request Body → ContextAsync (Response Body)
1414
/-- Expected response string -/
1515
expected : String
1616
/-- Whether to use chunked encoding -/
@@ -27,20 +27,21 @@ def toByteArray (req : Request (Array Chunk)) (chunked := false) : IO ByteArray
2727

2828
/-- Send multiple requests through a mock connection and return the response data. -/
2929
def sendRequests (client : Mock.Client) (server : Mock.Server) (reqs : Array (Request (Array Chunk)))
30-
(onRequest : Request Body → Async (Response Body))
30+
(onRequest : Request Body → ContextAsync (Response Body))
3131
(chunked : Bool := false) : IO ByteArray := Async.block do
3232
let mut data := .empty
3333
for req in reqs do data := data ++ (← toByteArray req chunked)
3434

3535
client.send data
3636
Std.Http.Server.serveConnection server onRequest (config := { lingeringTimeout := 3000 })
37+
|>.run (← Context.new)
3738

3839
let res ← client.recv?
3940
pure <| res.getD .empty
4041

4142
/-- Run a single test case, comparing actual response against expected response. -/
4243
def runTest (name : String) (client : Mock.Client) (server : Mock.Server) (req : Request (Array Chunk))
43-
(handler : Request Body → Async (Response Body)) (expected : String) (chunked : Bool := false) :
44+
(handler : Request Body → ContextAsync (Response Body)) (expected : String) (chunked : Bool := false) :
4445
IO Unit := do
4546
let response ← sendRequests client server #[req] handler chunked
4647
let responseData := String.fromUTF8! response
@@ -116,7 +117,7 @@ def hasUri (req : Request Body) (uri : String) : Bool :=
116117
request :=
117118
Request.new
118119
|>.method .get
119-
|>.uri (.originForm (.mk #[String.mk (List.replicate 2000 'a')] true) none none)
120+
|>.uri (.originForm (.mk #[String.ofList (List.replicate 2000 'a')] true) none none)
120121
|>.header! "Host" "api.example.com"
121122
|>.header! "Connection" "close"
122123
|>.body #[]
@@ -134,8 +135,8 @@ def hasUri (req : Request Body) (uri : String) : Bool :=
134135
request :=
135136
Request.new
136137
|>.method .get
137-
|>.uri (.originForm (.mk #[String.mk (List.replicate 200 'a')] true) none none)
138-
|>.header! "Host" (String.mk (List.replicate 8230 'a'))
138+
|>.uri (.originForm (.mk #[String.ofList (List.replicate 200 'a')] true) none none)
139+
|>.header! "Host" (String.ofList (List.replicate 8230 'a'))
139140
|>.header! "Connection" "close"
140141
|>.body #[]
141142

@@ -173,7 +174,7 @@ def hasUri (req : Request Body) (uri : String) : Bool :=
173174
|>.method .get
174175
|>.uri! "/api/test"
175176
|>.header! "Host" "api.example.com"
176-
|>.header! "X-Long-Value" (String.mk (List.replicate 9000 'x'))
177+
|>.header! "X-Long-Value" (String.ofList (List.replicate 9000 'x'))
177178
|>.header! "Connection" "close"
178179
|>.body #[]
179180

@@ -194,7 +195,7 @@ def hasUri (req : Request Body) (uri : String) : Bool :=
194195
|>.header! "Connection" "close"
195196

196197
for i in [0:200] do
197-
req := req |>.header! s!"X-Header-{i}" (String.mk (List.replicate 200 'a'))
198+
req := req |>.header! s!"X-Header-{i}" (String.ofList (List.replicate 200 'a'))
198199
return req |>.body #[]
199200

200201
handler := fun _ => do

tests/lean/run/async_http_connection_streaming.lean

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ structure TestCase where
1010
/-- The HTTP request to send -/
1111
request : Request (Array Chunk)
1212
/-- Handler function to process the request -/
13-
handler : Request Body → Async (Response Body)
13+
handler : Request Body → ContextAsync (Response Body)
1414
/-- Expected response string -/
1515
expected : String
1616
/-- Whether to use chunked encoding -/
@@ -27,20 +27,21 @@ def toByteArray (req : Request (Array Chunk)) (chunked := false) : IO ByteArray
2727

2828
/-- Send multiple requests through a mock connection and return the response data. -/
2929
def sendRequests (client : Mock.Client) (server : Mock.Server) (reqs : Array (Request (Array Chunk)))
30-
(onRequest : Request Body → Async (Response Body))
30+
(onRequest : Request Body → ContextAsync (Response Body))
3131
(chunked : Bool := false) : IO ByteArray := Async.block do
3232
let mut data := .empty
3333
for req in reqs do data := data ++ (← toByteArray req chunked)
3434

3535
client.send data
3636
Std.Http.Server.serveConnection server onRequest (config := { lingeringTimeout := 3000 })
37+
|>.run (← Context.new)
3738

3839
let res ← client.recv?
3940
pure <| res.getD .empty
4041

4142
/-- Run a single test case, comparing actual response against expected response. -/
4243
def runTest (name : String) (client : Mock.Client) (server : Mock.Server) (req : Request (Array Chunk))
43-
(handler : Request Body → Async (Response Body)) (expected : String) (chunked : Bool := false) :
44+
(handler : Request Body → ContextAsync (Response Body)) (expected : String) (chunked : Bool := false) :
4445
IO Unit := do
4546
let response ← sendRequests client server #[req] handler chunked
4647
let responseData := String.fromUTF8! response

0 commit comments

Comments
 (0)