Skip to content

Commit 182e364

Browse files
committed
Code cleanup
1 parent 169ddd2 commit 182e364

File tree

7 files changed

+860
-757
lines changed

7 files changed

+860
-757
lines changed

.rustfmt.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
max_width = 100
2+
comment_width = 80
3+
wrap_comments = true
4+
imports_granularity = "Crate"
5+
use_small_heuristics = "Default"
6+
group_imports = "StdExternalCrate"

server/src/main.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
#![deny(clippy::all)]
1717
#![warn(clippy::pedantic)]
1818

19-
use bytesize::ByteSize;
20-
use clap::Parser;
2119
use std::{
2220
net::{IpAddr, Ipv4Addr, SocketAddr},
2321
time::Duration,
2422
};
2523

24+
use bytesize::ByteSize;
25+
use clap::Parser;
26+
use matrix_http_rendezvous::{DEFAULT_MAX_BYTES, DEFAULT_MAX_ENTRIES, DEFAULT_TTL};
27+
2628
#[derive(Parser)]
2729
struct Options {
2830
/// Address on which to listen
@@ -38,18 +40,19 @@ struct Options {
3840
prefix: Option<String>,
3941

4042
/// Time to live of entries, in seconds
41-
#[arg(short, long, default_value_t = Duration::from_secs(60).into())]
43+
#[arg(short, long, default_value_t = DEFAULT_TTL.into())]
4244
ttl: humantime::Duration,
4345

4446
/// Maximum number of entries to store
45-
#[arg(short, long, default_value_t = 10000)]
47+
#[arg(short, long, default_value_t = DEFAULT_MAX_ENTRIES)]
4648
capacity: usize,
4749

4850
/// Maximum payload size, in bytes
49-
#[arg(short, long, default_value = "4KiB")]
51+
#[arg(short, long, default_value_t = ByteSize(DEFAULT_MAX_BYTES as u64))]
5052
max_bytes: ByteSize,
5153

52-
/// Set this flag to test how much memory the server might use with a sessions map fully loaded
54+
/// Set this flag to test how much memory the server might use with a
55+
/// sessions map fully loaded
5356
#[arg(long)]
5457
mem_check: bool,
5558
}

src/handlers.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2022 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use axum::{
16+
body::HttpBody,
17+
extract::{DefaultBodyLimit, Path, State},
18+
http::{
19+
header::{CONTENT_TYPE, ETAG, IF_MATCH, IF_NONE_MATCH, LOCATION},
20+
StatusCode,
21+
},
22+
response::{IntoResponse, Response},
23+
routing::{get, post},
24+
Router, TypedHeader,
25+
};
26+
use bytes::Bytes;
27+
use headers::{ContentType, HeaderName, HeaderValue, IfMatch, IfNoneMatch};
28+
use tower_http::{
29+
cors::{Any, CorsLayer},
30+
limit::RequestBodyLimitLayer,
31+
set_header::SetResponseHeaderLayer,
32+
};
33+
use ulid::Ulid;
34+
35+
use crate::Sessions;
36+
37+
async fn new_session(
38+
State(sessions): State<Sessions>,
39+
content_type: Option<TypedHeader<ContentType>>,
40+
payload: Bytes,
41+
) -> impl IntoResponse {
42+
let content_type =
43+
content_type.map_or(mime::APPLICATION_OCTET_STREAM, |TypedHeader(c)| c.into());
44+
let (id, session) = sessions.new_session(payload, content_type).await;
45+
let headers = session.typed_headers();
46+
47+
let location = id.to_string();
48+
let additional_headers = [(LOCATION, location)];
49+
(StatusCode::CREATED, headers, additional_headers)
50+
}
51+
52+
async fn delete_session(State(sessions): State<Sessions>, Path(id): Path<Ulid>) -> StatusCode {
53+
if sessions.delete_session(id).await {
54+
StatusCode::NO_CONTENT
55+
} else {
56+
StatusCode::NOT_FOUND
57+
}
58+
}
59+
60+
async fn update_session(
61+
State(sessions): State<Sessions>,
62+
Path(id): Path<Ulid>,
63+
content_type: Option<TypedHeader<ContentType>>,
64+
if_match: Option<TypedHeader<IfMatch>>,
65+
payload: Bytes,
66+
) -> Response {
67+
if let Some(mut session) = sessions.get_session_mut(id).await {
68+
if let Some(TypedHeader(if_match)) = if_match {
69+
if !if_match.precondition_passes(&session.etag()) {
70+
return (StatusCode::PRECONDITION_FAILED, session.typed_headers()).into_response();
71+
}
72+
}
73+
74+
let content_type =
75+
content_type.map_or(mime::APPLICATION_OCTET_STREAM, |TypedHeader(c)| c.into());
76+
77+
session.update(payload, content_type);
78+
(StatusCode::ACCEPTED, session.typed_headers()).into_response()
79+
} else {
80+
StatusCode::NOT_FOUND.into_response()
81+
}
82+
}
83+
84+
async fn get_session(
85+
State(sessions): State<Sessions>,
86+
Path(id): Path<Ulid>,
87+
if_none_match: Option<TypedHeader<IfNoneMatch>>,
88+
) -> Response {
89+
let session = if let Some(session) = sessions.get_session(id).await {
90+
session
91+
} else {
92+
return StatusCode::NOT_FOUND.into_response();
93+
};
94+
95+
if let Some(TypedHeader(if_none_match)) = if_none_match {
96+
if !if_none_match.precondition_passes(&session.etag()) {
97+
return (StatusCode::NOT_MODIFIED, session.typed_headers()).into_response();
98+
}
99+
}
100+
101+
(
102+
StatusCode::OK,
103+
session.typed_headers(),
104+
TypedHeader(session.content_type()),
105+
session.data(),
106+
)
107+
.into_response()
108+
}
109+
110+
#[must_use]
111+
#[allow(clippy::trait_duplication_in_bounds)]
112+
pub fn router<B>(prefix: &str, sessions: Sessions, max_bytes: usize) -> Router<(), B>
113+
where
114+
B: HttpBody + Send + 'static,
115+
<B as HttpBody>::Data: Send,
116+
<B as HttpBody>::Error: std::error::Error + Send + Sync,
117+
{
118+
let router = Router::with_state(sessions)
119+
.route("/", post(new_session))
120+
.route(
121+
"/:id",
122+
get(get_session).put(update_session).delete(delete_session),
123+
)
124+
.layer(DefaultBodyLimit::disable())
125+
.layer(RequestBodyLimitLayer::new(max_bytes))
126+
.layer(SetResponseHeaderLayer::if_not_present(
127+
HeaderName::from_static("x-max-bytes"),
128+
HeaderValue::from_str(&max_bytes.to_string())
129+
.expect("Could not construct x-max-bytes header value"),
130+
))
131+
.layer(
132+
CorsLayer::new()
133+
.allow_origin(Any)
134+
.allow_methods(Any)
135+
.allow_headers([CONTENT_TYPE, IF_MATCH, IF_NONE_MATCH])
136+
.expose_headers([ETAG, LOCATION, HeaderName::from_static("x-max-bytes")]),
137+
);
138+
139+
Router::new().nest(prefix, router)
140+
}

0 commit comments

Comments
 (0)