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)
This commit is contained in:
parent
d4d2f13f89
commit
0bae950b63
@ -77,6 +77,39 @@
|
|||||||
# endif
|
# endif
|
||||||
#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
|
* Public types
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
@ -181,6 +214,7 @@ typedef CODE int (*webclient_body_callback_t)(
|
|||||||
FAR void *ctx);
|
FAR void *ctx);
|
||||||
|
|
||||||
struct webclient_tls_connection;
|
struct webclient_tls_connection;
|
||||||
|
struct webclient_poll_info;
|
||||||
|
|
||||||
struct webclient_tls_ops
|
struct webclient_tls_ops
|
||||||
{
|
{
|
||||||
@ -196,6 +230,9 @@ struct webclient_tls_ops
|
|||||||
FAR void *buf, size_t len);
|
FAR void *buf, size_t len);
|
||||||
CODE int (*close)(FAR void *ctx,
|
CODE int (*close)(FAR void *ctx,
|
||||||
FAR struct webclient_tls_connection *conn);
|
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
|
struct webclient_context
|
||||||
@ -248,6 +285,7 @@ struct webclient_context
|
|||||||
* tls_ops - A vector to implement TLS operations.
|
* tls_ops - A vector to implement TLS operations.
|
||||||
* NULL means no https support.
|
* NULL means no https support.
|
||||||
* tls_ctx - A user pointer to be passed to tls_ops as it is.
|
* tls_ctx - A user pointer to be passed to tls_ops as it is.
|
||||||
|
* flags - OR'ed WEBCLIENT_FLAG_xxx values.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
FAR char *buffer;
|
FAR char *buffer;
|
||||||
@ -261,6 +299,7 @@ struct webclient_context
|
|||||||
FAR void *body_callback_arg;
|
FAR void *body_callback_arg;
|
||||||
FAR const struct webclient_tls_ops *tls_ops;
|
FAR const struct webclient_tls_ops *tls_ops;
|
||||||
FAR void *tls_ctx;
|
FAR void *tls_ctx;
|
||||||
|
unsigned int flags;
|
||||||
|
|
||||||
/* results
|
/* results
|
||||||
*
|
*
|
||||||
@ -275,6 +314,16 @@ struct webclient_context
|
|||||||
unsigned int http_status;
|
unsigned int http_status;
|
||||||
FAR char *http_reason;
|
FAR char *http_reason;
|
||||||
size_t http_reason_len;
|
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);
|
void webclient_set_defaults(FAR struct webclient_context *ctx);
|
||||||
int webclient_perform(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,
|
void webclient_set_static_body(FAR struct webclient_context *ctx,
|
||||||
FAR const void *body,
|
FAR const void *body,
|
||||||
size_t bodylen);
|
size_t bodylen);
|
||||||
|
int webclient_get_poll_info(FAR struct webclient_context *ctx,
|
||||||
|
FAR struct webclient_poll_info *info);
|
||||||
|
|
||||||
#undef EXTERN
|
#undef EXTERN
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -53,6 +53,8 @@
|
|||||||
#include <nuttx/compiler.h>
|
#include <nuttx/compiler.h>
|
||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -111,11 +113,6 @@
|
|||||||
# define CONFIG_WEBCLIENT_MAX_REDIRECT 50
|
# define CONFIG_WEBCLIENT_MAX_REDIRECT 50
|
||||||
#endif
|
#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_NONE 0
|
||||||
#define HTTPSTATUS_OK 1
|
#define HTTPSTATUS_OK 1
|
||||||
#define HTTPSTATUS_MOVED 2
|
#define HTTPSTATUS_MOVED 2
|
||||||
@ -128,15 +125,49 @@
|
|||||||
#define WGET_MODE_GET 0
|
#define WGET_MODE_GET 0
|
||||||
#define WGET_MODE_POST 1
|
#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
|
* 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
|
struct wget_s
|
||||||
{
|
{
|
||||||
/* Internal status */
|
/* Internal status */
|
||||||
|
|
||||||
uint8_t state;
|
enum webclient_state_e state;
|
||||||
uint8_t httpstatus;
|
uint8_t httpstatus;
|
||||||
|
|
||||||
uint16_t port; /* The port number to use in the connection */
|
uint16_t port; /* The port number to use in the connection */
|
||||||
@ -160,13 +191,18 @@ struct wget_s
|
|||||||
char scheme[sizeof("https") + 1];
|
char scheme[sizeof("https") + 1];
|
||||||
char hostname[CONFIG_WEBCLIENT_MAXHOSTNAME];
|
char hostname[CONFIG_WEBCLIENT_MAXHOSTNAME];
|
||||||
char filename[CONFIG_WEBCLIENT_MAXFILENAME];
|
char filename[CONFIG_WEBCLIENT_MAXFILENAME];
|
||||||
};
|
|
||||||
|
|
||||||
struct conn
|
bool need_conn_close;
|
||||||
{
|
struct conn conn;
|
||||||
bool tls;
|
unsigned int nredirect;
|
||||||
int sockfd;
|
int redirected;
|
||||||
struct webclient_tls_connection *tls_conn;
|
|
||||||
|
/* 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);
|
ssize_t ret = send(conn->sockfd, buffer, len, 0);
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
{
|
{
|
||||||
|
if (errno == EAGAIN)
|
||||||
|
{
|
||||||
|
conn->flags |= CONN_WANT_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
return -errno;
|
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
|
* 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);
|
ssize_t ret = recv(conn->sockfd, buffer, len, 0);
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
{
|
{
|
||||||
|
if (errno == EAGAIN)
|
||||||
|
{
|
||||||
|
conn->flags |= CONN_WANT_READ;
|
||||||
|
}
|
||||||
|
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,11 +765,9 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
{
|
{
|
||||||
struct wget_s *ws;
|
struct wget_s *ws;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
bool redirected;
|
|
||||||
unsigned int nredirect = 0;
|
|
||||||
char *dest;
|
char *dest;
|
||||||
char *ep;
|
char *ep;
|
||||||
struct conn conn;
|
struct conn *conn;
|
||||||
FAR const struct webclient_tls_ops *tls_ops = ctx->tls_ops;
|
FAR const struct webclient_tls_ops *tls_ops = ctx->tls_ops;
|
||||||
FAR const char *method = ctx->method;
|
FAR const char *method = ctx->method;
|
||||||
FAR void *tls_ctx = ctx->tls_ctx;
|
FAR void *tls_ctx = ctx->tls_ctx;
|
||||||
@ -760,6 +777,8 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
|
|
||||||
/* Initialize the state structure */
|
/* Initialize the state structure */
|
||||||
|
|
||||||
|
if (ctx->ws == NULL)
|
||||||
|
{
|
||||||
ws = calloc(1, sizeof(struct wget_s));
|
ws = calloc(1, sizeof(struct wget_s));
|
||||||
if (!ws)
|
if (!ws)
|
||||||
{
|
{
|
||||||
@ -781,19 +800,28 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws->state = WEBCLIENT_STATE_SOCKET;
|
||||||
|
ctx->ws = ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws = ctx->ws;
|
||||||
|
|
||||||
ninfo("hostname='%s' filename='%s'\n", ws->hostname, ws->filename);
|
ninfo("hostname='%s' filename='%s'\n", ws->hostname, ws->filename);
|
||||||
|
|
||||||
/* The following sequence may repeat indefinitely if we are redirected */
|
/* The following sequence may repeat indefinitely if we are redirected */
|
||||||
|
|
||||||
|
conn = &ws->conn;
|
||||||
do
|
do
|
||||||
|
{
|
||||||
|
if (ws->state == WEBCLIENT_STATE_SOCKET)
|
||||||
{
|
{
|
||||||
if (!strcmp(ws->scheme, "https") && tls_ops != NULL)
|
if (!strcmp(ws->scheme, "https") && tls_ops != NULL)
|
||||||
{
|
{
|
||||||
conn.tls = true;
|
conn->tls = true;
|
||||||
}
|
}
|
||||||
else if (!strcmp(ws->scheme, "http"))
|
else if (!strcmp(ws->scheme, "http"))
|
||||||
{
|
{
|
||||||
conn.tls = false;
|
conn->tls = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -811,8 +839,77 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
ws->offset = 0;
|
ws->offset = 0;
|
||||||
ws->datend = 0;
|
ws->datend = 0;
|
||||||
ws->ndx = 0;
|
ws->ndx = 0;
|
||||||
|
ws->redirected = 0;
|
||||||
|
|
||||||
if (conn.tls)
|
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
|
||||||
|
}
|
||||||
|
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")];
|
char port_str[sizeof("65535")];
|
||||||
|
|
||||||
@ -827,7 +924,12 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
|
|
||||||
snprintf(port_str, sizeof(port_str), "%u", ws->port);
|
snprintf(port_str, sizeof(port_str), "%u", ws->port);
|
||||||
ret = tls_ops->connect(tls_ctx, ws->hostname, port_str,
|
ret = tls_ops->connect(tls_ctx, ws->hostname, port_str,
|
||||||
ctx->timeout_sec, &conn.tls_conn);
|
ctx->timeout_sec,
|
||||||
|
&conn->tls_conn);
|
||||||
|
if (ret == 0)
|
||||||
|
{
|
||||||
|
ws->need_conn_close = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -835,15 +937,12 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
struct sockaddr_un server_un;
|
struct sockaddr_un server_un;
|
||||||
#endif
|
#endif
|
||||||
struct sockaddr_in server_in;
|
struct sockaddr_in server_in;
|
||||||
int domain;
|
|
||||||
const struct sockaddr *server_address;
|
const struct sockaddr *server_address;
|
||||||
socklen_t server_address_len;
|
socklen_t server_address_len;
|
||||||
|
|
||||||
#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
|
#if defined(CONFIG_WEBCLIENT_NET_LOCAL)
|
||||||
if (ctx->unix_socket_path != NULL)
|
if (ctx->unix_socket_path != NULL)
|
||||||
{
|
{
|
||||||
domain = PF_LOCAL;
|
|
||||||
|
|
||||||
memset(&server_un, 0, sizeof(server_un));
|
memset(&server_un, 0, sizeof(server_un));
|
||||||
server_un.sun_family = AF_LOCAL;
|
server_un.sun_family = AF_LOCAL;
|
||||||
strncpy(server_un.sun_path, ctx->unix_socket_path,
|
strncpy(server_un.sun_path, ctx->unix_socket_path,
|
||||||
@ -857,8 +956,6 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
domain = PF_INET;
|
|
||||||
|
|
||||||
/* Get the server address from the host name */
|
/* Get the server address from the host name */
|
||||||
|
|
||||||
server_in.sin_family = AF_INET;
|
server_in.sin_family = AF_INET;
|
||||||
@ -877,48 +974,35 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
server_address_len = sizeof(struct sockaddr_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
|
/* Connect to server. First we have to set some fields in the
|
||||||
* 'server' address structure. The system will assign me an
|
* 'server' address structure. The system will assign me an
|
||||||
* arbitrary local port that is not in use.
|
* arbitrary local port that is not in use.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ret = connect(conn.sockfd, server_address, server_address_len);
|
ret = connect(conn->sockfd, server_address,
|
||||||
|
server_address_len);
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
{
|
{
|
||||||
ret = -errno;
|
ret = -errno;
|
||||||
close(conn.sockfd);
|
DEBUGASSERT(ret < 0);
|
||||||
|
if (ret == -EISCONN)
|
||||||
|
{
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
nerr("ERROR: connect failed: %d\n", errno);
|
nerr("ERROR: connect failed: %d\n", errno);
|
||||||
free(ws);
|
goto errout_with_errno;
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws->state = WEBCLIENT_STATE_PREPARE_REQUEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws->state == WEBCLIENT_STATE_PREPARE_REQUEST)
|
||||||
|
{
|
||||||
/* Send the request */
|
/* Send the request */
|
||||||
|
|
||||||
dest = ws->buffer;
|
dest = ws->buffer;
|
||||||
@ -967,18 +1051,62 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
|
|
||||||
len = dest - ws->buffer;
|
len = dest - ws->buffer;
|
||||||
|
|
||||||
ret = conn_send_all(ctx, &conn, ws->buffer, len);
|
ws->state = WEBCLIENT_STATE_SEND_REQUEST;
|
||||||
|
ws->state_offset = 0;
|
||||||
|
ws->state_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
if (ret >= 0)
|
if (ws->state == WEBCLIENT_STATE_SEND_REQUEST)
|
||||||
{
|
{
|
||||||
size_t sent = 0;
|
ssize_t ssz;
|
||||||
|
|
||||||
while (sent < ctx->bodylen)
|
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 (ws->state == WEBCLIENT_STATE_SEND_REQUEST_BODY)
|
||||||
|
{
|
||||||
|
/* 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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
FAR const void *input_buffer;
|
||||||
size_t input_buffer_size = ws->buflen;
|
size_t input_buffer_size = ws->buflen;
|
||||||
|
|
||||||
size_t todo = ctx->bodylen - sent;
|
size_t todo = ws->state_len;
|
||||||
if (input_buffer_size > todo)
|
if (input_buffer_size > todo)
|
||||||
{
|
{
|
||||||
input_buffer_size = todo;
|
input_buffer_size = todo;
|
||||||
@ -996,33 +1124,56 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
goto errout_with_errno;
|
goto errout_with_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = conn_send_all(ctx, &conn, input_buffer,
|
ninfo("Got %zu bytes body chunk / total remaining %zu bytes\n",
|
||||||
input_buffer_size);
|
input_buffer_size, ws->state_len);
|
||||||
if (ret < 0)
|
ws->data_buffer = input_buffer;
|
||||||
|
ws->data_len = input_buffer_size;
|
||||||
|
ws->state_offset = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
break;
|
size_t bytes_to_send = ws->data_len - ws->state_offset;
|
||||||
}
|
|
||||||
|
|
||||||
sent += input_buffer_size;
|
DEBUGASSERT(bytes_to_send <= ws->state_len);
|
||||||
}
|
ssize_t ssz = conn_send(ctx, conn,
|
||||||
}
|
ws->data_buffer + ws->state_offset,
|
||||||
|
bytes_to_send);
|
||||||
if (ret < 0)
|
if (ssz < 0)
|
||||||
{
|
{
|
||||||
|
ret = ssz;
|
||||||
nerr("ERROR: send failed: %d\n", -ret);
|
nerr("ERROR: send failed: %d\n", -ret);
|
||||||
goto errout_with_errno;
|
goto errout_with_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Now loop to get the file sent in response to the GET. This
|
/* 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)
|
* loop continues until either we read the end of file (nbytes == 0)
|
||||||
* or until we detect that we have been redirected.
|
* or until we detect that we have been redirected.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ws->state = WEBCLIENT_STATE_STATUSLINE;
|
if (ws->state == WEBCLIENT_STATE_STATUSLINE ||
|
||||||
redirected = false;
|
ws->state == WEBCLIENT_STATE_HEADERS ||
|
||||||
|
ws->state == WEBCLIENT_STATE_DATA)
|
||||||
|
{
|
||||||
for (; ; )
|
for (; ; )
|
||||||
{
|
{
|
||||||
ws->datend = conn_recv(ctx, &conn, ws->buffer, ws->buflen);
|
ws->datend = conn_recv(ctx, conn, ws->buffer, ws->buflen);
|
||||||
if (ws->datend < 0)
|
if (ws->datend < 0)
|
||||||
{
|
{
|
||||||
ret = ws->datend;
|
ret = ws->datend;
|
||||||
@ -1039,6 +1190,8 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ninfo("Connection lost\n");
|
ninfo("Connection lost\n");
|
||||||
|
ws->state = WEBCLIENT_STATE_CLOSE;
|
||||||
|
ws->redirected = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1097,30 +1250,58 @@ int webclient_perform(FAR struct webclient_context *ctx)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
redirected = true;
|
ws->nredirect++;
|
||||||
nredirect++;
|
if (ws->nredirect > CONFIG_WEBCLIENT_MAX_REDIRECT)
|
||||||
if (nredirect > CONFIG_WEBCLIENT_MAX_REDIRECT)
|
|
||||||
{
|
{
|
||||||
nerr("ERROR: too many redirects (%u)\n", nredirect);
|
nerr("ERROR: too many redirects (%u)\n",
|
||||||
goto errout;
|
ws->nredirect);
|
||||||
|
ret = -ELOOP;
|
||||||
|
goto errout_with_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws->state = WEBCLIENT_STATE_CLOSE;
|
||||||
|
ws->redirected = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn_close(ctx, &conn);
|
|
||||||
}
|
}
|
||||||
while (redirected);
|
|
||||||
|
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 (ws->state != WEBCLIENT_STATE_DONE);
|
||||||
|
|
||||||
free(ws);
|
free(ws);
|
||||||
return OK;
|
return OK;
|
||||||
|
|
||||||
errout:
|
|
||||||
ret = -errno;
|
|
||||||
errout_with_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);
|
free(ws);
|
||||||
return ret;
|
return ret;
|
||||||
@ -1130,6 +1311,38 @@ errout_with_errno:
|
|||||||
* Public Functions
|
* 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
|
* 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->body_callback_arg = (void *)body; /* discard const */
|
||||||
ctx->bodylen = bodylen;
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user