Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions rust/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres-data:/var/lib/postgresql/data
- ./impls/src/postgres/sql/v0_create_vss_db.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- app-network

volumes:
postgres-data:

networks:
app-network:
driver: bridge
7 changes: 5 additions & 2 deletions rust/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use tokio::signal::unix::SignalKind;
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;

use crate::vss_service::VssService;
use crate::vss_service::{VssService, VssServiceConfig};
use api::auth::{Authorizer, NoopAuthorizer};
use api::kv_store::KvStore;
use impls::postgres_store::{Certificate, PostgresPlaintextBackend, PostgresTlsBackend};
Expand Down Expand Up @@ -118,7 +118,10 @@ fn main() {
match res {
Ok((stream, _)) => {
let io_stream = TokioIo::new(stream);
let vss_service = VssService::new(Arc::clone(&store), Arc::clone(&authorizer));
let vss_service_config = if let Some(req_body_size) = config.server_config.maximum_request_body_size {
VssServiceConfig::new(req_body_size)
} else {VssServiceConfig::default()};
let vss_service = VssService::new(Arc::clone(&store), Arc::clone(&authorizer), vss_service_config);
runtime.spawn(async move {
if let Err(err) = http1::Builder::new().serve_connection(io_stream, vss_service).await {
eprintln!("Failed to serve connection: {}", err);
Expand Down
1 change: 1 addition & 0 deletions rust/server/src/util/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub(crate) struct Config {
pub(crate) struct ServerConfig {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) maximum_request_body_size: Option<usize>,
}

#[derive(Deserialize)]
Expand Down
88 changes: 78 additions & 10 deletions rust/server/src/vss_service.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use http_body_util::{BodyExt, Full};
use http_body_util::{BodyExt, Full, Limited};
use hyper::body::{Bytes, Incoming};
use hyper::service::Service;
use hyper::{Request, Response, StatusCode};
Expand All @@ -18,15 +18,45 @@ use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

const MAXIMUM_REQUEST_BODY_SIZE: usize = 20 * 1024 * 1024;
const DEFAULT_REQUEST_BODY_SIZE: usize = 10 * 1024 * 1024;

#[derive(Clone)]
pub(crate) struct VssServiceConfig {
maximum_request_body_size: usize,
}

impl VssServiceConfig {
pub fn new(maximum_request_body_size: usize) -> Self {
let capped = maximum_request_body_size.min(MAXIMUM_REQUEST_BODY_SIZE);
if capped < maximum_request_body_size {
eprintln!(
"Warning: maximum_request_body_size {} exceeds limit, capped to {}.",
maximum_request_body_size, MAXIMUM_REQUEST_BODY_SIZE
);
}
Self { maximum_request_body_size: capped }
}
}

impl Default for VssServiceConfig {
fn default() -> Self {
Self { maximum_request_body_size: DEFAULT_REQUEST_BODY_SIZE }
}
}

#[derive(Clone)]
pub struct VssService {
store: Arc<dyn KvStore>,
authorizer: Arc<dyn Authorizer>,
config: VssServiceConfig,
}

impl VssService {
pub(crate) fn new(store: Arc<dyn KvStore>, authorizer: Arc<dyn Authorizer>) -> Self {
Self { store, authorizer }
pub(crate) fn new(
store: Arc<dyn KvStore>, authorizer: Arc<dyn Authorizer>, config: VssServiceConfig,
) -> Self {
Self { store, authorizer, config }
}
}

Expand All @@ -41,22 +71,51 @@ impl Service<Request<Incoming>> for VssService {
let store = Arc::clone(&self.store);
let authorizer = Arc::clone(&self.authorizer);
let path = req.uri().path().to_owned();
let maximum_request_body_size = self.config.maximum_request_body_size;

Box::pin(async move {
let prefix_stripped_path = path.strip_prefix(BASE_PATH_PREFIX).unwrap_or_default();

match prefix_stripped_path {
"/getObject" => {
handle_request(store, authorizer, req, handle_get_object_request).await
handle_request(
store,
authorizer,
req,
maximum_request_body_size,
handle_get_object_request,
)
.await
},
"/putObjects" => {
handle_request(store, authorizer, req, handle_put_object_request).await
handle_request(
store,
authorizer,
req,
maximum_request_body_size,
handle_put_object_request,
)
.await
},
"/deleteObject" => {
handle_request(store, authorizer, req, handle_delete_object_request).await
handle_request(
store,
authorizer,
req,
maximum_request_body_size,
handle_delete_object_request,
)
.await
},
"/listKeyVersions" => {
handle_request(store, authorizer, req, handle_list_object_request).await
handle_request(
store,
authorizer,
req,
maximum_request_body_size,
handle_list_object_request,
)
.await
},
_ => {
let error_msg = "Invalid request path.".as_bytes();
Expand Down Expand Up @@ -97,7 +156,7 @@ async fn handle_request<
Fut: Future<Output = Result<R, VssError>> + Send,
>(
store: Arc<dyn KvStore>, authorizer: Arc<dyn Authorizer>, request: Request<Incoming>,
handler: F,
maximum_request_body_size: usize, handler: F,
) -> Result<<VssService as Service<Request<Incoming>>>::Response, hyper::Error> {
let (parts, body) = request.into_parts();
let headers_map = parts
Expand All @@ -110,8 +169,17 @@ async fn handle_request<
Ok(auth_response) => auth_response.user_token,
Err(e) => return Ok(build_error_response(e)),
};
// TODO: we should bound the amount of data we read to avoid allocating too much memory.
let bytes = body.collect().await?.to_bytes();

let limited_body = Limited::new(body, maximum_request_body_size);
let bytes = match limited_body.collect().await {
Ok(body) => body.to_bytes(),
Err(_) => {
return Ok(Response::builder()
.status(StatusCode::PAYLOAD_TOO_LARGE)
.body(Full::new(Bytes::from("Request body too large")))
.unwrap());
},
};
match T::decode(bytes) {
Ok(request) => match handler(store.clone(), user_token, request).await {
Ok(response) => Ok(Response::builder()
Expand Down
1 change: 1 addition & 0 deletions rust/server/vss-server-config.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[server_config]
host = "127.0.0.1"
port = 8080
# maximum_request_body_size = 10485760 # Optional: maximum request body size in bytes capped at 20971520 (20 MB).

[postgresql_config]
username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_USERNAME`
Expand Down