Skip to content

Commit 51a3b4b

Browse files
committed
net: http: server: Enable HTTP1.0 request compatibility
Per RFC9112 (REF #1), HTTP/1.1 server is expected to be backward compatible with HTTP1.0 request. Mainly HTTP/1.0 does not support 1) Transfer Encoding : chunked 2) KeepAlives So, this change will identify the HTTP protocol version in the request and respond accordingly. REF# 1) https://httpwg.org/specs/rfc9112.html Tested: 1) Verified that HTTP/1.1 requests are served as usual i.e. with chunked transfer encoding and the connection is a Keep Alive connection Query exits after 10s, indicating that the client connection to the HTTP server is a Keep Alive. ``` time printf "GET /telem HTTP/1.1\r\nHost: 192.0.3.11\r\n\r\n" | nc 192.0.3.11 80 real 0m10.185s <- Indicates connection was kept active for 10s user 0m0.023s sys 0m0.023s ``` `Transfer Encoding : chunked` header and chunked encoding metadata (chunk size hex bytes, crlf, termination byte 0) are present in the response. ``` HTTP/1.1 200 OK Transfer-Encoding: chunked <- Chunked encoding header Content-Type: text/html 3e8 <- Chunk size hex bytes : /health/secondsdevice_ae:9a:22:48:0f:70"seconds0 + /health/locatedevice_ae:9a:22:48:0f:70"0 / /network/mac-addressdevice_ae:9a:22:48:0f:70" . . . 3e8 <- Chunk size hex byte _PLUS_rawdevice_ae:9a:22:48:0f:70"0 6 /dev/ISHARE_CBU_MINUS_rawdevice_ae:9a:22:48:0f:70"0 . . . /dev/part_iddevice_ae:9a:22:48:0f:70"0��������� , /dev/part_typedevice_ae:9a:22:48:0f:70"0 7 /dev/part_telemetry_disabledevice_ae:9a:22:48:0f:70"0 0 <- Termination Byte 0 ``` 2) Verfied that HTTP/1.0 requests are served with response without chunked transfer encoding and the connection is closed immediately. Query exits immediately indicating the connection is closed immediately ``` time printf "GET /telem HTTP/1.0\r\nHost: 192.0.3.11\r\n\r\n" | nc 192.0.3.11 80 real 0m0.186s <- Indicates connection was terminated immediately. user 0m0.018s sys 0m0.030s ``` `Transfer Encoding : chunked` header and chunked encoding metadata (chunk size hex bytes, crlf) not present in the response ``` HTTP/1.1 200 OK Content-Type: text/html : /health/uptimedevice_ae:9a:22:48:0f:70"seconds0 + /health/locatedevice_ae:9a:22:48:0f:70"0 / /network/mac-addressdevice_ae:9a:22:48:0f:70" . . . 3 /dev/part_iddevice_ae:9a:22:48:0f:70"0��������� , /dev/part_typedevice_ae:9a:22:48:0f:70"0 7 /dev/can_telemetry_disabledevice_ae:9a:22:48:0f:70"0 ``` Signed-off-by: Nikhil Namjoshi <nikhilnamjoshi@google.com>
1 parent 0601335 commit 51a3b4b

File tree

1 file changed

+53
-18
lines changed

1 file changed

+53
-18
lines changed

subsys/net/lib/http/http_server_http1.c

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ static const char conflict_response[] = "HTTP/1.1 409 Conflict\r\n\r\n";
3838
static const char final_chunk[] = "0\r\n\r\n";
3939
static const char *crlf = &final_chunk[3];
4040

41+
static bool is_client_http10(struct http_client_ctx *client)
42+
{
43+
if ((client->parser.http_major == 1) && (client->parser.http_minor == 0)) {
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
4150
static int send_http1_error_common(struct http_client_ctx *client,
4251
const char *response, size_t len)
4352
{
@@ -198,6 +207,7 @@ static int handle_http1_static_resource(
198207
#define RESPONSE_TEMPLATE_DYNAMIC_PART1 \
199208
"HTTP/1.1 %d%s%s\r\n" \
200209
"Transfer-Encoding: chunked\r\n"
210+
#define RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE "HTTP/1.1 %d%s%s\r\n"
201211

202212
static const char *http_status_str(enum http_status status)
203213
{
@@ -240,9 +250,12 @@ static int http1_send_headers(struct http_client_ctx *client, enum http_status s
240250
{
241251
int ret;
242252
bool content_type_sent = false;
243-
char http_response[MAX(sizeof(RESPONSE_TEMPLATE_DYNAMIC_PART1) + sizeof("xxx") +
244-
REASON_PHRASE_MAX_LENGTH,
245-
CONFIG_HTTP_SERVER_MAX_HEADER_LEN + 2)];
253+
int max_header_size = is_client_http10(client)
254+
? sizeof(RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE) +
255+
REASON_PHRASE_MAX_LENGTH
256+
: sizeof(RESPONSE_TEMPLATE_DYNAMIC_PART1) + sizeof("xxx") +
257+
REASON_PHRASE_MAX_LENGTH;
258+
char http_response[MAX(max_header_size, CONFIG_HTTP_SERVER_MAX_HEADER_LEN + 2)];
246259

247260
if (status < HTTP_100_CONTINUE || status > HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) {
248261
LOG_DBG("Invalid HTTP status code: %d", status);
@@ -254,8 +267,12 @@ static int http1_send_headers(struct http_client_ctx *client, enum http_status s
254267
return -EINVAL;
255268
}
256269

270+
const char *header_template = is_client_http10(client)
271+
? RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE
272+
: RESPONSE_TEMPLATE_DYNAMIC_PART1;
273+
257274
/* Send response code and transfer encoding */
258-
snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE_DYNAMIC_PART1, status,
275+
snprintk(http_response, sizeof(http_response), header_template, status,
259276
IS_ENABLED(CONFIG_HTTP_SERVER_COMPLETE_STATUS_PHRASES) ? " " : "",
260277
http_status_str(status));
261278

@@ -358,18 +375,26 @@ static int http1_dynamic_response(struct http_client_ctx *client, struct http_re
358375

359376
/* Send body data if provided */
360377
if (rsp->body != NULL && rsp->body_len > 0) {
361-
ret = snprintk(tmp, sizeof(tmp), "%zx\r\n", rsp->body_len);
362-
ret = http_server_sendall(client, tmp, ret);
363-
if (ret < 0) {
364-
return ret;
378+
379+
/* Check if the client expects HTTP/1.0 compatible response */
380+
if (!is_client_http10(client)) {
381+
/* Use Transfer-Encoding: chunked */
382+
ret = snprintk(tmp, sizeof(tmp), "%zx\r\n", rsp->body_len);
383+
ret = http_server_sendall(client, tmp, ret);
384+
if (ret < 0) {
385+
return ret;
386+
}
365387
}
366388

367389
ret = http_server_sendall(client, rsp->body, rsp->body_len);
368390
if (ret < 0) {
369391
return ret;
370392
}
371393

372-
(void)http_server_sendall(client, crlf, 2);
394+
if (!is_client_http10(client)) {
395+
/* Use Transfer-Encoding: chunked */
396+
(void)http_server_sendall(client, crlf, 2);
397+
}
373398
}
374399

375400
return 0;
@@ -410,10 +435,12 @@ static int dynamic_get_del_req(struct http_resource_detail_dynamic *dynamic_deta
410435

411436
dynamic_detail->holder = NULL;
412437

413-
ret = http_server_sendall(client, final_chunk,
414-
sizeof(final_chunk) - 1);
415-
if (ret < 0) {
416-
return ret;
438+
/* Only send the 0\r\n\r\n if the client is NOT HTTP/1.0 */
439+
if (!is_client_http10(client)) {
440+
ret = http_server_sendall(client, final_chunk, sizeof(final_chunk) - 1);
441+
if (ret < 0) {
442+
return ret;
443+
}
417444
}
418445

419446
return 0;
@@ -490,10 +517,12 @@ static int dynamic_post_put_req(struct http_resource_detail_dynamic *dynamic_det
490517
}
491518
}
492519

493-
ret = http_server_sendall(client, final_chunk,
494-
sizeof(final_chunk) - 1);
495-
if (ret < 0) {
496-
return ret;
520+
/* HTTP/1.0 client does not expect CRLF in the response */
521+
if (!is_client_http10(client)) {
522+
ret = http_server_sendall(client, final_chunk, sizeof(final_chunk) - 1);
523+
if (ret < 0) {
524+
return ret;
525+
}
497526
}
498527

499528
dynamic_detail->holder = NULL;
@@ -1111,7 +1140,13 @@ not_found: ; /* Add extra semicolon to make clang to compile when using label */
11111140
client->data_len -= parsed;
11121141

11131142
if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) {
1114-
if ((client->parser.flags & F_CONNECTION_CLOSE) == 0) {
1143+
/* FORCE CLOSE for HTTP/1.0 clients so they know the data is done */
1144+
if (is_client_http10(client)) {
1145+
LOG_DBG("HTTP/1.0 request complete, closing connection");
1146+
enter_http_done_state(client);
1147+
}
1148+
/* Standard HTTP/1.1 Keep-Alive logic */
1149+
else if ((client->parser.flags & F_CONNECTION_CLOSE) == 0) {
11151150
LOG_DBG("Waiting for another request, client %p", client);
11161151
client->server_state = HTTP_SERVER_PREFACE_STATE;
11171152
} else {

0 commit comments

Comments
 (0)