Skip to content

Commit

Permalink
add if-match to auto-range get (#226)
Browse files Browse the repository at this point in the history
*Issue #, if available:*

- If the object was updated when we get some range. The other range of the object could be from another version and we don't error out.

*Description of changes:*

- Add if-match header to make sure error out when the corner case happens.
  • Loading branch information
TingDaoK authored Nov 2, 2022
1 parent a41255e commit a3a8a2b
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 4 deletions.
3 changes: 3 additions & 0 deletions include/aws/s3/private/s3_auto_ranged_get.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ struct aws_s3_auto_ranged_get {
} synced_data;

uint32_t initial_message_has_range_header : 1;
uint32_t initial_message_has_if_match_header : 1;

struct aws_string *etag;
};

/* Creates a new auto-ranged get meta request. This will do multiple parallel ranged-gets when appropriate. */
Expand Down
2 changes: 2 additions & 0 deletions include/aws/s3/private/s3_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ extern const struct aws_byte_cursor g_s3_service_name;
AWS_S3_API
extern const struct aws_byte_cursor g_range_header_name;

extern const struct aws_byte_cursor g_if_match_header_name;

AWS_S3_API
extern const struct aws_byte_cursor g_content_range_header_name;

Expand Down
1 change: 1 addition & 0 deletions include/aws/s3/s3.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum aws_s3_errors {
AWS_ERROR_S3_LIST_PARTS_PARSE_FAILED,
AWS_ERROR_S3_RESUMED_PART_CHECKSUM_MISMATCH,
AWS_ERROR_S3_RESUME_FAILED,
AWS_ERROR_S3_OBJECT_MODIFIED,
AWS_ERROR_S3_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_S3_PACKAGE_ID)
};

Expand Down
1 change: 1 addition & 0 deletions source/s3.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ static struct aws_error_info s_errors[] = {
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_LIST_PARTS_PARSE_FAILED, "Failed to parse result from list parts"),
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RESUMED_PART_CHECKSUM_MISMATCH, "Checksum does not match previously uploaded part"),
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_RESUME_FAILED, "Resuming request failed"),
AWS_DEFINE_ERROR_INFO_S3(AWS_ERROR_S3_OBJECT_MODIFIED, "The object modifed during download.")
};
/* clang-format on */

