diff --git a/include/netutils/webclient.h b/include/netutils/webclient.h index 5f5c8ceff..eea1de8ee 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 19d66ee35..85b791f2a 100644 --- a/netutils/webclient/webclient.c +++ b/netutils/webclient/webclient.c @@ -53,6 +53,8 @@ #include #include +#include +#include #include #include #include @@ -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; } @@ -224,33 +265,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; + + /* 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; + } + + ws->state = WEBCLIENT_STATE_SOCKET; + ctx->ws = ws; } - ws->buffer = ctx->buffer; - ws->buflen = ctx->buflen; - - /* 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; - } + 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) + if (ws->state == WEBCLIENT_STATE_SOCKET) { - conn.tls = true; - } - else if (!strcmp(ws->scheme, "http")) - { - 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) { - nerr("ERROR: TLS on AF_LOCAL socket is not implemented\n"); + conn->tls = true; + } + else if (!strcmp(ws->scheme, "http")) + { + 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; - -#if defined(CONFIG_WEBCLIENT_NET_LOCAL) - if (ctx->unix_socket_path != NULL) - { - domain = PF_LOCAL; - - 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 - { - domain = PF_INET; - - /* Get the server address from the host name */ - - 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) */ - - nwarn("WARNING: Failed to resolve hostname\n"); - free(ws); - return -EHOSTUNREACH; - } - - server_address = (const struct sockaddr *)&server_in; - server_address_len = sizeof(struct sockaddr_in); - } - - /* Create a socket */ - - conn.sockfd = socket(domain, SOCK_STREAM, 0); - if (conn.sockfd < 0) - { - /* socket failed. It will set the errno appropriately */ - - nerr("ERROR: socket failed: %d\n", errno); - free(ws); - return -errno; - } - - /* Set send and receive timeout values */ - - tv.tv_sec = ctx->timeout_sec; - tv.tv_usec = 0; - - 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)); - - /* 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. + /* 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. */ - ret = connect(conn.sockfd, server_address, server_address_len); - if (ret == -1) + ws->httpstatus = HTTPSTATUS_NONE; + ws->offset = 0; + ws->datend = 0; + ws->ndx = 0; + ws->redirected = 0; + + if (conn->tls) { - ret = -errno; - close(conn.sockfd); +#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 + } + else + { + int domain; + +#if defined(CONFIG_WEBCLIENT_NET_LOCAL) + if (ctx->unix_socket_path != NULL) + { + domain = PF_LOCAL; + } + else +#endif + { + domain = PF_INET; + } + + /* Create a socket */ + + conn->sockfd = socket(domain, SOCK_STREAM, 0); + if (conn->sockfd < 0) + { + ret = -errno; + nerr("ERROR: socket failed: %d\n", errno); + goto errout_with_errno; + } + + 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; + + 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)); + } + } + + ws->state = WEBCLIENT_STATE_CONNECT; + } + + if (ws->state == WEBCLIENT_STATE_CONNECT) + { + if (conn->tls) + { + char port_str[sizeof("65535")]; + +#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 */ + + 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) */ + + nwarn("WARNING: Failed to resolve hostname\n"); + free(ws); + return -EHOSTUNREACH; + } + + 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. + */ + + 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) + { + nerr("ERROR: connect failed: %d\n", errno); + goto errout_with_errno; + } + + ws->state = WEBCLIENT_STATE_PREPARE_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, " "); + +#ifndef WGET_USE_URLENCODE + dest = append(dest, ep, ws->filename); +#else + /* TODO: should we use wget_urlencode_strcpy? */ + + 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, g_httpcrnl); + } + + if (ctx->bodylen) + { + char post_size[sizeof("18446744073709551615")]; + + dest = append(dest, ep, g_httpcontsize); + sprintf(post_size, "%zu", ctx->bodylen); + dest = append(dest, ep, post_size); + dest = append(dest, ep, g_httpcrnl); + } + + 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; + } + + if (ws->state == WEBCLIENT_STATE_SEND_REQUEST) + { + ssize_t ssz; + + 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 (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 (ret < 0) + if (ws->state == WEBCLIENT_STATE_SEND_REQUEST_BODY) { - nerr("ERROR: connect failed: %d\n", errno); - free(ws); - return ret; - } + /* 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) + */ - /* Send the request */ - - 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); -#else - /* TODO: should we use wget_urlencode_strcpy? */ - - 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, g_httpcrnl); - } - - if (ctx->bodylen) - { - char post_size[sizeof("18446744073709551615")]; - - dest = append(dest, ep, g_httpcontsize); - sprintf(post_size, "%zu", ctx->bodylen); - dest = append(dest, ep, post_size); - dest = append(dest, ep, g_httpcrnl); - } - - dest = append(dest, ep, g_httpuseragentfields); - - if (dest == NULL) - { - ret = -E2BIG; - goto errout_with_errno; - } - - len = dest - ws->buffer; - - ret = conn_send_all(ctx, &conn, ws->buffer, len); - - if (ret >= 0) - { - size_t sent = 0; - - 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; } - - ninfo("Connection lost\n"); - break; - } - - /* Handle initial parsing of the status line */ - - ws->offset = 0; - if (ws->state == WEBCLIENT_STATE_STATUSLINE) - { - ret = wget_parsestatus(ctx, ws); - if (ret < 0) + else if (ws->datend == 0) { - goto errout_with_errno; - } - } - - /* Parse the HTTP data */ - - if (ws->state == WEBCLIENT_STATE_HEADERS) - { - ret = wget_parseheaders(ctx, ws); - if (ret < 0) - { - goto errout_with_errno; - } - } - - /* Dispose of the data payload */ - - if (ws->state == WEBCLIENT_STATE_DATA) - { - if (ws->httpstatus != HTTPSTATUS_MOVED) - { - /* Let the client decide what to do with the - * received file. - */ - - if (ws->offset == ws->datend) + if (ws->state != WEBCLIENT_STATE_DATA) { - /* We don't have data to give to the client yet. */ + nerr("Connection lost unexpectedly\n"); + ret = -ECONNABORTED; + goto errout_with_errno; } - else if (ctx->sink_callback) + + ninfo("Connection lost\n"); + ws->state = WEBCLIENT_STATE_CLOSE; + ws->redirected = 0; + break; + } + + /* Handle initial parsing of the status line */ + + ws->offset = 0; + if (ws->state == WEBCLIENT_STATE_STATUSLINE) + { + ret = wget_parsestatus(ctx, ws); + if (ret < 0) { - ret = ctx->sink_callback(&ws->buffer, ws->offset, - ws->datend, &ws->buflen, - ctx->sink_callback_arg); - if (ret != 0) + goto errout_with_errno; + } + } + + /* Parse the HTTP data */ + + if (ws->state == WEBCLIENT_STATE_HEADERS) + { + ret = wget_parseheaders(ctx, ws); + if (ret < 0) + { + goto errout_with_errno; + } + } + + /* Dispose of the data payload */ + + if (ws->state == WEBCLIENT_STATE_DATA) + { + if (ws->httpstatus != HTTPSTATUS_MOVED) + { + /* 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; @@ -1130,6 +1311,38 @@ errout_with_errno: * Public Functions ****************************************************************************/ +/**************************************************************************** + * 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; +}