Skip to content

Commit 9151a32

Browse files
namjoshiniksfabiobaltieri
authored andcommitted
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 26b56d7 commit 9151a32

1 file changed

Lines changed: 54 additions & 18 deletions

File tree

subsys/net/lib/http/http_server_http1.c

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ 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(const struct http_client_ctx *client)
42+
{
43+
return ((client->parser.http_major == 1) && (client->parser.http_minor == 0));
44+
}
45+
4146
static int send_http1_error_common(struct http_client_ctx *client,
4247
const char *response, size_t len)
4348
{
@@ -198,6 +203,7 @@ static int handle_http1_static_resource(
198203
#define RESPONSE_TEMPLATE_DYNAMIC_PART1 \
199204
"HTTP/1.1 %d%s%s\r\n" \
200205
"Transfer-Encoding: chunked\r\n"
206+
#define RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE "HTTP/1.1 %d%s%s\r\n"
201207

202208
static const char *http_status_str(enum http_status status)
203209
{
@@ -234,15 +240,25 @@ static const char *http_status_str(enum http_status status)
234240
#define REASON_PHRASE_MAX_LENGTH 0
235241
#endif
236242

243+
#define RESPONSE_TEMPLATE_SIZE_HTTP10 \
244+
(sizeof(RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE) + REASON_PHRASE_MAX_LENGTH)
245+
#define RESPONSE_TEMPLATE_SIZE_DYNAMIC \
246+
(sizeof(RESPONSE_TEMPLATE_DYNAMIC_PART1) + sizeof("xxx") + REASON_PHRASE_MAX_LENGTH)
247+
248+
#define MAX_RESPONSE_TEMPLATE_SIZE \
249+
MAX(RESPONSE_TEMPLATE_SIZE_HTTP10, RESPONSE_TEMPLATE_SIZE_DYNAMIC)
250+
251+
#define HTTP_RESPONSE_BUF_SIZE \
252+
MAX(MAX_RESPONSE_TEMPLATE_SIZE, CONFIG_HTTP_SERVER_MAX_HEADER_LEN + 2)
253+
237254
static int http1_send_headers(struct http_client_ctx *client, enum http_status status,
238255
const struct http_header *headers, size_t header_count,
239256
struct http_resource_detail_dynamic *dynamic_detail)
240257
{
241258
int ret;
242259
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)];
260+
char http_response[HTTP_RESPONSE_BUF_SIZE];
261+
const char *header_template;
246262

247263
if (status < HTTP_100_CONTINUE || status > HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) {
248264
LOG_DBG("Invalid HTTP status code: %d", status);
@@ -254,8 +270,11 @@ static int http1_send_headers(struct http_client_ctx *client, enum http_status s
254270
return -EINVAL;
255271
}
256272

273+
header_template = is_client_http10(client) ? RESPONSE_TEMPLATE_DYNAMIC_HTTP_10_COMPATIBLE
274+
: RESPONSE_TEMPLATE_DYNAMIC_PART1;
275+
257276
/* Send response code and transfer encoding */
258-
snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE_DYNAMIC_PART1, status,
277+
snprintk(http_response, sizeof(http_response), header_template, status,
259278
IS_ENABLED(CONFIG_HTTP_SERVER_COMPLETE_STATUS_PHRASES) ? " " : "",
260279
http_status_str(status));
261280

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

359378
/* Send body data if provided */
360379
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;
380+
381+
/* Check if the client expects HTTP/1.0 compatible response */
382+
if (!is_client_http10(client)) {
383+
/* Use Transfer-Encoding: chunked */
384+
ret = snprintk(tmp, sizeof(tmp), "%zx\r\n", rsp->body_len);
385+
ret = http_server_sendall(client, tmp, ret);
386+
if (ret < 0) {
387+
return ret;
388+
}
365389
}
366390

367391
ret = http_server_sendall(client, rsp->body, rsp->body_len);
368392
if (ret < 0) {
369393
return ret;
370394
}
371395

372-
(void)http_server_sendall(client, crlf, 2);
396+
if (!is_client_http10(client)) {
397+
/* Use Transfer-Encoding: chunked */
398+
(void)http_server_sendall(client, crlf, 2);
399+
}
373400
}
374401

375402
return 0;
@@ -410,10 +437,12 @@ static int dynamic_get_del_req(struct http_resource_detail_dynamic *dynamic_deta
410437

411438
dynamic_detail->holder = NULL;
412439

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

419448
return 0;
@@ -490,10 +519,12 @@ static int dynamic_post_put_req(struct http_resource_detail_dynamic *dynamic_det
490519
}
491520
}
492521

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

499530
dynamic_detail->holder = NULL;
@@ -1111,7 +1142,12 @@ not_found: ; /* Add extra semicolon to make clang to compile when using label */
11111142
client->data_len -= parsed;
11121143

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

0 commit comments

Comments
 (0)