Skip to content

Commit 23e748c

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 23e748c

2 files changed

Lines changed: 58 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: 21 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 includes 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,8 +235,11 @@ 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

240+
AT#XCLOSE=0
241+
#XCLOSE: 0,0
242+
OK
234243

235244
HTTP HEAD (no body — ``#XHTTPCSTAT`` follows immediately after ``#XHTTPCHEAD``):
236245

@@ -242,7 +251,7 @@ HTTP HEAD (no body — ``#XHTTPCSTAT`` follows immediately after ``#XHTTPCHEAD``
242251

243252
#XHTTPCHEAD: 0,200,261
244253

245-
#XHTTPCSTAT: 0,200,0
254+
#XHTTPCSTAT: 0,200,0,0
246255

247256
HTTP POST with chunked response (``content_length=-1``):
248257

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

262-
#XHTTPCSTAT: 0,200,1132
271+
#XHTTPCSTAT: 0,200,1132,0
263272

264273
.. note::
265274

@@ -372,7 +381,7 @@ Example
372381
<256 bytes>
373382
OK
374383

375-
#XHTTPCSTAT: 0,200,512
384+
#XHTTPCSTAT: 0,200,512,0
376385

377386
Test command
378387
------------
@@ -412,7 +421,7 @@ The timer resets each time data is sent or received:
412421
* Receiving response headers or body bytes.
413422
* Pulling a body chunk in manual mode (``AT#XHTTPCDATA``).
414423

415-
If no such activity occurs within the configured window, the request is aborted and ``#XHTTPCSTAT: <handle>,-1,<total_bytes>`` is emitted.
424+
If no such activity occurs within the configured window, the request is aborted and ``#XHTTPCSTAT: <handle>,-1,<total_bytes>,<connection_close>`` is emitted.
416425
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.
417426

418427
HTTP request cancel #XHTTPCCANCEL
@@ -435,7 +444,7 @@ Syntax
435444
* The ``<handle>`` parameter is an integer.
436445
It identifies the socket of the request to cancel.
437446

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.
447+
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.
439448

440449
Example
441450
~~~~~~~
@@ -444,7 +453,7 @@ Example
444453

445454
AT#XHTTPCCANCEL=0
446455
OK
447-
#XHTTPCSTAT: 0,-1,0
456+
#XHTTPCSTAT: 0,-1,0,0
448457

449458
Test command
450459
------------

0 commit comments

Comments
 (0)