Skip to content

Commit 65b7a33

Browse files
committed
refactor: part of the connection API
1 parent c0e4c84 commit 65b7a33

File tree

7 files changed

+160
-6
lines changed

7 files changed

+160
-6
lines changed

src/Std/Internal/Http.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module
77

88
prelude
99
public import Std.Internal.Http.Server
10+
public import Std.Internal.Http.Client
1011

1112
public section
1213

src/Std/Internal/Http/Client/Config.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ namespace Config
8686
/--
8787
Convert this client config into an HTTP/1.1 protocol configuration.
8888
-/
89-
def toH1Config (config : Config) : Protocol.H1.Machine.Config :=
89+
def toH1Config (config : Config) : Protocol.H1.Config :=
9090
{ maxMessages := config.maxRequestsPerConnection
9191
maxHeaders := config.maxResponseHeaders
9292
maxHeaderSize := config.maxHeaderValueSize

src/Std/Internal/Http/Client/Connection.lean

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,148 @@ end PersistentConnection
124124

125125
namespace Connection
126126

127+
private inductive Recv
128+
| bytes (x : Option ByteArray)
129+
| timeout
130+
131+
private def receiveWithTimeout [Transport α] (socket : α) (expect : UInt64)
132+
(timeoutMs : Millisecond.Offset) (req : Timestamp) (all : Timestamp) :
133+
Async Recv := do
134+
Selectable.one #[
135+
.case (Transport.recvSelector socket expect) (fun x => pure <| .bytes x),
136+
.case (← Selector.sleep timeoutMs) (fun _ => pure <| .timeout),
137+
.case (← Selector.sleep (req - (← Timestamp.now) |>.toMilliseconds)) (fun _ => pure <| .timeout),
138+
.case (← Selector.sleep (all - (← Timestamp.now) |>.toMilliseconds)) (fun _ => pure <| .timeout)]
139+
140+
private def processNeedMoreData
141+
[Transport α] (config : Config) (socket : α) (expect : Option Nat)
142+
(req : Timestamp) (all : Timestamp):
143+
Async (Except Protocol.H1.Error (Option ByteArray)) := do
144+
try
145+
let expect := expect
146+
|>.getD config.defaultRequestBufferSize
147+
|>.min config.maxRecvChunkSize
148+
149+
let data ← receiveWithTimeout socket expect.toUInt64 config.readTimeout req all
150+
151+
match data with
152+
| .bytes (some bytes) => pure (.ok <| some bytes)
153+
| .bytes none => pure (.ok <| none)
154+
| .timeout => pure (.error .timeout)
155+
156+
catch _ =>
157+
pure (.error .timeout)
158+
127159
private def handle [Transport α] (connection : Connection α) (config : Client.Config) (requestChannel : CloseableChannel RequestPacket) : Async Unit := do
128160
let mut machine := connection.machine
129161
let socket := connection.socket
130162

163+
let mut responseStream ← Body.ByteStream.emptyWithCapacity
164+
let mut waitingForRequest := true
165+
let mut reqStream := none
166+
let mut requestTimer := (← Timestamp.now) + config.requestTimeout.val
167+
let mut connectionTimer := (← Timestamp.now) + config.keepAliveTimeout.val
168+
169+
let mut currentRequest := none
170+
171+
while ¬machine.halted do
172+
if waitingForRequest then
173+
match ← await (← requestChannel.recv) with
174+
| some packet =>
175+
currentRequest := some packet
176+
waitingForRequest := false
177+
178+
machine := machine.send packet.request.head
179+
180+
match packet.request.body with
181+
| .bytes data => machine := machine.sendData #[Chunk.mk data #[]] |>.userClosedBody
182+
| .zero => machine := machine.userClosedBody
183+
| .stream stream =>
184+
if let some size ← stream.getKnownSize then
185+
machine := machine.setKnownSize size
186+
187+
reqStream := some stream
188+
189+
| none =>
190+
break
191+
192+
let (newMachine, step) := machine.step
193+
machine := newMachine
194+
195+
if step.output.size > 0 then
196+
try
197+
Transport.sendAll socket step.output.data
198+
catch e =>
199+
if let some packet := currentRequest then
200+
packet.onError e
201+
202+
for event in step.events do
203+
match event with
204+
| .needMoreData expect => do
205+
try
206+
match ← processNeedMoreData config socket expect requestTimer connectionTimer with
207+
| .ok (some bs) => machine := machine.feed bs
208+
| .ok none => machine := machine.noMoreInput
209+
| .error _ => machine := machine.closeWriter.closeReader.userClosedBody
210+
211+
catch e =>
212+
if let some packet := currentRequest then
213+
packet.onError e
214+
215+
| .endHeaders head => do
216+
if let some (.fixed n) := Protocol.H1.Machine.getMessageSize head then
217+
responseStream.setKnownSize (some n)
218+
219+
if let some packet := currentRequest then
220+
let response := { head := machine.reader.messageHead, body := some (.stream responseStream) }
221+
packet.onResponse response
222+
223+
| .gotData final data =>
224+
discard <| responseStream.write data.toByteArray
225+
226+
if final then
227+
responseStream.close
228+
229+
| .chunkExt _ =>
230+
pure ()
231+
232+
| .failed e =>
233+
if let some packet := currentRequest then
234+
packet.onError (.userError (toString e))
235+
236+
| .close =>
237+
pure ()
238+
239+
| .next =>
240+
requestTimer := (← Timestamp.now) + config.requestTimeout.val
241+
responseStream ← Body.ByteStream.emptyWithCapacity
242+
reqStream := none
243+
currentRequest := none
244+
waitingForRequest := true
245+
246+
if let some stream := reqStream then
247+
if ¬machine.writer.isClosed then
248+
if machine.isReaderComplete then
249+
if let some data ← stream.recv then
250+
machine := machine.sendData data
251+
else
252+
machine := machine.userClosedBody
253+
else
254+
if ← stream.isClosed then
255+
pure ()
256+
else
257+
match ← stream.tryRecv with
258+
| some res => machine := machine.sendData res
259+
| none => machine := machine.userClosedBody
260+
261+
responseStream.close
262+
requestChannel.close
263+
264+
while true do
265+
if let some x ← requestChannel.tryRecv then
266+
x.onError (.userError "connection closed, cannot send more requests")
267+
else
268+
break
131269

