add proper request router for s3 api #163
3 changed files with 82 additions and 129 deletions
|
@ -228,10 +228,11 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
list_type,
|
list_type,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
if list_type == "2" {
|
||||||
handle_list(
|
handle_list(
|
||||||
garage,
|
garage,
|
||||||
&ListObjectsQuery {
|
&ListObjectsQuery {
|
||||||
is_v2: list_type == "v2",
|
is_v2: true,
|
||||||
bucket,
|
bucket,
|
||||||
delimiter: delimiter.map(|d| d.to_string()),
|
delimiter: delimiter.map(|d| d.to_string()),
|
||||||
max_keys: max_keys.unwrap_or(1000),
|
max_keys: max_keys.unwrap_or(1000),
|
||||||
|
@ -243,11 +244,17 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(format!(
|
||||||
|
"Invalid endpoint: list-type={}",
|
||||||
|
list_type
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Endpoint::DeleteObjects { bucket } => {
|
Endpoint::DeleteObjects { bucket } => {
|
||||||
handle_delete_objects(garage, &bucket, req, content_sha256).await
|
handle_delete_objects(garage, &bucket, req, content_sha256).await
|
||||||
}
|
}
|
||||||
_ => Err(Error::BadRequest("Unsupported call".to_string())),
|
endpoint => Err(Error::NotImplemented(endpoint.name().to_owned())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,10 @@ pub enum Error {
|
||||||
/// The client sent an invalid request
|
/// The client sent an invalid request
|
||||||
#[error(display = "Bad request: {}", _0)]
|
#[error(display = "Bad request: {}", _0)]
|
||||||
BadRequest(String),
|
BadRequest(String),
|
||||||
|
|
||||||
|
/// The client sent a request for an action not supported by garage
|
||||||
|
#[error(display = "Unimplemented action: {}", _0)]
|
||||||
|
NotImplemented(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<roxmltree::Error> for Error {
|
impl From<roxmltree::Error> for Error {
|
||||||
|
@ -94,6 +98,7 @@ impl Error {
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
}
|
}
|
||||||
Error::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE,
|
Error::InvalidRange(_) => StatusCode::RANGE_NOT_SATISFIABLE,
|
||||||
|
Error::NotImplemented(_) => StatusCode::NOT_IMPLEMENTED,
|
||||||
_ => StatusCode::BAD_REQUEST,
|
_ => StatusCode::BAD_REQUEST,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,53 @@ macro_rules! s3_match {
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
.map_err(|_| Error::BadRequest("Failed to parse query parameter".to_owned()))?
|
||||||
}};
|
}};
|
||||||
|
(@func
|
||||||
|
$(#[$doc:meta])*
|
||||||
|
pub enum Endpoint {
|
||||||
|
$(
|
||||||
|
$(#[$outer:meta])*
|
||||||
|
$variant:ident $({
|
||||||
|
bucket: String,
|
||||||
|
$($name:ident: $ty:ty,)*
|
||||||
|
})?,
|
||||||
|
)*
|
||||||
|
}) => {
|
||||||
|
$(#[$doc])*
|
||||||
|
pub enum Endpoint {
|
||||||
|
$(
|
||||||
|
$(#[$outer])*
|
||||||
|
$variant $({
|
||||||
|
bucket: String,
|
||||||
|
$($name: $ty, )*
|
||||||
|
})?,
|
||||||
|
)*
|
||||||
}
|
}
|
||||||
|
impl Endpoint {
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(Endpoint::$variant $({ $($name: _,)* .. })? => stringify!($variant),)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the bucket the request target. Returns None for requests not related to a bucket.
|
||||||
|
pub fn get_bucket(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Endpoint::$variant $({ bucket, $($name: _,)* .. })? => s3_match!{@if ($(bucket $($name)*)?) then (Some(bucket)) else (None)},
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(@if ($($cond:tt)+) then ($($then:tt)*) else ($($else:tt)*)) => {
|
||||||
|
$($then)*
|
||||||
|
};
|
||||||
|
(@if () then ($($then:tt)*) else ($($else:tt)*)) => {
|
||||||
|
$($else)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_match! {@func
|
||||||
|
|
||||||
/// List of all S3 API endpoints.
|
/// List of all S3 API endpoints.
|
||||||
///
|
///
|
||||||
|
@ -318,7 +364,7 @@ pub enum Endpoint {
|
||||||
},
|
},
|
||||||
ListObjectsV2 {
|
ListObjectsV2 {
|
||||||
bucket: String,
|
bucket: String,
|
||||||
/// This value should always be 2. It is not checked when constructing the struct
|
// This value should always be 2. It is not checked when constructing the struct
|
||||||
list_type: String,
|
list_type: String,
|
||||||
continuation_token: Option<String>,
|
continuation_token: Option<String>,
|
||||||
delimiter: Option<char>,
|
delimiter: Option<char>,
|
||||||
|
@ -440,7 +486,7 @@ pub enum Endpoint {
|
||||||
SelectObjectContent {
|
SelectObjectContent {
|
||||||
bucket: String,
|
bucket: String,
|
||||||
key: String,
|
key: String,
|
||||||
/// This value should always be 2. It is not checked when constructing the struct
|
// This value should always be 2. It is not checked when constructing the struct
|
||||||
select_type: String,
|
select_type: String,
|
||||||
},
|
},
|
||||||
UploadPart {
|
UploadPart {
|
||||||
|
@ -455,7 +501,7 @@ pub enum Endpoint {
|
||||||
part_number: u64,
|
part_number: u64,
|
||||||
upload_id: String,
|
upload_id: String,
|
||||||
},
|
},
|
||||||
}
|
}}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
/// Determine which S3 endpoint a request is for using the request, and a bucket which was
|
||||||
|
@ -496,11 +542,9 @@ impl Endpoint {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(message) = query.nonempty_message() {
|
if let Some(message) = query.nonempty_message() {
|
||||||
// maybe this should just be a warn! ?
|
debug!("Unused query parameter: {}", message)
|
||||||
Err(Error::BadRequest(message.to_owned()))
|
|
||||||
} else {
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine which endpoint a request is for, knowing it is a GET.
|
/// Determine which endpoint a request is for, knowing it is a GET.
|
||||||
|
@ -683,107 +727,6 @@ impl Endpoint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the bucket the request target. Returns None for requests not related to a bucket.
|
|
||||||
pub fn get_bucket(&self) -> Option<&str> {
|
|
||||||
s3_match! {
|
|
||||||
@extract
|
|
||||||
self,
|
|
||||||
bucket,
|
|
||||||
[
|
|
||||||
AbortMultipartUpload,
|
|
||||||
CompleteMultipartUpload,
|
|
||||||
CopyObject,
|
|
||||||
CreateBucket,
|
|
||||||
CreateMultipartUpload,
|
|
||||||
DeleteBucket,
|
|
||||||
DeleteBucketAnalyticsConfiguration,
|
|
||||||
DeleteBucketCors,
|
|
||||||
DeleteBucketEncryption,
|
|
||||||
DeleteBucketIntelligentTieringConfiguration,
|
|
||||||
DeleteBucketInventoryConfiguration,
|
|
||||||
DeleteBucketLifecycle,
|
|
||||||
DeleteBucketMetricsConfiguration,
|
|
||||||
DeleteBucketOwnershipControls,
|
|
||||||
DeleteBucketPolicy,
|
|
||||||
DeleteBucketReplication,
|
|
||||||
DeleteBucketTagging,
|
|
||||||
DeleteBucketWebsite,
|
|
||||||
DeleteObject,
|
|
||||||
DeleteObjects,
|
|
||||||
DeleteObjectTagging,
|
|
||||||
DeletePublicAccessBlock,
|
|
||||||
GetBucketAccelerateConfiguration,
|
|
||||||
GetBucketAcl,
|
|
||||||
GetBucketAnalyticsConfiguration,
|
|
||||||
GetBucketCors,
|
|
||||||
GetBucketEncryption,
|
|
||||||
GetBucketIntelligentTieringConfiguration,
|
|
||||||
GetBucketInventoryConfiguration,
|
|
||||||
GetBucketLifecycleConfiguration,
|
|
||||||
GetBucketLocation,
|
|
||||||
GetBucketLogging,
|
|
||||||
GetBucketMetricsConfiguration,
|
|
||||||
GetBucketNotificationConfiguration,
|
|
||||||
GetBucketOwnershipControls,
|
|
||||||
GetBucketPolicy,
|
|
||||||
GetBucketPolicyStatus,
|
|
||||||
GetBucketReplication,
|
|
||||||
GetBucketRequestPayment,
|
|
||||||
GetBucketTagging,
|
|
||||||
GetBucketVersioning,
|
|
||||||
GetBucketWebsite,
|
|
||||||
GetObject,
|
|
||||||
GetObjectAcl,
|
|
||||||
GetObjectLegalHold,
|
|
||||||
GetObjectLockConfiguration,
|
|
||||||
GetObjectRetention,
|
|
||||||
GetObjectTagging,
|
|
||||||
GetObjectTorrent,
|
|
||||||
GetPublicAccessBlock,
|
|
||||||
HeadBucket,
|
|
||||||
HeadObject,
|
|
||||||
ListBucketAnalyticsConfigurations,
|
|
||||||
ListBucketIntelligentTieringConfigurations,
|
|
||||||
ListBucketInventoryConfigurations,
|
|
||||||
ListBucketMetricsConfigurations,
|
|
||||||
ListMultipartUploads,
|
|
||||||
ListObjects,
|
|
||||||
ListObjectsV2,
|
|
||||||
ListObjectVersions,
|
|
||||||
ListParts,
|
|
||||||
PutBucketAccelerateConfiguration,
|
|
||||||
PutBucketAcl,
|
|
||||||
PutBucketAnalyticsConfiguration,
|
|
||||||
PutBucketCors,
|
|
||||||
PutBucketEncryption,
|
|
||||||
PutBucketIntelligentTieringConfiguration,
|
|
||||||
PutBucketInventoryConfiguration,
|
|
||||||
PutBucketLifecycleConfiguration,
|
|
||||||
PutBucketLogging,
|
|
||||||
PutBucketMetricsConfiguration,
|
|
||||||
PutBucketNotificationConfiguration,
|
|
||||||
PutBucketOwnershipControls,
|
|
||||||
PutBucketPolicy,
|
|
||||||
PutBucketReplication,
|
|
||||||
PutBucketRequestPayment,
|
|
||||||
PutBucketTagging,
|
|
||||||
PutBucketVersioning,
|
|
||||||
PutBucketWebsite,
|
|
||||||
PutObject,
|
|
||||||
PutObjectAcl,
|
|
||||||
PutObjectLegalHold,
|
|
||||||
PutObjectLockConfiguration,
|
|
||||||
PutObjectRetention,
|
|
||||||
PutObjectTagging,
|
|
||||||
PutPublicAccessBlock,
|
|
||||||
RestoreObject,
|
|
||||||
SelectObjectContent,
|
|
||||||
UploadPart,
|
|
||||||
UploadPartCopy,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the key the request target. Returns None for requests which don't use a key.
|
/// Get the key the request target. Returns None for requests which don't use a key.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_key(&self) -> Option<&str> {
|
pub fn get_key(&self) -> Option<&str> {
|
||||||
|
@ -929,10 +872,8 @@ macro_rules! generateQueryParameters {
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::BadRequest(format!(
|
debug!("Received an unknown query parameter: '{}'", k);
|
||||||
"Unknown query parameter '{}'",
|
false
|
||||||
k
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -953,7 +894,7 @@ macro_rules! generateQueryParameters {
|
||||||
Some("Keyword not used")
|
Some("Keyword not used")
|
||||||
} $(
|
} $(
|
||||||
else if self.$name.is_some() {
|
else if self.$name.is_some() {
|
||||||
Some(concat!("Query parameter not needed: '", $rest, "'" ))
|
Some(concat!("'", $rest, "'"))
|
||||||
}
|
}
|
||||||
)* else {
|
)* else {
|
||||||
None
|
None
|
||||||
|
|
Loading…
Reference in a new issue