///|
/// A ceiling on a request body. A `Content-Length` above it is answered `413` at
/// once, without calling the application. A body that grows past it while the
/// application reads it is answered `413` too, when no response has started;
/// the application then reads a disconnect, and what it sends afterwards is
/// dropped, since the client already has its answer.
pub(all) struct BodyLimit {
limit : Int32
} derive(Eq, Debug)
///|
pub extend BodyLimit with Eq::{equal, not_equal}
///|
pub extend BodyLimit with Debug::{to_repr}
///|
/// One mebibyte, nginx's `client_max_body_size`.
pub let body_limit : BodyLimit = { limit: 1 << 20, }
///|
pub fn BodyLimit::new(limit~ : Int32 = body_limit.limit) -> BodyLimit {
{ limit, }
}
///|
pub impl Layer for BodyLimit with fn wrap(self, app) {
(scope, receive, send) => {
guard scope is Http(hs) else {
app(scope, receive, send)
return
}
let declared = header(hs.headers, "content-length").map(v => {
@string.parse_int64(v.trim(chars=" ")) catch {
_ => -1L
}
})
if declared is Some(n) && n > self.limit.to_int64() {
respond(send, 413, [], "Content Too Large")
return
}
let mut seen = 0
let mut started = false
let mut refused = false
let guarded_receive : @spec.Receive = () => {
if refused {
return HttpDisconnect
}
let event = receive()
if event is HttpRequest(body~, ..) {
seen += body.length()
if seen > self.limit {
refused = true
if !started {
started = true
respond(send, 413, [], "Content Too Large")
}
return HttpDisconnect
}
}
event
}
let guarded_send : @spec.Send = event => {
if refused {
return
}
if event is HttpResponseStart(..) {
started = true
}
send(event)
}
app(scope, guarded_receive, guarded_send)
}
}
///|
pub extend BodyLimit with Layer::{wrap}