Skip to content

Commit baf8da8

Browse files
committed
app: http: Simplify http_process_request receive cases
Split HTTP_STATE_RECEIVING_HEADERS and HTTP_STATE_RECEIVING_BODY to separate switch cases. Signed-off-by: Juha Ylinen <juha.ylinen@nordicsemi.no>
1 parent fd4be41 commit baf8da8

1 file changed

Lines changed: 159 additions & 130 deletions

File tree

app/src/sm_at_httpc.c

Lines changed: 159 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ static void http_finish_request(struct http_request *req);
104104
static int http_start_request(struct http_request *req);
105105
static bool http_headers_complete(struct http_request *req, char *header_end,
106106
struct sm_socket *sock, bool hup);
107+
static void http_warn_incomplete_transfer(const struct http_request *req);
108+
static int http_recv_read(struct http_request *req, struct sm_socket *sock);
109+
static void http_process_recv_headers(struct http_request *req, struct sm_socket *sock,
110+
uint8_t events);
111+
static void http_process_recv_body(struct http_request *req, struct sm_socket *sock,
112+
uint8_t events);
107113
static int parse_http_status_code(const char *buf, int *status_code);
108114
static int parse_content_length(const char *buf, const char *header_end, int *length);
109115
static bool parse_connection_close(const char *buf, const char *header_end);
@@ -635,8 +641,9 @@ static bool http_headers_complete(struct http_request *req, char *header_end,
635641
LOG_DBG("HTTP %d: Headers complete, status %d", req->fd, req->status_code);
636642

637643
/*
638-
* If POLLHUP co-fired the connection is already closing; finish now
639-
* rather than re-arming POLLIN on a socket that will never fire again.
644+
* If POLLHUP arrived together with POLLIN, the connection is already
645+
* closing; finish now rather than re-arming POLLIN on a socket that
646+
* will never fire again.
640647
*/
641648
if (hup) {
642649
if (req->content_length > 0 && req->bytes_sent < req->content_length)
@@ -662,6 +669,112 @@ static bool http_headers_complete(struct http_request *req, char *header_end,
662669
return false;
663670
}
664671

672+
static void http_warn_incomplete_transfer(const struct http_request *req)
673+
{
674+
if (req->content_length > 0 &&
675+
req->total_received < req->content_length &&
676+
!req->connection_close) {
677+
LOG_WRN("HTTP %d: Incomplete transfer - received %d/%d bytes",
678+
req->fd, req->total_received, req->content_length);
679+
}
680+
}
681+
682+
/*
683+
* Read from the socket into recv_buf. On success, updates recv_buf_len,
684+
* total_received, and the idle timeout.
685+
*
686+
* Returns: >0 bytes read
687+
* 0 EOF (connection closed)
688+
* -1 EAGAIN (POLLIN re-armed)
689+
* -errno other recv error (caller should fail the request)
690+
*/
691+
static int http_recv_read(struct http_request *req, struct sm_socket *sock)
692+
{
693+
int ret;
694+
695+
ret = nrf_recv(req->fd, req->recv_buf + req->recv_buf_len,
696+
HTTP_RECV_BUF_SIZE - req->recv_buf_len - 1, NRF_MSG_DONTWAIT);
697+
698+
if (ret < 0) {
699+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
700+
set_xapoll_events(sock, NRF_POLLIN);
701+
return -1;
702+
}
703+
if (errno == ETIMEDOUT) {
704+
LOG_ERR("Recv timed out");
705+
return -ETIMEDOUT;
706+
}
707+
LOG_ERR("Recv failed: %d", errno);
708+
return -errno;
709+
}
710+
711+
if (ret == 0) {
712+
return 0;
713+
}
714+
715+
req->recv_buf_len += ret;
716+
req->recv_buf[req->recv_buf_len] = '\0';
717+
req->total_received += ret;
718+
req->timeout_timestamp = k_uptime_get() + HTTP_RESPONSE_TIMEOUT_MS;
719+
720+
return ret;
721+
}
722+
723+
static void http_process_recv_headers(struct http_request *req, struct sm_socket *sock,
724+
uint8_t events)
725+
{
726+
char *header_end = strstr((char *)req->recv_buf, "\r\n\r\n");
727+
728+
if (header_end) {
729+
if (http_headers_complete(req, header_end, sock, events & NRF_POLLHUP)) {
730+
return;
731+
}
732+
req->need_rearm_pollin = true;
733+
return;
734+
}
735+
736+
if (req->recv_buf_len >= HTTP_RECV_BUF_SIZE - 1) {
737+
LOG_ERR("HTTP headers too large");
738+
http_fail_request(req);
739+
return;
740+
}
741+
742+
req->need_rearm_pollin = true;
743+
}
744+
745+
static void http_process_recv_body(struct http_request *req, struct sm_socket *sock,
746+
uint8_t events)
747+
{
748+
bool body_done;
749+
750+
if (req->manual_mode) {
751+
/*
752+
* POLLIN fired in body state after xapoll_stop (race).
753+
* nrf_recv already consumed bytes from the socket buffer
754+
* into recv_buf and incremented total_received. Keep
755+
* recv_buf intact so the host can pull it; do NOT reset
756+
* recv_buf_len or the data is silently lost.
757+
*/
758+
xapoll_stop(sock);
759+
return;
760+
}
761+
762+
body_done = chunked_eof(req->recv_buf, req->recv_buf_len);
763+
764+
http_send_data(req, req->recv_buf, req->recv_buf_len);
765+
req->recv_buf_len = 0;
766+
767+
/* Finish if content-length satisfied, chunked EOF, or connection closing. */
768+
if (body_done ||
769+
(req->content_length > 0 && req->bytes_sent >= req->content_length) ||
770+
(events & NRF_POLLHUP)) {
771+
http_finish_request(req);
772+
return;
773+
}
774+
775+
req->need_rearm_pollin = true;
776+
}
777+
665778
/* Process HTTP request state machine (event-driven via XAPOLL) */
666779
static void http_process_request(struct http_request *req, uint8_t events)
667780
{
@@ -726,146 +839,62 @@ static void http_process_request(struct http_request *req, uint8_t events)
726839
break;
727840

728841
case HTTP_STATE_RECEIVING_HEADERS:
729-
case HTTP_STATE_RECEIVING_BODY:
730-
/* POLLHUP without POLLIN before headers are received is an error:
731-
* 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.
842+
/*
843+
* POLLHUP without POLLIN: closed before any response. When POLLIN
844+
* arrives at the same time, drain the socket buffer (e.g. Connection: close).
735845
*/
736-
if (events & NRF_POLLHUP) {
737-
if (!req->headers_complete && !(events & NRF_POLLIN)) {
738-
/* Server closed before headers arrived and no data to read */
739-
LOG_ERR("HTTP %d: Connection closed before headers (POLLHUP)",
740-
req->fd);
741-
http_fail_request(req);
742-
return;
743-
}
744-
if (req->headers_complete && !(events & NRF_POLLIN)) {
745-
/* POLLHUP alone during body reception: server closed cleanly
746-
* after all data. Treat as EOF.
747-
*/
748-
if (req->state == HTTP_STATE_RECEIVING_BODY) {
749-
if (req->content_length > 0 &&
750-
req->total_received < req->content_length &&
751-
!req->connection_close)
752-
LOG_WRN("HTTP %d: Incomplete - %d/%d bytes",
753-
req->fd, req->total_received,
754-
req->content_length);
755-
http_finish_request(req);
756-
return;
757-
}
758-
/* In RECEIVING_HEADERS, fall through to the POLLIN
759-
* handler which may drain remaining bytes.
760-
*/
761-
}
846+
if ((events & NRF_POLLHUP) && !(events & NRF_POLLIN)) {
847+
LOG_ERR("HTTP %d: Connection closed before headers (POLLHUP)",
848+
req->fd);
849+
http_fail_request(req);
850+
return;
762851
}
763852

764-
/* Handle POLLIN */
765-
if (events & (NRF_POLLIN)) {
766-
ret = nrf_recv(req->fd, req->recv_buf + req->recv_buf_len,
767-
HTTP_RECV_BUF_SIZE - req->recv_buf_len - 1,
768-
NRF_MSG_DONTWAIT);
853+
if (!(events & NRF_POLLIN)) {
854+
return;
855+
}
769856

770-
if (ret < 0) {
771-
if (errno == EAGAIN || errno == EWOULDBLOCK) {
772-
/* Socket buffer empty - re-arm POLLIN to wait for more data
773-
*/
774-
set_xapoll_events(sock, NRF_POLLIN);
775-
return;
776-
}
777-
if (errno == ETIMEDOUT) {
778-
LOG_ERR("Recv timed out");
779-
http_fail_request(req);
780-
return;
781-
}
782-
LOG_ERR("Recv failed: %d", errno);
857+
ret = http_recv_read(req, sock);
858+
if (ret < 0) {
859+
if (ret != -1) {
783860
http_fail_request(req);
784-
return;
785-
}
786-
787-
if (ret == 0) {
788-
/* Connection closed by server (EOF) */
789-
if (!req->headers_complete) {
790-
/* Closed before headers arrived */
791-
http_fail_request(req);
792-
return;
793-
}
794-
795-
/* Check if we received all expected data */
796-
if (req->content_length > 0 &&
797-
req->total_received < req->content_length &&
798-
!req->connection_close) {
799-
LOG_WRN("HTTP %d: Incomplete transfer - received %d/%d "
800-
"bytes",
801-
req->fd, req->total_received,
802-
req->content_length);
803-
}
804-
805-
http_finish_request(req);
806-
return;
807861
}
862+
return;
863+
}
864+
if (ret == 0) {
865+
http_fail_request(req);
866+
return;
867+
}
808868

809-
/* Data received - update idle timeout */
810-
req->recv_buf_len += ret;
811-
req->recv_buf[req->recv_buf_len] = '\0';
812-
req->total_received += ret;
813-
req->timeout_timestamp = k_uptime_get() + HTTP_RESPONSE_TIMEOUT_MS;
814-
815-
if (req->state == HTTP_STATE_RECEIVING_HEADERS) {
816-
/* Look for end of headers */
817-
char *header_end = strstr((char *)req->recv_buf, "\r\n\r\n");
818-
819-
if (header_end) {
820-
if (http_headers_complete(req, header_end, sock,
821-
events & NRF_POLLHUP)) {
822-
return;
823-
}
824-
req->need_rearm_pollin = true;
825-
return;
826-
}
869+
http_process_recv_headers(req, sock, events);
870+
break;
827871

828-
/* Check if buffer is full */
829-
if (req->recv_buf_len >= HTTP_RECV_BUF_SIZE - 1) {
830-
LOG_ERR("HTTP headers too large");
831-
http_fail_request(req);
832-
return;
833-
}
834-
} else {
835-
/* Receiving body */
836-
if (req->manual_mode) {
837-
/*
838-
* POLLIN fired in body state after xapoll_stop (race).
839-
* nrf_recv already consumed bytes from the socket buffer
840-
* into recv_buf and incremented total_received. Keep
841-
* recv_buf intact so the host can pull it; do NOT reset
842-
* recv_buf_len or the data is silently lost.
843-
*/
844-
xapoll_stop(sock);
845-
return;
846-
}
847-
bool body_done = chunked_eof(req->recv_buf, req->recv_buf_len);
872+
case HTTP_STATE_RECEIVING_BODY:
873+
/* POLLHUP alone: server closed cleanly after sending body. */
874+
if ((events & NRF_POLLHUP) && !(events & NRF_POLLIN)) {
875+
http_warn_incomplete_transfer(req);
876+
http_finish_request(req);
877+
return;
878+
}
848879

849-
http_send_data(req, req->recv_buf, req->recv_buf_len);
850-
req->recv_buf_len = 0;
880+
if (!(events & NRF_POLLIN)) {
881+
return;
882+
}
851883

852-
/* Finish if:
853-
* - content-length satisfied (known-length transfer)
854-
* - chunked terminator "0\r\n\r\n" just received
855-
* - POLLHUP co-fired (connection closing)
856-
*/
857-
if (body_done ||
858-
(req->content_length > 0 &&
859-
req->bytes_sent >= req->content_length) ||
860-
(events & NRF_POLLHUP)) {
861-
http_finish_request(req);
862-
return;
863-
}
884+
ret = http_recv_read(req, sock);
885+
if (ret < 0) {
886+
if (ret != -1) {
887+
http_fail_request(req);
864888
}
865-
866-
/* Set flag for socket layer to re-arm POLLIN for continuous reception */
867-
req->need_rearm_pollin = true;
889+
return;
890+
}
891+
if (ret == 0) {
892+
http_warn_incomplete_transfer(req);
893+
http_finish_request(req);
894+
return;
868895
}
896+
897+
http_process_recv_body(req, sock, events);
869898
break;
870899

871900
case HTTP_STATE_IDLE:

0 commit comments

Comments
 (0)