Skip to content

Commit 7459a21

Browse files
committed
app: http: Handle HTTP chunked transfer encoding
Detect HTTP/1.1 chunked transfer EOF marker in a body data buffer. Update documentation. Signed-off-by: Juha Ylinen <juha.ylinen@nordicsemi.no>
1 parent ef45232 commit 7459a21

2 files changed

Lines changed: 88 additions & 18 deletions

File tree

app/src/sm_at_httpc.c

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,15 @@ static void http_send_data(struct http_request *req, const uint8_t *data, int le
482482
data_send(req->pipe, data, len);
483483
}
484484

485+
/*
486+
* Detect HTTP/1.1 chunked transfer EOF marker in a body data buffer.
487+
* The final chunk is always "0\r\n\r\n" (zero-length chunk, RFC 9112 §7.1.1).
488+
*/
489+
static bool chunked_eof(const uint8_t *data, int len)
490+
{
491+
return len >= 5 && memcmp(data + len - 5, "0\r\n\r\n", 5) == 0;
492+
}
493+
485494
/* Parse HTTP status code from response buffer */
486495
static int parse_http_status_code(const char *buf, int *status_code)
487496
{
@@ -616,12 +625,14 @@ static bool http_headers_complete(struct http_request *req, char *header_end,
616625
}
617626

618627
/*
619-
* All body bytes arrived piggybacked with the headers and
620-
* content-length is satisfied. With keep-alive connections
621-
* there is no subsequent EOF to trigger the completion check
622-
* in the RECEIVING_BODY path, so finish here instead.
628+
* Finish early when all body data is already in hand (piggybacked):
629+
* either content-length is satisfied, or chunked terminator was received.
630+
* With keep-alive connections there is no subsequent EOF to trigger the
631+
* completion check in the RECEIVING_BODY path.
623632
*/
624-
if (req->content_length >= 0 && req->bytes_sent >= req->content_length) {
633+
if ((req->content_length >= 0 && req->bytes_sent >= req->content_length) ||
634+
(req->content_length < 0 &&
635+
chunked_eof(req->recv_buf + body_offset, body_len))) {
625636
http_finish_request(req);
626637
return true;
627638
}
@@ -697,11 +708,31 @@ static void http_process_request(struct http_request *req, uint8_t events)
697708
/* POLLHUP without POLLIN before headers are received is an error:
698709
* the server closed the connection before sending a valid response.
699710
*/
700-
if ((events & NRF_POLLHUP) && !(events & NRF_POLLIN) &&
701-
!req->headers_complete) {
702-
LOG_ERR("HTTP %d: Connection closed before headers (POLLHUP)", req->fd);
703-
http_fail_request(req);
704-
return;
711+
if (events & NRF_POLLHUP) {
712+
if (!req->headers_complete) {
713+
/* Server closed before headers arrived */
714+
LOG_ERR("HTTP %d: Connection closed before headers (POLLHUP)",
715+
req->fd);
716+
http_fail_request(req);
717+
return;
718+
}
719+
if (!(events & NRF_POLLIN)) {
720+
/* POLLHUP alone during body reception: server closed cleanly
721+
* after all data. Treat as EOF.
722+
*/
723+
if (req->state == HTTP_STATE_RECEIVING_BODY) {
724+
if (req->content_length > 0 &&
725+
req->total_received < req->content_length)
726+
LOG_WRN("HTTP %d: Incomplete - %d/%d bytes",
727+
req->fd, req->total_received,
728+
req->content_length);
729+
http_finish_request(req);
730+
return;
731+
}
732+
/* In RECEIVING_HEADERS, fall through to the POLLIN
733+
* handler which may drain remaining bytes.
734+
*/
735+
}
705736
}
706737

707738
/* Handle POLLIN */
@@ -786,14 +817,18 @@ static void http_process_request(struct http_request *req, uint8_t events)
786817
xapoll_stop(sock);
787818
return;
788819
}
820+
bool body_done = chunked_eof(req->recv_buf, req->recv_buf_len);
821+
789822
http_send_data(req, req->recv_buf, req->recv_buf_len);
790823
req->recv_buf_len = 0;
791824

792-
/* Finish if content-length satisfied, or if the connection
793-
* is already closing (POLLHUP co-fired) — no point re-arming
794-
* POLLIN on a closing socket; nrf_recv would return EAGAIN.
825+
/* Finish if:
826+
* - content-length satisfied (known-length transfer)
827+
* - chunked terminator "0\r\n\r\n" just received
828+
* - POLLHUP co-fired (connection closing)
795829
*/
796-
if ((req->content_length > 0 &&
830+
if (body_done ||
831+
(req->content_length > 0 &&
797832
req->bytes_sent >= req->content_length) ||
798833
(events & NRF_POLLHUP)) {
799834
http_finish_request(req);

doc/app/at_httpc.rst

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ Unsolicited notification
109109
* The ``<status_code>`` parameter is an integer.
110110
It contains the HTTP status code returned by the server.
111111
* The ``<content_length>`` parameter is an integer.
112-
It contains the value of the ``Content-Length`` response header, or ``-1`` when no such header is present.
112+
It contains the value of the ``Content-Length`` response header, or ``-1`` when the server uses chunked transfer encoding or does not provide a content length.
113113

114114
``#XHTTPCDATA`` is emitted in automatic mode for each received body chunk::
115115

@@ -124,6 +124,14 @@ The notification line is terminated with ``\r\n`` and the raw body bytes follow
124124
* The ``<length>`` parameter is an integer.
125125
It contains the number of body bytes in this chunk.
126126

127+
.. note::
128+
129+
When the server uses ``Transfer-Encoding: chunked``, the body bytes delivered via ``#XHTTPCDATA`` include the raw chunked framing.
130+
Each chunk consists of a size line (hexadecimal length followed by ``\r\n``), the chunk data, and a trailing ``\r\n``.
131+
The final zero-length chunk ``0\r\n\r\n`` is also forwarded.
132+
The host strips this framing to recover the original body content.
133+
The ``<total_bytes>`` field in ``#XHTTPCSTAT`` reflects the raw wire byte count, including chunked framing, not the decoded body length.
134+
127135
``#XHTTPCSTAT`` is emitted when the request completes, fails, or is cancelled::
128136

129137
#XHTTPCSTAT: <handle>,<status_code>,<total_bytes>
@@ -134,6 +142,7 @@ The notification line is terminated with ``\r\n`` and the raw body bytes follow
134142
It contains the HTTP status code on success, or ``-1`` on failure, cancel, or timeout.
135143
* The ``<total_bytes>`` parameter is an integer.
136144
On successful completion, failure, or timeout, it contains the total number of response body bytes received by the HTTP client.
145+
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).
137146
On cancel (``status_code=-1`` from ``AT#XHTTPCCANCEL``), it contains the number of response body bytes already delivered to the host.
138147

139148
.. note::
@@ -235,6 +244,28 @@ HTTP HEAD (no body — ``#XHTTPCSTAT`` follows immediately after ``#XHTTPCHEAD``
235244

236245
#XHTTPCSTAT: 0,200,0
237246

247+
HTTP POST with chunked response (``content_length=-1``):
248+
249+
::
250+
251+
AT#XHTTPCREQ=0,<url>,1,1,1024
252+
#XHTTPCREQ: 0
253+
OK
254+
<1024 bytes payload>
255+
#XDATAMODE: 0
256+
257+
#XHTTPCHEAD: 0,200,-1
258+
259+
#XHTTPCDATA: 0,0,1132
260+
460\r\n{"args":{},"data":"<1024 bytes>","url":"..."}\r\n0\r\n\r\n
261+
262+
#XHTTPCSTAT: 0,200,1132
263+
264+
.. note::
265+
266+
The 1132 raw bytes break down as chunked framing: ``460\r\n`` (chunk-size 1120 decimal in hex, 5 bytes), 1120 bytes of JSON body, ``\r\n`` (chunk trailer, 2 bytes), and ``0\r\n\r\n`` (final zero-length chunk, 5 bytes).
267+
Strip this framing to recover the 1120-byte JSON body.
268+
238269
Test command
239270
------------
240271

@@ -313,9 +344,13 @@ When the socket buffer is temporarily empty (EAGAIN)::
313344
It contains the number of body bytes delivered in this pull.
314345
A value of ``0`` means the socket buffer is currently empty.
315346

316-
When all body bytes have been delivered, ``#XHTTPCSTAT`` is sent as a URC after
317-
the final ``OK``. This happens either when the server closes the connection, or
318-
when the ``Content-Length`` bytes have all been forwarded.
347+
When all body bytes have been delivered, ``#XHTTPCSTAT`` is sent as a URC after the final ``OK``.
348+
This happens either when the server closes the connection, when the ``Content-Length`` bytes have all been forwarded, or when the chunked transfer ``0\r\n\r\n`` terminator is received.
349+
350+
.. note::
351+
352+
When the server uses ``Transfer-Encoding: chunked`` (``content_length=-1`` in ``#XHTTPCHEAD``), the bytes returned by ``#XHTTPCDATA`` include raw chunked framing.
353+
See the note under ``#XHTTPCDATA`` in the ``AT#XHTTPCREQ`` section.
319354

320355
.. note::
321356

0 commit comments

Comments
 (0)