132270
end Connection
133271

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ def get (headers : Headers) (name : String) : Option HeaderValue :=
159159
headers.data.get? name.toLower
160160
|>.map (.joinCommaSep ∘ Prod.snd)
161161

162+
/--
163+
Tries to retrieve the `HeaderValue` for the given key.
164+
Returns `none` if the header is absent.
165+
-/
166+
@[inline]
167+
def getAll? (headers : Headers) (name : String) : Option (Array HeaderValue) :=
168+
headers.data.get? name.toLower
169+
|>.map Prod.snd
170+
162171
/--
163172
Tries to check if the entry is present in the `Headers`
164173
-/
@@ -265,8 +274,9 @@ def merge (headers1 headers2 : Headers) : Headers :=
265274

266275
instance : ToString Headers where
267276
toString headers :=
268-
let pairs := headers.data.toList.map (fun (_, (k, vs)) => s!"{k}: {HeaderValue.joinCommaSep vs |>.value}")
269-
String.intercalate "\r\n" pairs
277+
let pairs := headers.data.toArray.flatMap (fun (_, (k, vs)) => vs.map (k, ·))
278+
let pairs := pairs.map (fun (k, vs) => s!"{k}: {vs.value}")
279+
String.intercalate "\r\n" pairs.toList
270280

271281
instance : Encode .v11 Headers where
272282
encode buffer := buffer.writeString ∘ toString

src/Std/Internal/Http/Data/URI/Basic.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ structure Path where
100100
absolute : Bool := false
101101
deriving Inhabited, Repr
102102

103+
instance : ToString Path where
104+
toString path :=
105+
let result := String.intercalate "/" path.segments.toList
106+
if path.absolute then "/" ++ result else result
107+
103108
/--
104109
Query string represented as an array of key–value pairs.
105110
-/

src/Std/Internal/Http/Protocol/H1.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ private def processHeaders (machine : Machine dir) : Machine dir :=
220220
| .fixed n => .needFixedBody n
221221
| .chunked => .needChunkedSize
222222

223-
224223
def setHeaders (messageHead : Message.Head dir.swap) (machine : Machine dir) : Machine dir :=
225224
let hasClose := hasConnectionClose messageHead.headers
226225

@@ -285,7 +284,8 @@ def shouldFlush (machine : Machine dir) : Bool :=
285284
machine.failed ∨
286285
machine.reader.state == .closed ∨
287286
machine.writer.userData.size ≥ machine.config.highMark ∨
288-
machine.writer.isReadyToSend
287+
machine.writer.isReadyToSend ∨
288+
machine.writer.knownSize.isSome
289289

290290
@[inline]
291291
def isReaderComplete (machine : Machine dir) : Bool :=

src/Std/Internal/Http/Server/Connection.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ private def handle
8888
let socket := connection.socket
8989

9090
let mut requestStream ← Body.ByteStream.emptyWithCapacity
91-
let mut requestTimer := (← Timestamp.now ) + config.requestTimeout.val
91+
let mut requestTimer := (← Timestamp.now) + config.requestTimeout.val
9292
let mut connectionTimer := (← Timestamp.now) + config.keepAliveTimeout.val
9393

9494
let mut response ← IO.Promise.new

0 commit comments

Comments
 (0)