Expand Down
40 changes: 40 additions & 0 deletions source/s3_auto_ranged_get.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ struct aws_s3_meta_request *aws_s3_meta_request_auto_ranged_get_new(
AWS_ASSERT(headers != NULL);

auto_ranged_get->initial_message_has_range_header = aws_http_headers_has(headers, g_range_header_name);
auto_ranged_get->initial_message_has_if_match_header = aws_http_headers_has(headers, g_if_match_header_name);

AWS_LOGF_DEBUG(
AWS_LS_S3_META_REQUEST, "id=%p Created new Auto-Ranged Get Meta Request.", (void *)&auto_ranged_get->base);
Expand All @@ -115,6 +116,7 @@ static void s_s3_meta_request_auto_ranged_get_destroy(struct aws_s3_meta_request
AWS_PRECONDITION(meta_request->impl);

struct aws_s3_auto_ranged_get *auto_ranged_get = meta_request->impl;
aws_string_destroy(auto_ranged_get->etag);
aws_mem_release(meta_request->allocator, auto_ranged_get);
}

Expand Down Expand Up @@ -350,6 +352,7 @@ static int s_s3_auto_ranged_get_prepare_request(

/* Generate a new ranged get request based on the original message. */
struct aws_http_message *message = NULL;
struct aws_s3_auto_ranged_get *auto_ranged_get = meta_request->impl;

switch (request->request_tag) {
case AWS_S3_AUTO_RANGE_GET_REQUEST_TYPE_HEAD_OBJECT:
Expand Down Expand Up @@ -382,6 +385,19 @@ static int s_s3_auto_ranged_get_prepare_request(
if (meta_request->checksum_config.validate_response_checksum) {
aws_http_headers_set(aws_http_message_get_headers(message), g_request_validation_mode, g_enabled);
}
if (!auto_ranged_get->initial_message_has_if_match_header && auto_ranged_get->etag) {
/* Add the if_match to the request */
AWS_LOGF_DEBUG(
AWS_LS_S3_META_REQUEST,
"id=%p: Added the If-Match header to request %p for part %d",
(void *)meta_request,
(void *)request,
request->part_number);
aws_http_headers_set(
aws_http_message_get_headers(message),
g_if_match_header_name,
aws_byte_cursor_from_string(auto_ranged_get->etag));
}

aws_s3_request_setup_send_data(request, message);
aws_http_message_release(message);
Expand Down Expand Up @@ -574,6 +590,24 @@ static void s_s3_auto_ranged_get_request_finished(
goto update_synced_data;
}

if (!request_failed && !auto_ranged_get->initial_message_has_if_match_header) {
AWS_ASSERT(auto_ranged_get->etag == NULL);
struct aws_byte_cursor etag_header_value;

if (aws_http_headers_get(request->send_data.response_headers, g_etag_header_name, &etag_header_value)) {
aws_raise_error(AWS_ERROR_S3_MISSING_ETAG);
error_code = AWS_ERROR_S3_MISSING_ETAG;
goto update_synced_data;
}

AWS_LOGF_TRACE(
AWS_LS_S3_META_REQUEST,
"id=%p Etag received for the meta request. value is: " PRInSTR "",
(void *)meta_request,
AWS_BYTE_CURSOR_PRI(etag_header_value));
auto_ranged_get->etag = aws_string_new_from_cursor(auto_ranged_get->base.allocator, &etag_header_value);
}

/* If we were able to discover the object-range/content length successfully, then any error code that was passed
* into this function is being handled and does not indicate an overall failure.*/
error_code = AWS_ERROR_SUCCESS;
Expand Down Expand Up @@ -671,6 +705,12 @@ static void s_s3_auto_ranged_get_request_finished(
}

if (error_code != AWS_ERROR_SUCCESS) {
if (error_code == AWS_ERROR_S3_INVALID_RESPONSE_STATUS &&
request->send_data.response_status == AWS_HTTP_STATUS_CODE_412_PRECONDITION_FAILED &&
!auto_ranged_get->initial_message_has_if_match_header) {
/* Use more clear error code as we added the if-match header under the hood. */
error_code = AWS_ERROR_S3_OBJECT_MODIFIED;
}
aws_s3_meta_request_set_fail_synced(meta_request, request, error_code);
if (error_code == AWS_ERROR_S3_RESPONSE_CHECKSUM_MISMATCH) {
/* It's a mismatch of checksum, tell user that we validated the checksum and the algorithm we validated
Expand Down
8 changes: 4 additions & 4 deletions source/s3_request_messages.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ static const struct aws_byte_cursor s_x_amz_meta_prefix = AWS_BYTE_CUR_INIT_FROM
const size_t g_s3_abort_multipart_upload_excluded_headers_count =
AWS_ARRAY_SIZE(g_s3_abort_multipart_upload_excluded_headers);

static int s_s3_message_util_add_content_range_header(
static int s_s3_message_util_add_range_header(
uint64_t part_range_start,
uint64_t part_range_end,
struct aws_http_message *out_message);
Expand All @@ -242,7 +242,7 @@ struct aws_http_message *aws_s3_ranged_get_object_message_new(
return NULL;
}

if (s_s3_message_util_add_content_range_header(range_start, range_end, message)) {
if (s_s3_message_util_add_range_header(range_start, range_end, message)) {
goto error_clean_up;
}

Expand Down Expand Up @@ -995,8 +995,8 @@ int aws_s3_message_util_copy_headers(
return AWS_OP_SUCCESS;
}

/* Add a content-range header.*/
static int s_s3_message_util_add_content_range_header(
/* Add a range header.*/
static int s_s3_message_util_add_range_header(
uint64_t part_range_start,
uint64_t part_range_end,
struct aws_http_message *out_message) {
Expand Down
1 change: 1 addition & 0 deletions source/s3_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const struct aws_byte_cursor g_s3_client_version = AWS_BYTE_CUR_INIT_FROM_STRING
const struct aws_byte_cursor g_s3_service_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("s3");
const struct aws_byte_cursor g_host_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Host");
const struct aws_byte_cursor g_range_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Range");
const struct aws_byte_cursor g_if_match_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("If-Match");
const struct aws_byte_cursor g_etag_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("ETag");
const struct aws_byte_cursor g_content_range_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Range");
const struct aws_byte_cursor g_content_type_header_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Content-Type");
Expand Down

0 comments on commit a3a8a2b

Please sign in to comment.