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;
+}