Skip to content

Commit 62895f8

Browse files
committed
app: http: Handle "Connection: close" header in HTTP response
Add <connection_close> field to #XHTTPCSTAT URC to indicate when the server signals connection closure via the "Connection: close" header. XHTTPCSTAT: <handle>,<status_code>,<bytes>,<connection_close> When <connection_close> is set, the host must close and reopen the socket before sending the next request. Signed-off-by: Juha Ylinen <juha.ylinen@nordicsemi.no>
1 parent f34c54a commit 62895f8

2 files changed

Lines changed: 55 additions & 22 deletions

File tree

app/src/sm_at_httpc.c

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ struct http_request {
8282
struct modem_pipe *pipe; /* AT pipe that created this request */
8383
bool manual_mode; /* Manual mode: body not auto-received, host pulls chunks */
8484
int bytes_sent; /* Response-body bytes sent to the host */
85+
bool connection_close; /* Server sent "Connection: close" header */
8586
};
8687

8788
static const char * const http_method_str[] = {
@@ -105,6 +106,7 @@ static bool http_headers_complete(struct http_request *req, char *header_end,
105106
struct sm_socket *sock, bool hup);
106107
static int parse_http_status_code(const char *buf, int *status_code);
107108
static int parse_content_length(const char *buf, const char *header_end, int *length);
109+
static bool parse_connection_close(const char *buf, const char *header_end);
108110
static void http_timeout_work_fn(struct k_work *work);
109111

110112
static K_MUTEX_DEFINE(http_mutex);
@@ -433,20 +435,22 @@ static void http_close_request(struct http_request *req)
433435
/* Send error via XHTTPCSTAT with -1 status code */
434436
static void http_send_error(struct http_request *req)
435437
{
436-
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,-1,%d\r\n", req->fd, req->total_received);
438+
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,-1,%d,%d\r\n", req->fd,
439+
req->total_received, (int)req->connection_close);
437440
}
438441

439442
/* Send status URC */
440443
static void http_send_status(struct http_request *req)
441444
{
442-
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,%d,%d\r\n", req->fd, req->status_code,
443-
req->total_received);
445+
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,%d,%d,%d\r\n", req->fd, req->status_code,
446+
req->total_received, (int)req->connection_close);
444447
}
445448

446449
/* Send cancel status URC with bytes already delivered to host */
447450
static void http_send_cancel_status(struct http_request *req)
448451
{
449-
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,-1,%d\r\n", req->fd, req->bytes_sent);
452+
urc_send_to(req->pipe, "\r\n#XHTTPCSTAT: %d,-1,%d,%d\r\n", req->fd,
453+
req->bytes_sent, (int)req->connection_close);
450454
}
451455

452456
/* Send error and close request */
@@ -511,6 +515,19 @@ static int parse_http_status_code(const char *buf, int *status_code)
511515
return 0;
512516
}
513517

