You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2021/08/09 05:34:35 UTC
[incubator-nuttx-apps] 01/03: webclient: Implement non-blocking I/O
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx-apps.git
commit 0bae950b63b7af1455d48bba9e320bf269125783
Author: YAMAMOTO Takashi <ya...@midokura.com>
AuthorDate: Wed Feb 17 17:17:42 2021 +0900
webclient: Implement non-blocking I/O
* webclient_perform
* Add a new flag to use non-blocking mode (WEBCLIENT_FLAG_NON_BLOCKING)
* Implement restarting
* Add a few associated API functions
* webclient_get_poll_info: get the descriptor info for poll/select
* webclient_abort: abort the operation (instead of restarting)
---
include/netutils/webclient.h | 52 +++
netutils/webclient/webclient.c | 835 +++++++++++++++++++++++++++--------------
2 files changed, 614 insertions(+), 273 deletions(-)
diff --git a/include/netutils/webclient.h b/include/netutils/webclient.h
index 5f5c8ce..eea1de8 100644
--- a/include/netutils/webclient.h
+++ b/include/netutils/webclient.h
@@ -77,6 +77,39 @@
# endif
#endif
+/* The following WEBCLIENT_FLAG_xxx constants are for
+ * webclient_context::flags.
+ */
+
+/* WEBCLIENT_FLAG_NON_BLOCKING tells webclient_perform() to
+ * use non-blocking I/O.
+ *
+ * If this flag is set, webclient_perform() returns -EAGAIN
+ * when it would otherwise block for network I/O. In that case,
+ * the application should either retry the operation later by calling
+ * webclient_perform() again, or abort it by calling webclient_abort().
+ * It can also use webclient_get_poll_info() to avoid busy-retrying.
+ *
+ * If this flag is set, it's the application's responsibility to
+ * implement a timeout.
+ *
+ * If the application specifies tls_ops, it's the application's
+ * responsibility to make the TLS implementation to use non-blocking I/O
+ * in addition to specifying this flag.
+ *
+ * Caveat: Even when this flag is set, the current implementation performs
+ * the name resolution in a blocking manner.
+ */
+
+#define WEBCLIENT_FLAG_NON_BLOCKING 1U
+
+/* The following WEBCLIENT_FLAG_xxx constants are for
+ * webclient_poll_info::flags.
+ */
+
+#define WEBCLIENT_POLL_INFO_WANT_READ 1U
+#define WEBCLIENT_POLL_INFO_WANT_WRITE 2U
+
/****************************************************************************
* Public types
****************************************************************************/
@@ -181,6 +214,7 @@ typedef CODE int (*webclient_body_callback_t)(
FAR void *ctx);
struct webclient_tls_connection;
+struct webclient_poll_info;
struct webclient_tls_ops
{
@@ -196,6 +230,9 @@ struct webclient_tls_ops
FAR void *buf, size_t len);
CODE int (*close)(FAR void *ctx,
FAR struct webclient_tls_connection *conn);
+ CODE int (*get_poll_info)(FAR void *ctx,
+ FAR struct webclient_tls_connection *conn,
+ FAR struct webclient_poll_info *info);
};
struct webclient_context
@@ -248,6 +285,7 @@ struct webclient_context
* tls_ops - A vector to implement TLS operations.
* NULL means no https support.
* tls_ctx - A user pointer to be passed to tls_ops as it is.
+ * flags - OR'ed WEBCLIENT_FLAG_xxx values.
*/
FAR char *buffer;
@@ -261,6 +299,7 @@ struct webclient_context
FAR void *body_callback_arg;
FAR const struct webclient_tls_ops *tls_ops;
FAR void *tls_ctx;
+ unsigned int flags;
/* results
*
@@ -275,6 +314,16 @@ struct webclient_context
unsigned int http_status;
FAR char *http_reason;
size_t http_reason_len;
+
+ struct wget_s *ws;
+};
+
+struct webclient_poll_info
+{
+ /* A file descriptor to wait for i/o. */
+
+ int fd;
+ unsigned int flags; /* OR'ed WEBCLIENT_POLL_INFO_xxx flags */
};
/****************************************************************************
@@ -329,9 +378,12 @@ int wget_post(FAR const char *url, FAR const char *posts, FAR char *buffer,
void webclient_set_defaults(FAR struct webclient_context *ctx);
int webclient_perform(FAR struct webclient_context *ctx);
+void webclient_abort(FAR struct webclient_context *ctx);
void webclient_set_static_body(FAR struct webclient_context *ctx,
FAR const void *body,
size_t bodylen);
+int webclient_get_poll_info(FAR struct webclient_context *ctx,
+ FAR struct webclient_poll_info *info);
#undef EXTERN
#ifdef __cplusplus
diff --git a/netutils/webclient/webclient.c b/netutils/webclient/webclient.c
index 19d66ee..85b791f 100644
--- a/netutils/webclient/webclient.c
+++ b/netutils/webclient/webclient.c
@@ -53,6 +53,8 @@
#include <nuttx/compiler.h>
#include <debug.h>
+#include <assert.h>
+#include <fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdint.h>
@@ -111,11 +113,6 @@
# define CONFIG_WEBCLIENT_MAX_REDIRECT 50
#endif
-#define WEBCLIENT_STATE_STATUSLINE 0
-#define WEBCLIENT_STATE_HEADERS 1
-#define WEBCLIENT_STATE_DATA 2
-#define WEBCLIENT_STATE_CLOSE 3
-
#define HTTPSTATUS_NONE 0
#define HTTPSTATUS_OK 1
#define HTTPSTATUS_MOVED 2
@@ -128,15 +125,49 @@
#define WGET_MODE_GET 0
#define WGET_MODE_POST 1
+/* The following CONN_ flags are for conn::flags.
+ */
+
+#define CONN_WANT_READ WEBCLIENT_POLL_INFO_WANT_READ
+#define CONN_WANT_WRITE WEBCLIENT_POLL_INFO_WANT_WRITE
+
/****************************************************************************
* Private Types
****************************************************************************/
+enum webclient_state_e
+ {
+ WEBCLIENT_STATE_SOCKET,
+ WEBCLIENT_STATE_CONNECT,
+ WEBCLIENT_STATE_PREPARE_REQUEST,
+ WEBCLIENT_STATE_SEND_REQUEST,
+ WEBCLIENT_STATE_SEND_REQUEST_BODY,
+ WEBCLIENT_STATE_STATUSLINE,
+ WEBCLIENT_STATE_HEADERS,
+ WEBCLIENT_STATE_DATA,
+ WEBCLIENT_STATE_CLOSE,
+ WEBCLIENT_STATE_DONE,
+ };
+
+struct conn
+{
+ bool tls;
+
+ /* for !tls */
+
+ int sockfd;
+ unsigned int flags;
+
+ /* for tls */
+
+ struct webclient_tls_connection *tls_conn;
+};
+
struct wget_s
{
/* Internal status */
- uint8_t state;
+ enum webclient_state_e state;
uint8_t httpstatus;
uint16_t port; /* The port number to use in the connection */
@@ -160,13 +191,18 @@ struct wget_s
char scheme[sizeof("https") + 1];
char hostname[CONFIG_WEBCLIENT_MAXHOSTNAME];
char filename[CONFIG_WEBCLIENT_MAXFILENAME];
-};
-struct conn
-{
- bool tls;
- int sockfd;
- struct webclient_tls_connection *tls_conn;
+ bool need_conn_close;
+ struct conn conn;
+ unsigned int nredirect;
+ int redirected;
+
+ /* progress and todo for the current state (WEBCLIENT_STATE_) */
+
+ off_t state_offset;
+ size_t state_len;
+ FAR const void *data_buffer;
+ size_t data_len;
};
/****************************************************************************
@@ -217,6 +253,11 @@ static ssize_t conn_send(struct webclient_context *ctx, struct conn *conn,
ssize_t ret = send(conn->sockfd, buffer, len, 0);
if (ret == -1)
{
+ if (errno == EAGAIN)
+ {
+ conn->flags |= CONN_WANT_WRITE;
+ }
+
return -errno;
}
@@ -225,33 +266,6 @@ static ssize_t conn_send(struct webclient_context *ctx, struct conn *conn,
}
/****************************************************************************
- * Name: conn_send_all
- ****************************************************************************/
-
-static int conn_send_all(struct webclient_context *ctx, struct conn *conn,
- const void *buffer, size_t len)
-{
- size_t left = len;
- ssize_t ret;
-
- while (left > 0)
- {
- ret = conn_send(ctx, conn, buffer, left);
- if (ret < 0)
- {
- goto errout;
- }
-
- buffer += ret;
- left -= ret;
- }
-
- return len;
-errout:
- return ret;
-}
-
-/****************************************************************************
* Name: conn_recv
****************************************************************************/
@@ -267,6 +281,11 @@ static ssize_t conn_recv(struct webclient_context *ctx, struct conn *conn,
ssize_t ret = recv(conn->sockfd, buffer, len, 0);
if (ret == -1)
{
+ if (errno == EAGAIN)
+ {
+ conn->flags |= CONN_WANT_READ;
+ }
+
return -errno;
}
@@ -746,11 +765,9 @@ int webclient_perform(FAR struct webclient_context *ctx)
{
struct wget_s *ws;
struct timeval tv;
- bool redirected;
- unsigned int nredirect = 0;
char *dest;
char *ep;
- struct conn conn;
+ struct conn *conn;
FAR const struct webclient_tls_ops *tls_ops = ctx->tls_ops;
FAR const char *method = ctx->method;
FAR void *tls_ctx = ctx->tls_ctx;
@@ -760,225 +777,336 @@ int webclient_perform(FAR struct webclient_context *ctx)
/* Initialize the state structure */
- ws = calloc(1, sizeof(struct wget_s));
- if (!ws)
+ if (ctx->ws == NULL)
{
- return -errno;
- }
+ ws = calloc(1, sizeof(struct wget_s));
+ if (!ws)
+ {
+ return -errno;
+ }
- ws->buffer = ctx->buffer;
- ws->buflen = ctx->buflen;
+ ws->buffer = ctx->buffer;
+ ws->buflen = ctx->buflen;
- /* Parse the hostname (with optional port number) and filename
- * from the URL.
- */
+ /* Parse the hostname (with optional port number) and filename
+ * from the URL.
+ */
- ret = parseurl(ctx->url, ws);
- if (ret != 0)
- {
- nwarn("WARNING: Malformed URL: %s\n", ctx->url);
- free(ws);
- return ret;
+ ret = parseurl(ctx->url, ws);
+ if (ret != 0)
+ {
+ nwarn("WARNING: Malformed URL: %s\n", ctx->url);
+ free(ws);
+ return ret;
+ }
+
+ ws->state = WEBCLIENT_STATE_SOCKET;
+ ctx->ws = ws;
}
+ ws = ctx->ws;
+
ninfo("hostname='%s' filename='%s'\n", ws->hostname, ws->filename);
/* The following sequence may repeat indefinitely if we are redirected */
+ conn = &ws->conn;
do
{
- if (!strcmp(ws->scheme, "https") && tls_ops != NULL)
- {
- conn.tls = true;
- }
- else if (!strcmp(ws->scheme, "http"))
+ if (ws->state == WEBCLIENT_STATE_SOCKET)
{
- conn.tls = false;
- }
- else
- {
- nerr("ERROR: unsupported scheme: %s\n", ws->scheme);
- free(ws);
- return -ENOTSUP;
- }
-
- /* Re-initialize portions of the state structure that could have
- * been left from the previous time through the loop and should not
- * persist with the new connection.
- */
-
- ws->httpstatus = HTTPSTATUS_NONE;
- ws->offset = 0;
- ws->datend = 0;
- ws->ndx = 0;
-
- if (conn.tls)
- {
- char port_str[sizeof("65535")];
-
-#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
- if (ctx->unix_socket_path != NULL)
+ if (!strcmp(ws->scheme, "https") && tls_ops != NULL)
+ {
+ conn->tls = true;
+ }
+ else if (!strcmp(ws->scheme, "http"))
{
- nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n");
+ conn->tls = false;
+ }
+ else
+ {
+ nerr("ERROR: unsupported scheme: %s\n", ws->scheme);
free(ws);
return -ENOTSUP;
}
-#endif
- snprintf(port_str, sizeof(port_str), "%u", ws->port);
- ret = tls_ops->connect(tls_ctx, ws->hostname, port_str,
- ctx->timeout_sec, &conn.tls_conn);
- }
- else
- {
-#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
- struct sockaddr_un server_un;
-#endif
- struct sockaddr_in server_in;
- int domain;
- const struct sockaddr *server_address;
- socklen_t server_address_len;
+ /* Re-initialize portions of the state structure that could have
+ * been left from the previous time through the loop and should not
+ * persist with the new connection.
+ */
-#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
- if (ctx->unix_socket_path != NULL)
- {
- domain = PF_LOCAL;
+ ws->httpstatus = HTTPSTATUS_NONE;
+ ws->offset = 0;
+ ws->datend = 0;
+ ws->ndx = 0;
+ ws->redirected = 0;
- memset(&server_un, 0, sizeof(server_un));
- server_un.sun_family = AF_LOCAL;
- strncpy(server_un.sun_path, ctx->unix_socket_path,
- sizeof(server_un.sun_path));
-#if !defined(__NuttX__) && !defined(__linux__)
- server_un.sun_len = SUN_LEN(&server_un);
+ if (conn->tls)
+ {
+#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
+ if (ctx->unix_socket_path != NULL)
+ {
+ nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n");
+ free(ws);
+ return -ENOTSUP;
+ }
#endif
- server_address = (const struct sockaddr *)&server_un;
- server_address_len = sizeof(server_un);
}
else
-#endif
{
- domain = PF_INET;
+ int domain;
- /* Get the server address from the host name */
+#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
+ if (ctx->unix_socket_path != NULL)
+ {
+ domain = PF_LOCAL;
+ }
+ else
+#endif
+ {
+ domain = PF_INET;
+ }
- server_in.sin_family = AF_INET;
- server_in.sin_port = htons(ws->port);
- ret = wget_gethostip(ws->hostname, &server_in.sin_addr);
- if (ret < 0)
+ /* Create a socket */
+
+ conn->sockfd = socket(domain, SOCK_STREAM, 0);
+ if (conn->sockfd < 0)
{
- /* Could not resolve host (or malformed IP address) */
+ ret = -errno;
+ nerr("ERROR: socket failed: %d\n", errno);
+ goto errout_with_errno;
+ }
- nwarn("WARNING: Failed to resolve hostname\n");
- free(ws);
- return -EHOSTUNREACH;
+ ws->need_conn_close = true;
+
+ if ((ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0)
+ {
+ int flags = fcntl(conn->sockfd, F_GETFL, 0);
+ ret = fcntl(conn->sockfd, F_SETFL, flags | O_NONBLOCK);
+ if (ret == -1)
+ {
+ ret = -errno;
+ nerr("ERROR: F_SETFL failed: %d\n", ret);
+ goto errout_with_errno;
+ }
}
+ else
+ {
+ /* Set send and receive timeout values */
+
+ tv.tv_sec = ctx->timeout_sec;
+ tv.tv_usec = 0;
- server_address = (const struct sockaddr *)&server_in;
- server_address_len = sizeof(struct sockaddr_in);
+ setsockopt(conn->sockfd, SOL_SOCKET, SO_RCVTIMEO,
+ (FAR const void *)&tv, sizeof(struct timeval));
+ setsockopt(conn->sockfd, SOL_SOCKET, SO_SNDTIMEO,
+ (FAR const void *)&tv, sizeof(struct timeval));
+ }
}
- /* Create a socket */
+ ws->state = WEBCLIENT_STATE_CONNECT;
+ }
- conn.sockfd = socket(domain, SOCK_STREAM, 0);
- if (conn.sockfd < 0)
+ if (ws->state == WEBCLIENT_STATE_CONNECT)
+ {
+ if (conn->tls)
{
- /* socket failed. It will set the errno appropriately */
+ char port_str[sizeof("65535")];
- nerr("ERROR: socket failed: %d\n", errno);
- free(ws);
- return -errno;
+#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
+ if (ctx->unix_socket_path != NULL)
+ {
+ nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n");
+ free(ws);
+ return -ENOTSUP;
+ }
+#endif
+
+ snprintf(port_str, sizeof(port_str), "%u", ws->port);
+ ret = tls_ops->connect(tls_ctx, ws->hostname, port_str,
+ ctx->timeout_sec,
+ &conn->tls_conn);
+ if (ret == 0)
+ {
+ ws->need_conn_close = true;
+ }
}
+ else
+ {
+#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
+ struct sockaddr_un server_un;
+#endif
+ struct sockaddr_in server_in;
+ const struct sockaddr *server_address;
+ socklen_t server_address_len;
+
+#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
+ if (ctx->unix_socket_path != NULL)
+ {
+ memset(&server_un, 0, sizeof(server_un));
+ server_un.sun_family = AF_LOCAL;
+ strncpy(server_un.sun_path, ctx->unix_socket_path,
+ sizeof(server_un.sun_path));
+#if !defined(__NuttX__) && !defined(__linux__)
+ server_un.sun_len = SUN_LEN(&server_un);
+#endif
+ server_address = (const struct sockaddr *)&server_un;
+ server_address_len = sizeof(server_un);
+ }
+ else
+#endif
+ {
+ /* Get the server address from the host name */
- /* Set send and receive timeout values */
+ server_in.sin_family = AF_INET;
+ server_in.sin_port = htons(ws->port);
+ ret = wget_gethostip(ws->hostname, &server_in.sin_addr);
+ if (ret < 0)
+ {
+ /* Could not resolve host (or malformed IP address) */
- tv.tv_sec = ctx->timeout_sec;
- tv.tv_usec = 0;
+ nwarn("WARNING: Failed to resolve hostname\n");
+ free(ws);
+ return -EHOSTUNREACH;
+ }
- setsockopt(conn.sockfd, SOL_SOCKET, SO_RCVTIMEO,
- (FAR const void *)&tv, sizeof(struct timeval));
- setsockopt(conn.sockfd, SOL_SOCKET, SO_SNDTIMEO,
- (FAR const void *)&tv, sizeof(struct timeval));
+ server_address = (const struct sockaddr *)&server_in;
+ server_address_len = sizeof(struct sockaddr_in);
+ }
- /* Connect to server. First we have to set some fields in the
- * 'server' address structure. The system will assign me an
- * arbitrary local port that is not in use.
- */
+ /* Connect to server. First we have to set some fields in the
+ * 'server' address structure. The system will assign me an
+ * arbitrary local port that is not in use.
+ */
- ret = connect(conn.sockfd, server_address, server_address_len);
- if (ret == -1)
+ ret = connect(conn->sockfd, server_address,
+ server_address_len);
+ if (ret == -1)
+ {
+ ret = -errno;
+ DEBUGASSERT(ret < 0);
+ if (ret == -EISCONN)
+ {
+ ret = 0;
+ }
+ }
+ }
+
+ if (ret < 0)
{
- ret = -errno;
- close(conn.sockfd);
+ nerr("ERROR: connect failed: %d\n", errno);
+ goto errout_with_errno;
}
- }
- if (ret < 0)
- {
- nerr("ERROR: connect failed: %d\n", errno);
- free(ws);
- return ret;
+ ws->state = WEBCLIENT_STATE_PREPARE_REQUEST;
}
- /* Send the request */
+ if (ws->state == WEBCLIENT_STATE_PREPARE_REQUEST)
+ {
+ /* Send the request */
- dest = ws->buffer;
- ep = ws->buffer + ws->buflen;
- dest = append(dest, ep, method);
- dest = append(dest, ep, " ");
+ dest = ws->buffer;
+ ep = ws->buffer + ws->buflen;
+ dest = append(dest, ep, method);
+ dest = append(dest, ep, " ");
#ifndef WGET_USE_URLENCODE
- dest = append(dest, ep, ws->filename);
+ dest = append(dest, ep, ws->filename);
#else
- /* TODO: should we use wget_urlencode_strcpy? */
+ /* TODO: should we use wget_urlencode_strcpy? */
- dest = append(dest, ep, ws->filename);
+ dest = append(dest, ep, ws->filename);
#endif
- dest = append(dest, ep, " ");
- dest = append(dest, ep, g_http10);
- dest = append(dest, ep, g_httpcrnl);
- dest = append(dest, ep, g_httphost);
- dest = append(dest, ep, ws->hostname);
- dest = append(dest, ep, g_httpcrnl);
-
- for (i = 0; i < ctx->nheaders; i++)
- {
- dest = append(dest, ep, ctx->headers[i]);
+ dest = append(dest, ep, " ");
+ dest = append(dest, ep, g_http10);
+ dest = append(dest, ep, g_httpcrnl);
+ dest = append(dest, ep, g_httphost);
+ dest = append(dest, ep, ws->hostname);
dest = append(dest, ep, g_httpcrnl);
- }
- if (ctx->bodylen)
- {
- char post_size[sizeof("18446744073709551615")];
+ for (i = 0; i < ctx->nheaders; i++)
+ {
+ dest = append(dest, ep, ctx->headers[i]);
+ dest = append(dest, ep, g_httpcrnl);
+ }
- dest = append(dest, ep, g_httpcontsize);
- sprintf(post_size, "%zu", ctx->bodylen);
- dest = append(dest, ep, post_size);
- dest = append(dest, ep, g_httpcrnl);
- }
+ if (ctx->bodylen)
+ {
+ char post_size[sizeof("18446744073709551615")];
- dest = append(dest, ep, g_httpuseragentfields);
+ dest = append(dest, ep, g_httpcontsize);
+ sprintf(post_size, "%zu", ctx->bodylen);
+ dest = append(dest, ep, post_size);
+ dest = append(dest, ep, g_httpcrnl);
+ }
- if (dest == NULL)
- {
- ret = -E2BIG;
- goto errout_with_errno;
+ dest = append(dest, ep, g_httpuseragentfields);
+
+ if (dest == NULL)
+ {
+ ret = -E2BIG;
+ goto errout_with_errno;
+ }
+
+ len = dest - ws->buffer;
+
+ ws->state = WEBCLIENT_STATE_SEND_REQUEST;
+ ws->state_offset = 0;
+ ws->state_len = len;
}
- len = dest - ws->buffer;
+ if (ws->state == WEBCLIENT_STATE_SEND_REQUEST)
+ {
+ ssize_t ssz;
- ret = conn_send_all(ctx, &conn, ws->buffer, len);
+ ssz = conn_send(ctx, conn,
+ ws->buffer + ws->state_offset,
+ ws->state_len);
+ if (ssz < 0)
+ {
+ ret = ssz;
+ nerr("ERROR: send failed: %d\n", -ret);
+ goto errout_with_errno;
+ }
- if (ret >= 0)
+ if (ssz >= 0)
+ {
+ ws->state_offset += ssz;
+ ws->state_len -= ssz;
+ if (ws->state_len == 0)
+ {
+ ws->state = WEBCLIENT_STATE_SEND_REQUEST_BODY;
+ ws->state_offset = 0;
+ ws->state_len = ctx->bodylen;
+ ws->data_buffer = NULL;
+ ninfo("Sending %zu bytes request body\n", ws->state_len);
+ }
+ }
+ }
+
+ if (ws->state == WEBCLIENT_STATE_SEND_REQUEST_BODY)
{
- size_t sent = 0;
+ /* In this state,
+ *
+ * ws->data_buffer the data given by the user
+ * ws->data_offset the byte offset in the entire body
+ * ws->data_len the byte size of the data in ws->data_buffer
+ * ws->state_offset the send offset in ws->data_buffer
+ * ws->state_len the number of remaining bytes to send (total)
+ */
- while (sent < ctx->bodylen)
+ if (ws->state_len == 0)
+ {
+ ninfo("Finished sending request body\n");
+ ws->state = WEBCLIENT_STATE_STATUSLINE;
+ }
+ else if (ws->data_buffer == NULL)
{
FAR const void *input_buffer;
size_t input_buffer_size = ws->buflen;
- size_t todo = ctx->bodylen - sent;
+ size_t todo = ws->state_len;
if (input_buffer_size > todo)
{
input_buffer_size = todo;
@@ -996,131 +1124,184 @@ int webclient_perform(FAR struct webclient_context *ctx)
goto errout_with_errno;
}
- ret = conn_send_all(ctx, &conn, input_buffer,
- input_buffer_size);
- if (ret < 0)
+ ninfo("Got %zu bytes body chunk / total remaining %zu bytes\n",
+ input_buffer_size, ws->state_len);
+ ws->data_buffer = input_buffer;
+ ws->data_len = input_buffer_size;
+ ws->state_offset = 0;
+ }
+ else
+ {
+ size_t bytes_to_send = ws->data_len - ws->state_offset;
+
+ DEBUGASSERT(bytes_to_send <= ws->state_len);
+ ssize_t ssz = conn_send(ctx, conn,
+ ws->data_buffer + ws->state_offset,
+ bytes_to_send);
+ if (ssz < 0)
{
- break;
+ ret = ssz;
+ nerr("ERROR: send failed: %d\n", -ret);
+ goto errout_with_errno;
}
- sent += input_buffer_size;
+ ninfo("Sent %zd bytes request body at offset %ju "
+ "in the %zu bytes chunk, "
+ "total remaining %zu bytes\n",
+ ssz,
+ (uintmax_t)ws->state_offset,
+ ws->data_len,
+ ws->state_len);
+ ws->state_len -= ssz;
+ ws->state_offset += ssz;
+ DEBUGASSERT(ws->state_offset <= ws->data_len);
+ if (ws->state_offset == ws->data_len)
+ {
+ ws->data_buffer = NULL;
+ }
}
}
- if (ret < 0)
- {
- nerr("ERROR: send failed: %d\n", -ret);
- goto errout_with_errno;
- }
-
/* Now loop to get the file sent in response to the GET. This
* loop continues until either we read the end of file (nbytes == 0)
* or until we detect that we have been redirected.
*/
- ws->state = WEBCLIENT_STATE_STATUSLINE;
- redirected = false;
- for (; ; )
+ if (ws->state == WEBCLIENT_STATE_STATUSLINE ||
+ ws->state == WEBCLIENT_STATE_HEADERS ||
+ ws->state == WEBCLIENT_STATE_DATA)
{
- ws->datend = conn_recv(ctx, &conn, ws->buffer, ws->buflen);
- if (ws->datend < 0)
+ for (; ; )
{
- ret = ws->datend;
- nerr("ERROR: recv failed: %d\n", -ret);
- goto errout_with_errno;
- }
- else if (ws->datend == 0)
- {
- if (ws->state != WEBCLIENT_STATE_DATA)
+ ws->datend = conn_recv(ctx, conn, ws->buffer, ws->buflen);
+ if (ws->datend < 0)
{
- nerr("Connection lost unexpectedly\n");
- ret = -ECONNABORTED;
+ ret = ws->datend;
+ nerr("ERROR: recv failed: %d\n", -ret);
goto errout_with_errno;
}
+ else if (ws->datend == 0)
+ {
+ if (ws->state != WEBCLIENT_STATE_DATA)
+ {
+ nerr("Connection lost unexpectedly\n");
+ ret = -ECONNABORTED;
+ goto errout_with_errno;
+ }
- ninfo("Connection lost\n");
- break;
- }
+ ninfo("Connection lost\n");
+ ws->state = WEBCLIENT_STATE_CLOSE;
+ ws->redirected = 0;
+ break;
+ }
- /* Handle initial parsing of the status line */
+ /* Handle initial parsing of the status line */
- ws->offset = 0;
- if (ws->state == WEBCLIENT_STATE_STATUSLINE)
- {
- ret = wget_parsestatus(ctx, ws);
- if (ret < 0)
+ ws->offset = 0;
+ if (ws->state == WEBCLIENT_STATE_STATUSLINE)
{
- goto errout_with_errno;
+ ret = wget_parsestatus(ctx, ws);
+ if (ret < 0)
+ {
+ goto errout_with_errno;
+ }
}
- }
- /* Parse the HTTP data */
+ /* Parse the HTTP data */
- if (ws->state == WEBCLIENT_STATE_HEADERS)
- {
- ret = wget_parseheaders(ctx, ws);
- if (ret < 0)
+ if (ws->state == WEBCLIENT_STATE_HEADERS)
{
- goto errout_with_errno;
+ ret = wget_parseheaders(ctx, ws);
+ if (ret < 0)
+ {
+ goto errout_with_errno;
+ }
}
- }
- /* Dispose of the data payload */
+ /* Dispose of the data payload */
- if (ws->state == WEBCLIENT_STATE_DATA)
- {
- if (ws->httpstatus != HTTPSTATUS_MOVED)
+ if (ws->state == WEBCLIENT_STATE_DATA)
{
- /* Let the client decide what to do with the
- * received file.
- */
-
- if (ws->offset == ws->datend)
+ if (ws->httpstatus != HTTPSTATUS_MOVED)
{
- /* We don't have data to give to the client yet. */
- }
- else if (ctx->sink_callback)
- {
- ret = ctx->sink_callback(&ws->buffer, ws->offset,
- ws->datend, &ws->buflen,
- ctx->sink_callback_arg);
- if (ret != 0)
+ /* Let the client decide what to do with the
+ * received file.
+ */
+
+ if (ws->offset == ws->datend)
{
- goto errout_with_errno;
+ /* We don't have data to give to the client yet. */
+ }
+ else if (ctx->sink_callback)
+ {
+ ret = ctx->sink_callback(&ws->buffer, ws->offset,
+ ws->datend, &ws->buflen,
+ ctx->sink_callback_arg);
+ if (ret != 0)
+ {
+ goto errout_with_errno;
+ }
+ }
+ else
+ {
+ ctx->callback(&ws->buffer, ws->offset, ws->datend,
+ &ws->buflen, ctx->sink_callback_arg);
}
}
else
{
- ctx->callback(&ws->buffer, ws->offset, ws->datend,
- &ws->buflen, ctx->sink_callback_arg);
- }
- }
- else
- {
- redirected = true;
- nredirect++;
- if (nredirect > CONFIG_WEBCLIENT_MAX_REDIRECT)
- {
- nerr("ERROR: too many redirects (%u)\n", nredirect);
- goto errout;
- }
+ ws->nredirect++;
+ if (ws->nredirect > CONFIG_WEBCLIENT_MAX_REDIRECT)
+ {
+ nerr("ERROR: too many redirects (%u)\n",
+ ws->nredirect);
+ ret = -ELOOP;
+ goto errout_with_errno;
+ }
- break;
+ ws->state = WEBCLIENT_STATE_CLOSE;
+ ws->redirected = 1;
+ break;
+ }
}
}
}
- conn_close(ctx, &conn);
+ if (ws->state == WEBCLIENT_STATE_CLOSE)
+ {
+ conn_close(ctx, conn);
+ ws->need_conn_close = false;
+ if (ws->redirected)
+ {
+ ws->state = WEBCLIENT_STATE_SOCKET;
+ }
+ else
+ {
+ ws->state = WEBCLIENT_STATE_DONE;
+ }
+ }
}
- while (redirected);
+ while (ws->state != WEBCLIENT_STATE_DONE);
free(ws);
return OK;
-errout:
- ret = -errno;
errout_with_errno:
- conn_close(ctx, &conn);
+ if ((ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0 &&
+ (ret == -EAGAIN || ret == -EINPROGRESS || ret == -EALREADY))
+ {
+ if (ret == -EINPROGRESS || ret == -EALREADY)
+ {
+ conn->flags |= CONN_WANT_WRITE;
+ }
+
+ return -EAGAIN;
+ }
+
+ if (ws->need_conn_close)
+ {
+ conn_close(ctx, conn);
+ }
free(ws);
return ret;
@@ -1131,6 +1312,38 @@ errout_with_errno:
****************************************************************************/
/****************************************************************************
+ * Name: webclient_abort
+ *
+ * Description:
+ * This function is used to free the resources used by webclient_perform()
+ * in case of non blocking I/O.
+ *
+ * After webclient_perform() returned -EAGAIN, the application can either
+ * retry the operation by calling webclient_perform() again or abort
+ * the operation by calling this function.
+ *
+ ****************************************************************************/
+
+void webclient_abort(FAR struct webclient_context *ctx)
+{
+ struct wget_s *ws = ctx->ws;
+
+ if (ws == NULL)
+ {
+ return;
+ }
+
+ if (ws->need_conn_close)
+ {
+ struct conn *conn = &ws->conn;
+
+ conn_close(ctx, conn);
+ }
+
+ free(ws);
+}
+
+/****************************************************************************
* Name: web_post_str
****************************************************************************/
@@ -1304,3 +1517,79 @@ void webclient_set_static_body(FAR struct webclient_context *ctx,
ctx->body_callback_arg = (void *)body; /* discard const */
ctx->bodylen = bodylen;
}
+
+/****************************************************************************
+ * Name: webclient_get_poll_info
+ *
+ * Description:
+ * This function retrieves the information necessary
+ * to wait for events when using non blocking I/O.
+ *
+ * When using WEBCLIENT_FLAG_NON_BLOCKING, webclient_perform() can
+ * return -EAGAIN when it would otherwise block for I/O.
+ * In that case, the application can use this function to
+ * get the information necessary to wait for the I/O events
+ * using poll()/select(), by populating the the given
+ * webclient_poll_info structure with the information.
+ *
+ * The following is an example to use this function to handle EAGAIN.
+ *
+ * retry:
+ * ret = webclient_perform(&ctx);
+ * if (ret == -EAGAIN)
+ * {
+ * struct webclient_poll_info info;
+ * struct pollfd pfd;
+ *
+ * ret = webclient_get_poll_info(&ctx, &info);
+ * if (ret != 0)
+ * {
+ * ...
+ * }
+ *
+ * memset(&pfd, 0, sizeof(pfd));
+ * pfd.fd = info.fd;
+ * if ((info.flags & WEBCLIENT_POLL_INFO_WANT_READ) != 0)
+ * {
+ * pfd.events |= POLLIN;
+ * }
+ *
+ * if ((info.flags & WEBCLIENT_POLL_INFO_WANT_WRITE) != 0)
+ * {
+ * pfd.events |= POLLOUT;
+ * }
+ *
+ * ret = poll(&pfd, 1, -1);
+ * if (ret != 0)
+ * {
+ * ...
+ * }
+ *
+ * goto retry;
+ * }
+ *
+ ****************************************************************************/
+
+int webclient_get_poll_info(FAR struct webclient_context *ctx,
+ FAR struct webclient_poll_info *info)
+{
+ struct wget_s *ws;
+ struct conn *conn;
+
+ ws = ctx->ws;
+ if (ws == NULL)
+ {
+ return -EINVAL;
+ }
+
+ conn = &ws->conn;
+ if (conn->tls)
+ {
+ return ctx->tls_ops->get_poll_info(ctx->tls_ctx, conn->tls_conn, info);
+ }
+
+ info->fd = conn->sockfd;
+ info->flags = conn->flags & (CONN_WANT_READ | CONN_WANT_WRITE);
+ conn->flags &= ~(CONN_WANT_READ | CONN_WANT_WRITE);
+ return 0;
+}