RateLimit header fields for HTTP
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::utils::{account::Account, jmap::JmapUtils, server::TestServer, smtp::SmtpConnection};
|
||||
use crate::utils::{
|
||||
account::Account, http::HttpRequest, jmap::JmapUtils, server::TestServer, smtp::SmtpConnection,
|
||||
};
|
||||
use email::{cache::MessageCacheFetch, mailbox::INBOX_ID};
|
||||
use jmap::blob::upload::DISABLE_UPLOAD_QUOTA;
|
||||
use jmap_client::{
|
||||
@@ -102,6 +104,12 @@ pub async fn test(test: &mut TestServer) {
|
||||
// Test temporary blob quota (3 files)
|
||||
DISABLE_UPLOAD_QUOTA.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
let client = account.jmap_client().await;
|
||||
let raw_http = HttpRequest::with_credentials(
|
||||
8899,
|
||||
"user1@example.org",
|
||||
"this is a very strong password1",
|
||||
);
|
||||
let upload_url = format!("/jmap/upload/{account_id}");
|
||||
for i in 0..3 {
|
||||
assert_eq!(
|
||||
client
|
||||
@@ -112,14 +120,45 @@ pub async fn test(test: &mut TestServer) {
|
||||
1024
|
||||
);
|
||||
}
|
||||
match client
|
||||
.upload(None, vec![b'Z'; 1024], None)
|
||||
.await
|
||||
.unwrap_err()
|
||||
{
|
||||
jmap_client::Error::Problem(err) if err.detail().unwrap().contains("quota") => (),
|
||||
other => panic!("Unexpected error: {:?}", other),
|
||||
}
|
||||
let resp = raw_http
|
||||
.send_full(
|
||||
hyper::Method::POST,
|
||||
&upload_url,
|
||||
Some(vec![b'Z'; 1024]),
|
||||
Some("application/octet-stream"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
resp.status.as_u16(),
|
||||
429,
|
||||
"blob-files-quota body: {}",
|
||||
resp.body
|
||||
);
|
||||
let policy = resp
|
||||
.rate_limit_policy()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit-Policy on {:?}", resp.headers));
|
||||
assert!(
|
||||
policy.contains("\"blob-upload-files\";q=3"),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
assert!(
|
||||
policy.contains("\"blob-upload-bytes\";q=50000")
|
||||
&& policy.contains(r#"qu="content-bytes""#),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
let state = resp
|
||||
.rate_limit()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit on {:?}", resp.headers));
|
||||
assert!(
|
||||
state.contains("\"blob-upload-files\";r=0") && state.contains("t="),
|
||||
"RateLimit = {state}"
|
||||
);
|
||||
assert!(
|
||||
resp.retry_after().is_some(),
|
||||
"missing Retry-After on {:?}",
|
||||
resp.headers
|
||||
);
|
||||
assert!(resp.body.contains("quota"), "body = {}", resp.body);
|
||||
test.blob_expire_all().await;
|
||||
|
||||
// Test temporary blob quota (50000 bytes)
|
||||
@@ -134,14 +173,40 @@ pub async fn test(test: &mut TestServer) {
|
||||
25000
|
||||
);
|
||||
}
|
||||
match client
|
||||
.upload(None, vec![b'z'; 1024], None)
|
||||
.await
|
||||
.unwrap_err()
|
||||
{
|
||||
jmap_client::Error::Problem(err) if err.detail().unwrap().contains("quota") => (),
|
||||
other => panic!("Unexpected error: {:?}", other),
|
||||
}
|
||||
let resp = raw_http
|
||||
.send_full(
|
||||
hyper::Method::POST,
|
||||
&upload_url,
|
||||
Some(vec![b'z'; 1024]),
|
||||
Some("application/octet-stream"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
resp.status.as_u16(),
|
||||
429,
|
||||
"blob-bytes-quota body: {}",
|
||||
resp.body
|
||||
);
|
||||
let policy = resp
|
||||
.rate_limit_policy()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit-Policy on {:?}", resp.headers));
|
||||
assert!(
|
||||
policy.contains("\"blob-upload-bytes\";q=50000")
|
||||
&& policy.contains(r#"qu="content-bytes""#),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
let state = resp
|
||||
.rate_limit()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit on {:?}", resp.headers));
|
||||
assert!(
|
||||
state.contains("\"blob-upload-bytes\";r=0") && state.contains("t="),
|
||||
"RateLimit = {state}"
|
||||
);
|
||||
assert!(
|
||||
resp.retry_after().is_some(),
|
||||
"missing Retry-After on {:?}",
|
||||
resp.headers
|
||||
);
|
||||
test.blob_expire_all().await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1100)).await;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use crate::{
|
||||
system::authentication::validate_password_with_ip,
|
||||
utils::{
|
||||
http::HttpRequest,
|
||||
imap::{ImapConnection, Type},
|
||||
registry::UnwrapRegistryId,
|
||||
server::TestServer,
|
||||
@@ -271,6 +272,11 @@ pub async fn test(test: &mut TestServer) {
|
||||
|
||||
// Concurrent requests check
|
||||
let client = Arc::new(client);
|
||||
let raw_http = HttpRequest::with_credentials(
|
||||
8899,
|
||||
"user@example.org",
|
||||
"this is a very strong password",
|
||||
);
|
||||
for _ in 0..8 {
|
||||
let client_ = client.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -283,14 +289,48 @@ pub async fn test(test: &mut TestServer) {
|
||||
});
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
assert!(matches!(
|
||||
client
|
||||
.mailbox_query(
|
||||
mailbox::query::Filter::name("__sleep").into(),
|
||||
[mailbox::query::Comparator::name()].into(),
|
||||
)
|
||||
.await,
|
||||
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||
"methodCalls": [
|
||||
["Mailbox/query", {
|
||||
"accountId": user_id.to_string(),
|
||||
"filter": { "name": "__sleep" }
|
||||
}, "c1"]
|
||||
]
|
||||
}))
|
||||
.unwrap();
|
||||
let resp = raw_http
|
||||
.send_full(
|
||||
hyper::Method::POST,
|
||||
"/jmap/",
|
||||
Some(body),
|
||||
Some("application/json"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
resp.status.as_u16(),
|
||||
400,
|
||||
"concurrent-requests body: {}",
|
||||
resp.body
|
||||
);
|
||||
let policy = resp
|
||||
.rate_limit_policy()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit-Policy header on {:?}", resp.headers));
|
||||
assert!(
|
||||
policy.contains("\"concurrent-requests\"") && policy.contains("q=8"),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
assert!(
|
||||
policy.contains(r#"qu="concurrent-requests""#),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
let state = resp
|
||||
.rate_limit()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit header on {:?}", resp.headers));
|
||||
assert!(
|
||||
state.contains("\"concurrent-requests\"") && state.contains("r=0"),
|
||||
"RateLimit = {state}"
|
||||
);
|
||||
|
||||
// Wait for sleep to be done
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
@@ -303,9 +343,37 @@ pub async fn test(test: &mut TestServer) {
|
||||
});
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
assert!(matches!(
|
||||
client.upload(None, b"sleep".to_vec(), None).await,
|
||||
Err(jmap_client::Error::Problem(err)) if err.status() == Some(400)));
|
||||
let resp = raw_http
|
||||
.send_full(
|
||||
hyper::Method::POST,
|
||||
&format!("/jmap/upload/{user_id}"),
|
||||
Some(b"sleep".to_vec()),
|
||||
Some("application/octet-stream"),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
resp.status.as_u16(),
|
||||
400,
|
||||
"concurrent-uploads body: {}",
|
||||
resp.body
|
||||
);
|
||||
let policy = resp
|
||||
.rate_limit_policy()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit-Policy header on {:?}", resp.headers));
|
||||
assert!(
|
||||
policy.contains("\"concurrent-uploads\"") && policy.contains("q=4"),
|
||||
"RateLimit-Policy = {policy}"
|
||||
);
|
||||
let state = resp
|
||||
.rate_limit()
|
||||
.unwrap_or_else(|| panic!("missing RateLimit header on {:?}", resp.headers));
|
||||
assert!(
|
||||
state.contains("\"concurrent-uploads\"") && state.contains("r=0"),
|
||||
"RateLimit = {state}"
|
||||
);
|
||||
|
||||
// Wait for sleep to be done before continuing
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
|
||||
// Disable X-Forwarded-For processing
|
||||
admin
|
||||
|
||||
@@ -158,7 +158,12 @@ async fn pagination_test(test: &mut TestServer) {
|
||||
.await
|
||||
.object_ids()
|
||||
.collect();
|
||||
assert_eq!(asc_order.len(), 12, "expected 12 tasks, got {}", asc_order.len());
|
||||
assert_eq!(
|
||||
asc_order.len(),
|
||||
12,
|
||||
"expected 12 tasks, got {}",
|
||||
asc_order.len()
|
||||
);
|
||||
|
||||
let desc_order: Vec<Id> = asc_order.iter().rev().copied().collect();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user