518+
/* Parse Connection: close header from response buffer */
519+
static bool parse_connection_close(const char *buf, const char *header_end)
520+
{
521+
const char *p;
522+
523+
p = strstr(buf, "Connection: close");
524+
if (!p) {
525+
p = strstr(buf, "connection: close");
526+
}
527+
528+
return p != NULL && p < header_end;
529+
}
530+
514531
/* Parse Content-Length header from response buffer */
515532
static int parse_content_length(const char *buf, const char *header_end, int *length)
516533
{
@@ -563,6 +580,11 @@ static bool http_headers_complete(struct http_request *req, char *header_end,
563580
LOG_DBG("HTTP %d: No Content-Length header", req->fd);
564581
}
565582

583+
req->connection_close = parse_connection_close((char *)req->recv_buf, header_end);
584+
if (req->connection_close) {
585+
LOG_INF("HTTP %d: Server sent Connection: close", req->fd);
586+
}
587+
566588
req->headers_complete = true;
567589
req->state = HTTP_STATE_RECEIVING_BODY;
568590

@@ -707,22 +729,26 @@ static void http_process_request(struct http_request *req, uint8_t events)
707729
case HTTP_STATE_RECEIVING_BODY:
708730
/* POLLHUP without POLLIN before headers are received is an error:
709731
* the server closed the connection before sending a valid response.
732+
* When POLLIN co-fires, data may still be in the socket buffer (e.g.
733+
* a server with Connection: close that sends headers and closes
734+
* simultaneously); fall through to the POLLIN handler to drain it.
710735
*/
711736
if (events & NRF_POLLHUP) {
712-
if (!req->headers_complete) {
713-
/* Server closed before headers arrived */
737+
if (!req->headers_complete && !(events & NRF_POLLIN)) {
738+
/* Server closed before headers arrived and no data to read */
714739
LOG_ERR("HTTP %d: Connection closed before headers (POLLHUP)",
715740
req->fd);
716741
http_fail_request(req);
717742
return;
718743
}
719-
if (!(events & NRF_POLLIN)) {
744+
if (req->headers_complete && !(events & NRF_POLLIN)) {
720745
/* POLLHUP alone during body reception: server closed cleanly
721-
* after all data. Treat as EOF.
746+
* after all data. Treat as EOF.
722747
*/
723748
if (req->state == HTTP_STATE_RECEIVING_BODY) {
724749
if (req->content_length > 0 &&
725-
req->total_received < req->content_length)
750+
req->total_received < req->content_length &&
751+
!req->connection_close)
726752
LOG_WRN("HTTP %d: Incomplete - %d/%d bytes",
727753
req->fd, req->total_received,
728754
req->content_length);
@@ -768,7 +794,8 @@ static void http_process_request(struct http_request *req, uint8_t events)
768794

769795
/* Check if we received all expected data */
770796
if (req->content_length > 0 &&
771-
req->total_received < req->content_length) {
797+
req->total_received < req->content_length &&
798+
!req->connection_close) {
772799
LOG_WRN("HTTP %d: Incomplete transfer - received %d/%d "
773800
"bytes",
774801
req->fd, req->total_received,

doc/app/at_httpc.rst

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ The notification line is terminated with ``\r\n`` and the raw body bytes follow
134134

135135
``#XHTTPCSTAT`` is emitted when the request completes, fails, or is cancelled::
136136

137-
#XHTTPCSTAT: <handle>,<status_code>,<total_bytes>
137+
#XHTTPCSTAT: <handle>,<status_code>,<total_bytes>,<connection_close>
138138

139139
* The ``<handle>`` parameter is an integer.
140140
It identifies the socket.
@@ -144,6 +144,10 @@ The notification line is terminated with ``\r\n`` and the raw body bytes follow
144144
On successful completion, failure, or timeout, it contains the total number of response body bytes received by the HTTP client.
145145
For chunked transfer encoding this includes the raw framing bytes (chunk-size lines, ``\r\n`` separators, and the final ``0\r\n\r\n`` terminator).
146146
On cancel (``status_code=-1`` from ``AT#XHTTPCCANCEL``), it contains the number of response body bytes already delivered to the host.
147+
* The ``<connection_close>`` parameter is an integer.
148+
It is ``1`` when the server included a ``Connection: close`` header in its response, indicating that the TCP connection will be closed after this response.
149+
It is ``0`` otherwise (keep-alive connection).
150+
When ``<connection_close>`` is ``1``, the host must close the socket with ``AT#XCLOSE`` and open a new connection before issuing the next request.
147151

148152
.. note::
149153

@@ -169,7 +173,7 @@ HTTP GET (automatic mode):
169173
#XHTTPCDATA: 0,0,261
170174
<261 bytes>
171175

172-
#XHTTPCSTAT: 0,200,261
176+
#XHTTPCSTAT: 0,200,261,0
173177

174178
HTTP GET (manual mode):
175179

@@ -186,7 +190,7 @@ HTTP GET (manual mode):
186190
<261 bytes>
187191
OK
188192

189-
#XHTTPCSTAT: 0,200,261
193+
#XHTTPCSTAT: 0,200,261,0
190194

191195
HTTP POST with JSON body and custom header:
192196

@@ -203,10 +207,12 @@ HTTP POST with JSON body and custom header:
203207
#XHTTPCDATA: 0,0,432
204208
<432 bytes>
205209

206-
#XHTTPCSTAT: 0,200,432
210+
#XHTTPCSTAT: 0,200,432,0
207211

208212
HTTP GET with Range header (``body_len=0`` is required as a placeholder when headers follow a GET):
209213

214+
In this example the server returns ``connection_close=1``, so the host must close and reopen the socket before the next request.
215+
210216
::
211217

212218
AT#XHTTPCREQ=0,<url>,0,1,0,"Range: bytes=0-127"
@@ -218,7 +224,7 @@ HTTP GET with Range header (``body_len=0`` is required as a placeholder when hea
218224
#XHTTPCDATA: 0,0,128
219225
<128 bytes>
220226

221-
#XHTTPCSTAT: 0,206,128
227+
#XHTTPCSTAT: 0,206,128,0
222228

223229
AT#XHTTPCREQ=0,<url>,0,1,0,"Range: bytes=128-255"
224230
#XHTTPCREQ: 0
@@ -229,7 +235,7 @@ HTTP GET with Range header (``body_len=0`` is required as a placeholder when hea
229235
#XHTTPCDATA: 0,0,128
230236
<128 bytes>
231237

232-
#XHTTPCSTAT: 0,206,128
238+
#XHTTPCSTAT: 0,206,128,1
233239

234240

235241
HTTP HEAD (no body — ``#XHTTPCSTAT`` follows immediately after ``#XHTTPCHEAD``):
@@ -242,7 +248,7 @@ HTTP HEAD (no body — ``#XHTTPCSTAT`` follows immediately after ``#XHTTPCHEAD``
242248

243249
#XHTTPCHEAD: 0,200,261
244250

245-
#XHTTPCSTAT: 0,200,0
251+
#XHTTPCSTAT: 0,200,0,0
246252

247253
HTTP POST with chunked response (``content_length=-1``):
248254

@@ -259,7 +265,7 @@ HTTP POST with chunked response (``content_length=-1``):
259265
#XHTTPCDATA: 0,0,1132
260266
460\r\n{"args":{},"data":"<1024 bytes>","url":"..."}\r\n0\r\n\r\n
261267

262-
#XHTTPCSTAT: 0,200,1132
268+
#XHTTPCSTAT: 0,200,1132,0
263269

264270
.. note::
265271

@@ -372,7 +378,7 @@ Example
372378
<256 bytes>
373379
OK
374380

375-
#XHTTPCSTAT: 0,200,512
381+
#XHTTPCSTAT: 0,200,512,0
376382

377383
Test command
378384
------------
@@ -412,7 +418,7 @@ The timer resets each time data is sent or received:
412418
* Receiving response headers or body bytes.
413419
* Pulling a body chunk in manual mode (``AT#XHTTPCDATA``).
414420

415-
If no such activity occurs within the configured window, the request is aborted and ``#XHTTPCSTAT: <handle>,-1,<total_bytes>`` is emitted.
421+
If no such activity occurs within the configured window, the request is aborted and ``#XHTTPCSTAT: <handle>,-1,<total_bytes>,<connection_close>`` is emitted.
416422
The timeout is enforced by a background timer that fires independently of normal socket poll events, so a server that stalls silently (no TCP RST or FIN) is also detected.
417423

418424
HTTP request cancel #XHTTPCCANCEL
@@ -435,7 +441,7 @@ Syntax
435441
* The ``<handle>`` parameter is an integer.
436442
It identifies the socket of the request to cancel.
437443

438-
An unsolicited ``#XHTTPCSTAT: <handle>,-1,<total_bytes>`` notification is emitted after cancellation, where ``<total_bytes>`` is the number of response body bytes already delivered to the host.
444+
An unsolicited ``#XHTTPCSTAT: <handle>,-1,<total_bytes>,<connection_close>`` notification is emitted after cancellation, where ``<total_bytes>`` is the number of response body bytes already delivered to the host.
439445

440446
Example
441447
~~~~~~~
@@ -444,7 +450,7 @@ Example
444450

445451
AT#XHTTPCCANCEL=0
446452
OK
447-
#XHTTPCSTAT: 0,-1,0
453+
#XHTTPCSTAT: 0,-1,0,0
448454

449455
Test command
450456
------------

0 commit comments

Comments
 (0)