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:34 UTC
[incubator-nuttx-apps] branch master updated (d4d2f13 -> b8fd862)
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx-apps.git.
from d4d2f13 Rename CONFIG_LIB_BOARDCTL to CONFIG_BOARDCTL
new 0bae950 webclient: Implement non-blocking I/O
new cb1f7c9 webclient.h: Document webclient_client lifetime
new b8fd862 webclient: Add some assertions on webclient_context state
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
include/netutils/webclient.h | 131 +++++++
netutils/webclient/webclient.c | 868 ++++++++++++++++++++++++++++-------------
2 files changed, 726 insertions(+), 273 deletions(-)
[incubator-nuttx-apps] 01/03: webclient: Implement non-blocking I/O
Posted by xi...@apache.org.
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;
+}
[incubator-nuttx-apps] 03/03: webclient: Add some assertions on
webclient_context state
Posted by xi...@apache.org.
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 b8fd862965b719b75ca1c83cae27f25d89a60eb0
Author: YAMAMOTO Takashi <ya...@midokura.com>
AuthorDate: Fri May 14 09:44:06 2021 +0900
webclient: Add some assertions on webclient_context state
---
include/netutils/webclient.h | 11 +++++++++++
netutils/webclient/webclient.c | 33 +++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+)
diff --git a/include/netutils/webclient.h b/include/netutils/webclient.h
index 94ae5c3..560f04c 100644
--- a/include/netutils/webclient.h
+++ b/include/netutils/webclient.h
@@ -384,6 +384,17 @@ struct webclient_context
size_t http_reason_len;
struct wget_s *ws;
+
+#ifdef CONFIG_DEBUG_ASSERTIONS
+ enum webclient_context_state_e
+ {
+ WEBCLIENT_CONTEXT_STATE_UNINITIALIZED,
+ WEBCLIENT_CONTEXT_STATE_INITIALIZED,
+ WEBCLIENT_CONTEXT_STATE_IN_PROGRESS,
+ WEBCLIENT_CONTEXT_STATE_ABORTED,
+ WEBCLIENT_CONTEXT_STATE_DONE,
+ } state;
+#endif
};
struct webclient_poll_info
diff --git a/netutils/webclient/webclient.c b/netutils/webclient/webclient.c
index 85b791f..629bd40 100644
--- a/netutils/webclient/webclient.c
+++ b/netutils/webclient/webclient.c
@@ -131,6 +131,14 @@
#define CONN_WANT_READ WEBCLIENT_POLL_INFO_WANT_READ
#define CONN_WANT_WRITE WEBCLIENT_POLL_INFO_WANT_WRITE
+#ifdef CONFIG_DEBUG_ASSERTIONS
+#define _CHECK_STATE(ctx, s) DEBUGASSERT((ctx)->state == (s))
+#define _SET_STATE(ctx, s) ctx->state = (s)
+#else
+#define _CHECK_STATE(ctx, s) do {} while (0)
+#define _SET_STATE(ctx, s) do {} while (0)
+#endif
+
/****************************************************************************
* Private Types
****************************************************************************/
@@ -775,6 +783,12 @@ int webclient_perform(FAR struct webclient_context *ctx)
int len;
int ret;
+#ifdef CONFIG_DEBUG_ASSERTIONS
+ DEBUGASSERT(ctx->state == WEBCLIENT_CONTEXT_STATE_INITIALIZED ||
+ (ctx->state == WEBCLIENT_CONTEXT_STATE_IN_PROGRESS &&
+ (ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0));
+#endif
+
/* Initialize the state structure */
if (ctx->ws == NULL)
@@ -782,6 +796,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
ws = calloc(1, sizeof(struct wget_s));
if (!ws)
{
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return -errno;
}
@@ -797,6 +812,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
{
nwarn("WARNING: Malformed URL: %s\n", ctx->url);
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return ret;
}
@@ -827,6 +843,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
{
nerr("ERROR: unsupported scheme: %s\n", ws->scheme);
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return -ENOTSUP;
}
@@ -848,6 +865,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
{
nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n");
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return -ENOTSUP;
}
#endif
@@ -918,6 +936,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
{
nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n");
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return -ENOTSUP;
}
#endif
@@ -967,6 +986,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
nwarn("WARNING: Failed to resolve hostname\n");
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return -EHOSTUNREACH;
}
@@ -1284,6 +1304,7 @@ int webclient_perform(FAR struct webclient_context *ctx)
while (ws->state != WEBCLIENT_STATE_DONE);
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return OK;
errout_with_errno:
@@ -1295,6 +1316,7 @@ errout_with_errno:
conn->flags |= CONN_WANT_WRITE;
}
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_IN_PROGRESS);
return -EAGAIN;
}
@@ -1304,6 +1326,7 @@ errout_with_errno:
}
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_DONE);
return ret;
}
@@ -1326,6 +1349,9 @@ errout_with_errno:
void webclient_abort(FAR struct webclient_context *ctx)
{
+ _CHECK_STATE(ctx, WEBCLIENT_CONTEXT_STATE_IN_PROGRESS);
+ DEBUGASSERT((ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0);
+
struct wget_s *ws = ctx->ws;
if (ws == NULL)
@@ -1341,6 +1367,7 @@ void webclient_abort(FAR struct webclient_context *ctx)
}
free(ws);
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_ABORTED);
}
/****************************************************************************
@@ -1503,6 +1530,7 @@ void webclient_set_defaults(FAR struct webclient_context *ctx)
memset(ctx, 0, sizeof(*ctx));
ctx->method = "GET";
ctx->timeout_sec = CONFIG_WEBCLIENT_TIMEOUT;
+ _SET_STATE(ctx, WEBCLIENT_CONTEXT_STATE_INITIALIZED);
}
/****************************************************************************
@@ -1513,6 +1541,8 @@ void webclient_set_static_body(FAR struct webclient_context *ctx,
FAR const void *body,
size_t bodylen)
{
+ _CHECK_STATE(ctx, WEBCLIENT_CONTEXT_STATE_INITIALIZED);
+
ctx->body_callback = webclient_static_body_func;
ctx->body_callback_arg = (void *)body; /* discard const */
ctx->bodylen = bodylen;
@@ -1576,6 +1606,9 @@ int webclient_get_poll_info(FAR struct webclient_context *ctx,
struct wget_s *ws;
struct conn *conn;
+ _CHECK_STATE(ctx, WEBCLIENT_CONTEXT_STATE_IN_PROGRESS);
+ DEBUGASSERT((ctx->flags & WEBCLIENT_FLAG_NON_BLOCKING) != 0);
+
ws = ctx->ws;
if (ws == NULL)
{
[incubator-nuttx-apps] 02/03: webclient.h: Document
webclient_client lifetime
Posted by xi...@apache.org.
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 cb1f7c9dfe01dee632e8c48be5ae5dc3d43e33dd
Author: YAMAMOTO Takashi <ya...@midokura.com>
AuthorDate: Fri May 14 08:59:29 2021 +0900
webclient.h: Document webclient_client lifetime
---
include/netutils/webclient.h | 68 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/include/netutils/webclient.h b/include/netutils/webclient.h
index eea1de8..94ae5c3 100644
--- a/include/netutils/webclient.h
+++ b/include/netutils/webclient.h
@@ -235,6 +235,74 @@ struct webclient_tls_ops
FAR struct webclient_poll_info *info);
};
+/* Note on webclient_client lifetime
+ *
+ * (uninitialized)
+ * |
+ * webclient_set_defaults
+ * |
+ * v
+ * INITIALIZED
+ * |
+ * | IN-PROGRESS
+ * | |
+ * | +-------------+
+ * | | |
+ * | | webclient_abort
+ * | | |
+ * | | v
+ * | | ABORTED
+ * | |
+ * webclient_perform
+ * |
+ * +---------------+
+ * | |
+ * | non-blocking mode,
+ * | returns -EAGAIN
+ * | |
+ * v v
+ * DONE IN-PROGRESS
+ *
+ * (uninitialized):
+ * After the memory for webclient_context is allocated,
+ * it should be initialized with webclient_set_defaults() before
+ * feeding it to other functions taking a webclient_context.
+ *
+ * webclient_abort() makes the state back to this state.
+ * If the application wants to reuse the context for another request,
+ * it should initialize it with webclient_set_defaults() again.
+ *
+ * INITIALIZED:
+ * After calling webclient_set_defaults(), the application can set up
+ * the request parameters by setting the struct fields before the first
+ * call of webclient_perform().
+ * E.g. url, method, buffers, and callbacks.
+ *
+ * webclient_set_static_body() can only be used in this state.
+ *
+ * IN-PROGRESS:
+ * This state only exists for the non-blocking mode.
+ *
+ * webclient_get_poll_info() can only be used in this state.
+ *
+ * ABORTED:
+ * The HTTP operation has been aborted by webclient_abort().
+ *
+ * DONE:
+ * The HTTP operation has been completed. (Either successfully or not.)
+ *
+ * The application can examine the struct fields to see the result.
+ * E.g. http_status and http_reason.
+ *
+ * ABORTED, DONE:
+ * The application can now dispose the resources associated to the
+ * context.
+ * E.g. buffers
+ *
+ * If the application wants to reuse the context for another request,
+ * it should initialize it with webclient_set_defaults() again.
+ */
+
struct webclient_context
{
/* request parameters