Skip to content

Commit 33f2e2a

Browse files
authored
Add compression options to aws_http_header struct (#192)
1 parent a079831 commit 33f2e2a

File tree

12 files changed

+158
-177
lines changed

12 files changed

+158
-177
lines changed

include/aws/http/private/h2_decoder.h

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,13 @@ struct aws_h2_decoder_vtable {
3636
/* For HEADERS header-block: _begin() is called, then 0+ _i() calls, then _end().
3737
* No other decoder callbacks will occur in this time. */
3838
int (*on_headers_begin)(uint32_t stream_id, void *userdata);
39-
int (*on_headers_i)(
40-
uint32_t stream_id,
41-
const struct aws_http_header *header,
42-
enum aws_h2_header_field_hpack_behavior hpack_behavior,
43-
void *userdata);
39+
int (*on_headers_i)(uint32_t stream_id, const struct aws_http_header *header, void *userdata);
4440
int (*on_headers_end)(uint32_t stream_id, void *userdata);
4541

4642
/* For PUSH_PROMISE header-block: _begin() is called, then 0+ _i() calls, then _end().
4743
* No other decoder callbacks will occur in this time. */
4844
int (*on_push_promise_begin)(uint32_t stream_id, uint32_t promised_stream_id, void *userdata);
49-
int (*on_push_promise_i)(
50-
uint32_t stream_id,
51-
const struct aws_http_header *header,
52-
enum aws_h2_header_field_hpack_behavior hpack_behavior,
53-
void *userdata);
45+
int (*on_push_promise_i)(uint32_t stream_id, const struct aws_http_header *header, void *userdata);
5446

5547
int (*on_push_promise_end)(uint32_t stream_id, void *userdata);
5648

include/aws/http/private/h2_frames.h

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,6 @@ enum aws_h2_settings {
7878
* See RFC-7540 3.5 - HTTP/2 Connection Preface */
7979
extern const struct aws_byte_cursor aws_h2_connection_preface_client_string;
8080

81-
/* RFC-7541 2.4 */
82-
enum aws_h2_header_field_hpack_behavior {
83-
AWS_H2_HEADER_BEHAVIOR_SAVE,
84-
AWS_H2_HEADER_BEHAVIOR_NO_SAVE,
85-
AWS_H2_HEADER_BEHAVIOR_NO_FORWARD_SAVE,
86-
};
87-
8881
/**
8982
* Present in all frames that may have set AWS_H2_FRAME_F_PRIORITY
9083
*
@@ -101,13 +94,8 @@ struct aws_h2_frame_priority_settings {
10194
uint8_t weight;
10295
};
10396

104-
struct aws_h2_frame_header_field {
105-
struct aws_http_header header;
106-
enum aws_h2_header_field_hpack_behavior hpack_behavior;
107-
const size_t index; /* DO NOT TOUCH unless you're pretty sure you know what you're doing */
108-
};
10997
struct aws_h2_frame_header_block {
110-
/* array_list of aws_h2_frame_header_field */
98+
/* array_list of aws_http_header */
11199
struct aws_array_list header_fields;
112100
};
113101

include/aws/http/private/hpack.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,10 @@ struct aws_hpack_decode_result {
3636

3737
union {
3838
/* If type is AWS_HPACK_DECODE_T_HEADER_FIELD */
39-
struct aws_hpack_decoded_header_field {
40-
struct aws_http_header header;
41-
enum aws_h2_header_field_hpack_behavior hpack_behavior;
42-
} header_field;
39+
struct aws_http_header header_field;
4340

4441
/* If type is AWS_HPACK_DECODE_T_DYNAMIC_TABLE_RESIZE */
45-
struct aws_hpack_decoded_resize {
46-
size_t size;
47-
} dynamic_table_resize;
42+
size_t dynamic_table_resize;
4843
} data;
4944
};
5045

include/aws/http/request_response.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,48 @@ struct aws_input_stream;
2929
*/
3030
struct aws_http_stream;
3131

32+
/**
33+
* Controls whether a header's strings may be compressed by encoding the index of
34+
* strings in a cache, rather than encoding the literal string.
35+
*
36+
* This setting has no effect on HTTP/1.x connections.
37+
* On HTTP/2 connections this controls HPACK behavior.
38+
* See RFC-7541 Section 7.1 for security considerations.
39+
*/
40+
enum aws_http_header_compression {
41+
/**
42+
* Compress header by encoding the cached index of its strings,
43+
* or by updating the cache to contain these strings for future reference.
44+
* Best for headers that are sent repeatedly.
45+
* This is the default setting.
46+
*/
47+
AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
48+
49+
/**
50+
* Encode header strings literally.
51+
* If an intermediary re-broadcasts the headers, it is permitted to use cache.
52+
* Best for unique headers that are unlikely to repeat.
53+
*/
54+
AWS_HTTP_HEADER_COMPRESSION_NO_CACHE,
55+
56+
/**
57+
* Encode header strings literally and forbid all intermediaries from using
58+
* cache when re-broadcasting.
59+
* Best for header fields that are highly valuable or sensitive to recovery.
60+
*/
61+
AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE,
62+
};
63+
3264
/**
3365
* A lightweight HTTP header struct.
3466
* Note that the underlying strings are not owned by the byte cursors.
3567
*/
3668
struct aws_http_header {
3769
struct aws_byte_cursor name;
3870
struct aws_byte_cursor value;
71+
72+
/* Controls whether the header's strings may be compressed via caching. */
73+
enum aws_http_header_compression compression;
3974
};
4075

4176
/**

source/h2_decoder.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,19 +1035,19 @@ static int s_state_fn_header_block_entry(struct aws_h2_decoder *decoder, struct
10351035
/* #TODO Cookie headers must be concatenated into single delivery RFC-7540 8.1.2.5 */
10361036

10371037
if (result.type == AWS_HPACK_DECODE_T_HEADER_FIELD) {
1038-
const struct aws_hpack_decoded_header_field *field = &result.data.header_field;
1038+
const struct aws_http_header *header_field = &result.data.header_field;
10391039

10401040
DECODER_LOGF(
10411041
TRACE,
10421042
decoder,
10431043
"Decoded header field: \"" PRInSTR ": " PRInSTR "\"",
1044-
AWS_BYTE_CURSOR_PRI(field->header.name),
1045-
AWS_BYTE_CURSOR_PRI(field->header.value));
1044+
AWS_BYTE_CURSOR_PRI(header_field->name),
1045+
AWS_BYTE_CURSOR_PRI(header_field->value));
10461046

10471047
if (decoder->header_block_in_progress.is_push_promise) {
1048-
DECODER_CALL_VTABLE_STREAM_ARGS(decoder, on_push_promise_i, &field->header, field->hpack_behavior);
1048+
DECODER_CALL_VTABLE_STREAM_ARGS(decoder, on_push_promise_i, header_field);
10491049
} else {
1050-
DECODER_CALL_VTABLE_STREAM_ARGS(decoder, on_headers_i, &field->header, field->hpack_behavior);
1050+
DECODER_CALL_VTABLE_STREAM_ARGS(decoder, on_headers_i, header_field);
10511051
}
10521052
}
10531053

source/h2_frames.c

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ int aws_h2_frame_header_block_init(struct aws_h2_frame_header_block *header_bloc
101101
AWS_PRECONDITION(header_block);
102102
AWS_PRECONDITION(allocator);
103103

104-
return aws_array_list_init_dynamic(
105-
&header_block->header_fields, allocator, 0, sizeof(struct aws_h2_frame_header_field));
104+
return aws_array_list_init_dynamic(&header_block->header_fields, allocator, 0, sizeof(struct aws_http_header));
106105
}
107106
void aws_h2_frame_header_block_clean_up(struct aws_h2_frame_header_block *header_block) {
108107
AWS_PRECONDITION(header_block);
@@ -126,27 +125,27 @@ int aws_h2_frame_header_block_get_encoded_length(
126125
const size_t num_headers = aws_array_list_length(&header_block->header_fields);
127126
for (size_t i = 0; i < num_headers; ++i) {
128127

129-
const struct aws_h2_frame_header_field *field = NULL;
128+
const struct aws_http_header *field = NULL;
130129
aws_array_list_get_at_ptr(&header_block->header_fields, (void **)&field, i);
131130
AWS_ASSERT(field);
132131

133132
bool found_value = false;
134-
const size_t index = aws_hpack_find_index(encoder->hpack, &field->header, &found_value);
133+
const size_t index = aws_hpack_find_index(encoder->hpack, field, &found_value);
135134

136135
uint8_t prefix_size;
137136
/* If a value was found, this is an indexed header */
138137
if (found_value) {
139138
prefix_size = 7;
140139
} else {
141140
/* If not indexed, determine the appropriate flags and prefixes */
142-
switch (field->hpack_behavior) {
143-
case AWS_H2_HEADER_BEHAVIOR_SAVE:
141+
switch (field->compression) {
142+
case AWS_HTTP_HEADER_COMPRESSION_USE_CACHE:
144143
prefix_size = 6;
145144
break;
146-
case AWS_H2_HEADER_BEHAVIOR_NO_SAVE:
145+
case AWS_HTTP_HEADER_COMPRESSION_NO_CACHE:
147146
prefix_size = 4;
148147
break;
149-
case AWS_H2_HEADER_BEHAVIOR_NO_FORWARD_SAVE:
148+
case AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE:
150149
prefix_size = 5;
151150
break;
152151
default:
@@ -161,12 +160,11 @@ int aws_h2_frame_header_block_get_encoded_length(
161160
if (!found_value) {
162161
/* If not an indexed header, check if the name needs to be written */
163162
if (!index) {
164-
*length +=
165-
aws_hpack_get_encoded_length_string(encoder->hpack, field->header.name, encoder->use_huffman);
163+
*length += aws_hpack_get_encoded_length_string(encoder->hpack, field->name, encoder->use_huffman);
166164
}
167165

168166
/* Value must be written if the field isn't pure indexed */
169-
*length += aws_hpack_get_encoded_length_string(encoder->hpack, field->header.value, encoder->use_huffman);
167+
*length += aws_hpack_get_encoded_length_string(encoder->hpack, field->value, encoder->use_huffman);
170168
}
171169
}
172170

@@ -186,12 +184,12 @@ int aws_h2_frame_header_block_encode(
186184

187185
for (size_t i = 0; i < num_headers; ++i) {
188186

189-
const struct aws_h2_frame_header_field *field = NULL;
187+
const struct aws_http_header *field = NULL;
190188
aws_array_list_get_at_ptr(&header_block->header_fields, (void **)&field, i);
191189
AWS_ASSERT(field);
192190

193191
bool found_value = true;
194-
const size_t index = aws_hpack_find_index(encoder->hpack, &field->header, &found_value);
192+
const size_t index = aws_hpack_find_index(encoder->hpack, field, &found_value);
195193

196194
uint8_t mask;
197195
uint8_t prefix_size;
@@ -201,16 +199,16 @@ int aws_h2_frame_header_block_encode(
201199
prefix_size = 7;
202200
} else {
203201
/* If not indexed, determine the appropriate flags and prefixes */
204-
switch (field->hpack_behavior) {
205-
case AWS_H2_HEADER_BEHAVIOR_SAVE:
202+
switch (field->compression) {
203+
case AWS_HTTP_HEADER_COMPRESSION_USE_CACHE:
206204
mask = s_literal_save_field_mask;
207205
prefix_size = 6;
208206
break;
209-
case AWS_H2_HEADER_BEHAVIOR_NO_SAVE:
207+
case AWS_HTTP_HEADER_COMPRESSION_NO_CACHE:
210208
mask = 0; /* No bits set, just 4 bit prefix */
211209
prefix_size = 4;
212210
break;
213-
case AWS_H2_HEADER_BEHAVIOR_NO_FORWARD_SAVE:
211+
case AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE:
214212
mask = s_literal_no_forward_save_mask;
215213
prefix_size = 4;
216214
break;
@@ -236,23 +234,23 @@ int aws_h2_frame_header_block_encode(
236234
if (!found_value) {
237235
/* If not an indexed header, check if the name needs to be written */
238236
if (!index) {
239-
scratch = field->header.name;
237+
scratch = field->name;
240238
if (aws_hpack_encode_string(encoder->hpack, &scratch, encoder->use_huffman, output)) {
241239
return AWS_OP_ERR;
242240
}
243241
AWS_ASSERT(scratch.len == 0);
244242
}
245243

246244
/* Value must be written if the field isn't pure indexed */
247-
scratch = field->header.value;
245+
scratch = field->value;
248246
if (aws_hpack_encode_string(encoder->hpack, &scratch, encoder->use_huffman, output)) {
249247
return AWS_OP_ERR;
250248
}
251249
AWS_ASSERT(scratch.len == 0);
252250

253-
if (field->hpack_behavior == AWS_H2_HEADER_BEHAVIOR_SAVE) {
251+
if (field->compression == AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) {
254252
/* Save for next time */
255-
aws_hpack_insert_header(encoder->hpack, &field->header);
253+
aws_hpack_insert_header(encoder->hpack, field);
256254
}
257255
}
258256

source/h2_stream.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,9 @@ static struct aws_h2_frame_headers *s_new_headers_frame(
128128
/* #TODO headers frame needs to respect max frame size, and use CONTINUATION */
129129
const size_t num_headers = aws_http_message_get_header_count(message);
130130
for (size_t i = 0; i < num_headers; ++i) {
131-
struct aws_h2_frame_header_field header_field = {
132-
/* #TODO: let user specify hpack behavior */
133-
.hpack_behavior = AWS_H2_HEADER_BEHAVIOR_SAVE,
134-
};
131+
struct aws_http_header header_field;
135132

136-
aws_http_message_get_header(message, &header_field.header, i);
133+
aws_http_message_get_header(message, &header_field, i);
137134
if (aws_array_list_push_back(&headers_frame->header_block.header_fields, &header_field)) {
138135
goto error_push_back;
139136
}

source/hpack.c

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ struct aws_hpack_context {
278278

279279
struct hpack_progress_literal {
280280
uint8_t prefix_size;
281-
enum aws_h2_header_field_hpack_behavior hpack_behavior;
281+
enum aws_http_header_compression compression;
282282
uint64_t name_index;
283283
size_t name_length;
284284
} literal;
@@ -990,7 +990,7 @@ int aws_hpack_decode(
990990

991991
} else if (first_byte & (1 << 6)) {
992992
/* 01xxxxxx: Literal Header Field with Incremental Indexing */
993-
context->progress_entry.u.literal.hpack_behavior = AWS_H2_HEADER_BEHAVIOR_SAVE;
993+
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_USE_CACHE;
994994
context->progress_entry.u.literal.prefix_size = 6;
995995
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
996996

@@ -1000,12 +1000,12 @@ int aws_hpack_decode(
10001000

10011001
} else if (first_byte & (1 << 4)) {
10021002
/* 0001xxxx: Literal Header Field Never Indexed */
1003-
context->progress_entry.u.literal.hpack_behavior = AWS_H2_HEADER_BEHAVIOR_NO_FORWARD_SAVE;
1003+
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_FORWARD_CACHE;
10041004
context->progress_entry.u.literal.prefix_size = 4;
10051005
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
10061006
} else {
10071007
/* 0000xxxx: Literal Header Field without Indexing */
1008-
context->progress_entry.u.literal.hpack_behavior = AWS_H2_HEADER_BEHAVIOR_NO_SAVE;
1008+
context->progress_entry.u.literal.compression = AWS_HTTP_HEADER_COMPRESSION_NO_CACHE;
10091009
context->progress_entry.u.literal.prefix_size = 4;
10101010
context->progress_entry.state = HPACK_ENTRY_STATE_LITERAL_BEGIN;
10111011
}
@@ -1031,8 +1031,7 @@ int aws_hpack_decode(
10311031
}
10321032

10331033
result->type = AWS_HPACK_DECODE_T_HEADER_FIELD;
1034-
result->data.header_field.header = *header;
1035-
result->data.header_field.hpack_behavior = AWS_H2_HEADER_BEHAVIOR_SAVE;
1034+
result->data.header_field = *header;
10361035
goto handle_complete;
10371036
} break;
10381037

@@ -1117,17 +1116,17 @@ int aws_hpack_decode(
11171116
struct aws_http_header header;
11181117
header.value = aws_byte_cursor_from_buf(&context->progress_entry.scratch);
11191118
header.name = aws_byte_cursor_advance(&header.value, literal->name_length);
1119+
header.compression = literal->compression;
11201120

11211121
/* Save to table if necessary */
1122-
if (literal->hpack_behavior == AWS_H2_HEADER_BEHAVIOR_SAVE) {
1122+
if (literal->compression == AWS_HTTP_HEADER_COMPRESSION_USE_CACHE) {
11231123
if (aws_hpack_insert_header(context, &header)) {
11241124
return AWS_OP_ERR;
11251125
}
11261126
}
11271127

11281128
result->type = AWS_HPACK_DECODE_T_HEADER_FIELD;
1129-
result->data.header_field.header = header;
1130-
result->data.header_field.hpack_behavior = literal->hpack_behavior;
1129+
result->data.header_field = header;
11311130
goto handle_complete;
11321131
} break;
11331132

@@ -1156,7 +1155,7 @@ int aws_hpack_decode(
11561155
}
11571156

11581157
result->type = AWS_HPACK_DECODE_T_DYNAMIC_TABLE_RESIZE;
1159-
result->data.dynamic_table_resize.size = size;
1158+
result->data.dynamic_table_resize = size;
11601159
goto handle_complete;
11611160
} break;
11621161

source/request_response.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ int aws_http_headers_add(struct aws_http_headers *headers, struct aws_byte_curso
110110
return AWS_OP_ERR;
111111
}
112112

113-
struct aws_http_header header = {name, value};
113+
struct aws_http_header header = {.name = name, .value = value};
114114

115115
/* Store our own copy of the strings.
116116
* We put the name and value into the same allocation. */

0 commit comments

Comments
 (0)