webclient improvements

Highlights:

* TLS support (a hook to allow users to provide TLS implementation)
* ability to add extra request headers
* ability to use PUT method
* ability to report http status
* error handling improvements

Proposed on the ML while ago:
https://www.mail-archive.com/dev@nuttx.apache.org/msg03803.html

The original API is kept for now.
I plan to remove them after adapting the existing users.
(examples in this repo)
This commit is contained in:
YAMAMOTO Takashi 2020-07-12 08:54:28 +09:00 committed by Xiang Xiao
parent 07f2318fae
commit a1db71fa43
2 changed files with 565 additions and 147 deletions

View File

@ -103,6 +103,140 @@
typedef void (*wget_callback_t)(FAR char **buffer, int offset,
int datend, FAR int *buflen, FAR void *arg);
/* webclient_sink_callback_t: callback to consume data
*
* Same as wget_callback_t, but allowed to fail.
*
* Input Parameters:
* Same as wget_callback_t
*
* Return value:
* 0 on success.
* A negative errno on error.
*/
typedef CODE int (*webclient_sink_callback_t)(FAR char **buffer, int offset,
int datend, FAR int *buflen,
FAR void *arg);
/* webclient_body_callback_t: a callback to provide request body
*
* This callback can be called multiple times to provide
* webclient_context::bodylen bytes of the data.
* It's the responsibility of this callback to maintain the current
* "offset" of the data. (similarly to fread(3))
*
* An implementation of this callback should perform either of
* the followings:
*
* - fill the buffer (specified by buffer and *sizep) with the data
* - update *datap with a buffer filled with the data
*
* Either ways, it should update *sizep to the size of the data.
*
* A short result is allowed. In that case, this callback will be called
* again to provide the remaining data.
*
* Input Parameters:
* buffer - The buffer to fill.
* sizep - The size of buffer/data in bytes.
* datap - The data to return.
* ctx - The value of webclient_context::body_callback_arg.
*
* Return value:
* 0 on success.
* A negative errno on error.
*/
typedef CODE int (*webclient_body_callback_t)(
FAR void *buffer,
FAR size_t *sizep,
FAR const void * FAR *datap,
FAR void *ctx);
struct webclient_tls_connection;
struct webclient_tls_ops
{
CODE int (*connect)(FAR void *ctx,
FAR const char *hostname, FAR const char *port,
unsigned int timeout_second,
FAR struct webclient_tls_connection **connp);
CODE ssize_t (*send)(FAR void *ctx,
FAR struct webclient_tls_connection *conn,
FAR const void *buf, size_t len);
CODE ssize_t (*recv)(FAR void *ctx,
FAR struct webclient_tls_connection *conn,
FAR void *buf, size_t len);
CODE int (*close)(FAR void *ctx,
FAR struct webclient_tls_connection *conn);
};
struct webclient_context
{
/* request parameters
*
* method - HTTP method like "GET", "POST".
* The default value is "GET".
* url - A pointer to a string containing the full URL.
* (e.g., http://www.nutt.org/index.html, or
* http://192.168.23.1:80/index.html)
* headers - An array of pointers to the extra headers.
* nheaders - The number of elements in the "headers" array.
* bodylen - The size of the request body.
*/
FAR const char *method;
FAR const char *url;
FAR const char * FAR const *headers;
unsigned int nheaders;
size_t bodylen;
/* other parameters
*
* buffer - A user provided buffer to receive the file data
* (also used for the outgoing GET request)
* It should be large enough to hold the whole
* request header. It should also be large enough
* to hold the whole response header.
* buflen - The size of the user provided buffer
* sink_callback - As data is obtained from the host, this function
* is to dispose of each block of file data as it is
* received.
* callback - a compat version of sink_callback.
* sink_callback_arg - User argument passed to callback.
* body_callback - A callback function to provide the request body.
* body_callback_arg - User argument passed to body_callback.
* 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.
*/
FAR char *buffer;
int buflen;
wget_callback_t callback;
webclient_sink_callback_t sink_callback;
FAR void *sink_callback_arg;
webclient_body_callback_t body_callback;
FAR void *body_callback_arg;
FAR const struct webclient_tls_ops *tls_ops;
FAR void *tls_ctx;
/* results
*
* http_status - HTTP status code
* http_reason - A buffer to store HTTP reason phrase.
* If NULL, the reason phrase will be discarded.
* http_reason_len - The size of the http_reason buffer
* A reason phrase longer than the buffer size
* will be silently truncated.
*/
unsigned int http_status;
FAR char *http_reason;
size_t http_reason_len;
};
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
@ -153,6 +287,12 @@ int wget(FAR const char *url, FAR char *buffer, int buflen,
int wget_post(FAR const char *url, FAR const char *posts, FAR char *buffer,
int buflen, wget_callback_t callback, FAR void *arg);
void webclient_set_defaults(FAR struct webclient_context *ctx);
int webclient_perform(FAR struct webclient_context *ctx);
void webclient_set_static_body(FAR struct webclient_context *ctx,
FAR const void *body,
size_t bodylen);
#undef EXTERN
#ifdef __cplusplus
}

View File

@ -158,6 +158,13 @@ struct wget_s
char filename[CONFIG_WEBCLIENT_MAXFILENAME];
};
struct conn
{
bool tls;
int sockfd;
struct webclient_tls_connection *tls_conn;
};
/****************************************************************************
* Private Data
****************************************************************************/
@ -169,8 +176,6 @@ static const char g_httpcontenttype[] = "content-type: ";
#endif
static const char g_httphost[] = "host: ";
static const char g_httplocation[] = "location: ";
static const char g_httpget[] = "GET ";
static const char g_httppost[] = "POST ";
static const char g_httpuseragentfields[] =
"Connection: close\r\n"
@ -178,10 +183,6 @@ static const char g_httpuseragentfields[] =
CONFIG_NSH_WGET_USERAGENT
"\r\n\r\n";
static const char g_http200[] = "200 ";
static const char g_http301[] = "301 ";
static const char g_http302[] = "302 ";
static const char g_httpcrnl[] = "\r\n";
static const char g_httpform[] = "Content-Type: "
@ -196,10 +197,137 @@ static const char g_httpcache[] = "Cache-Control: no-cache";
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: conn_send
****************************************************************************/
static ssize_t conn_send(struct webclient_context *ctx, struct conn *conn,
const void *buffer, size_t len)
{
if (conn->tls)
{
return ctx->tls_ops->send(ctx->tls_ctx, conn->tls_conn, buffer, len);
}
else
{
ssize_t ret = send(conn->sockfd, buffer, len, 0);
if (ret == -1)
{
return -errno;
}
return ret;
}
}
/****************************************************************************
* 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
****************************************************************************/
static ssize_t conn_recv(struct webclient_context *ctx, struct conn *conn,
void *buffer, size_t len)
{
if (conn->tls)
{
return ctx->tls_ops->recv(ctx->tls_ctx, conn->tls_conn, buffer, len);
}
else
{
ssize_t ret = recv(conn->sockfd, buffer, len, 0);
if (ret == -1)
{
return -errno;
}
return ret;
}
}
/****************************************************************************
* Name: conn_close
****************************************************************************/
static void conn_close(struct webclient_context *ctx, struct conn *conn)
{
if (conn->tls)
{
ctx->tls_ops->close(ctx->tls_ctx, conn->tls_conn);
}
else
{
close(conn->sockfd);
}
}
/****************************************************************************
* Name: webclient_static_body_func
****************************************************************************/
static int webclient_static_body_func(FAR void *buffer,
FAR size_t *sizep,
FAR const void * FAR *datap,
FAR void *ctx)
{
*datap = ctx;
return 0;
}
/****************************************************************************
* Name: append
****************************************************************************/
static char *append(char *dest, char *ep, const char *src)
{
int len;
if (dest == NULL)
{
return NULL;
}
len = strlen(src);
if (ep - dest < len + 1)
{
return NULL;
}
memcpy(dest, src, len);
dest[len] = '\0';
return dest + len;
}
/****************************************************************************
* Name: wget_strcpy
****************************************************************************/
#ifdef WGET_USE_URLENCODE
static char *wget_strcpy(char *dest, const char *src)
{
int len = strlen(src);
@ -208,6 +336,7 @@ static char *wget_strcpy(char *dest, const char *src)
dest[len] = '\0';
return dest + len;
}
#endif
/****************************************************************************
* Name: wget_urlencode_strcpy
@ -229,7 +358,8 @@ static char *wget_urlencode_strcpy(char *dest, const char *src)
* Name: wget_parsestatus
****************************************************************************/
static inline int wget_parsestatus(struct wget_s *ws)
static inline int wget_parsestatus(struct webclient_context *ctx,
struct wget_s *ws)
{
int offset;
int ndx;
@ -247,22 +377,50 @@ static inline int wget_parsestatus(struct wget_s *ws)
if ((strncmp(ws->line, g_http10, strlen(g_http10)) == 0) ||
(strncmp(ws->line, g_http11, strlen(g_http11)) == 0))
{
unsigned long http_status;
char *ep;
dest = &(ws->line[9]);
ws->httpstatus = HTTPSTATUS_NONE;
/* Check for 200 OK */
errno = 0;
http_status = strtoul(dest, &ep, 10);
if (ep == dest)
{
return -EINVAL;
}
if (strncmp(dest, g_http200, strlen(g_http200)) == 0)
if (*ep != ' ')
{
return -EINVAL;
}
if (errno != 0)
{
return -errno;
}
ctx->http_status = http_status;
if (ctx->http_reason != NULL)
{
strncpy(ctx->http_reason,
ep + 1,
ctx->http_reason_len - 1);
ctx->http_reason[ctx->http_reason_len - 1] = 0;
}
/* Check for 2xx (Successful) */
if ((http_status / 100) == 2)
{
ws->httpstatus = HTTPSTATUS_OK;
}
/* Check for 301 Moved permanently or 302 Found.
/* Check for 3xx (Redirection)
* Location: header line will contain the new location.
*/
else if (strncmp(dest, g_http301, strlen(g_http301)) == 0 ||
strncmp(dest, g_http302, strlen(g_http302)) == 0)
else if ((http_status / 100) == 3)
{
ws->httpstatus = HTTPSTATUS_MOVED;
}
@ -315,7 +473,14 @@ static int parseurl(FAR const char *url, FAR struct wget_s *ws)
if (url_s.port == 0)
{
ws->port = 80;
if (!strcmp(ws->scheme, "https"))
{
ws->port = 443;
}
else
{
ws->port = 80;
}
}
else
{
@ -472,32 +637,15 @@ static int wget_gethostip(FAR char *hostname, FAR struct in_addr *dest)
}
/****************************************************************************
* Name: wget_base
*
* Description:
* Obtain the requested file from an HTTP server using the GET method.
*
* Input Parameters
* url - A pointer to a string containing either the full URL to
* the file to get (e.g., http://www.nutt.org/index.html, or
* http://192.168.23.1:80/index.html).
* buffer - A user provided buffer to receive the file data (also
* used for the outgoing GET request
* buflen - The size of the user provided buffer
* callback - As data is obtained from the host, this function is
* to dispose of each block of file data as it is received.
* arg - User argument passed to callback.
* mode - Indicates GET or POST modes
* Name: webclient_perform
*
* Returned Value:
* 0: if the GET operation completed successfully;
* -1: On a failure with errno set appropriately
* 0: if the operation completed successfully;
* Negative errno: On a failure
*
****************************************************************************/
static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
wget_callback_t callback, FAR void *arg,
FAR const char *posts, uint8_t mode)
int webclient_perform(FAR struct webclient_context *ctx)
{
struct sockaddr_in server;
struct wget_s *ws;
@ -505,7 +653,12 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
bool redirected;
unsigned int nredirect = 0;
char *dest;
int sockfd;
char *ep;
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;
unsigned int i;
int len;
int ret;
@ -514,23 +667,22 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
ws = calloc(1, sizeof(struct wget_s));
if (!ws)
{
return ERROR;
return -errno;
}
ws->buffer = buffer;
ws->buflen = buflen;
ws->buffer = ctx->buffer;
ws->buflen = ctx->buflen;
/* Parse the hostname (with optional port number) and filename
* from the URL.
*/
ret = parseurl(url, ws);
ret = parseurl(ctx->url, ws);
if (ret != 0)
{
nwarn("WARNING: Malformed URL: %s\n", url);
nwarn("WARNING: Malformed URL: %s\n", ctx->url);
free(ws);
errno = -ret;
return ERROR;
return ret;
}
ninfo("hostname='%s' filename='%s'\n", ws->hostname, ws->filename);
@ -539,11 +691,19 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
do
{
if (strcmp(ws->scheme, "http"))
if (!strcmp(ws->scheme, "https") && tls_ops != NULL)
{
conn.tls = true;
}
else if (!strcmp(ws->scheme, "http"))
{
conn.tls = false;
}
else
{
nerr("ERROR: unsupported scheme: %s\n", ws->scheme);
free(ws);
return ERROR;
return -ENOTSUP;
}
/* Re-initialize portions of the state structure that could have
@ -556,119 +716,164 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
ws->datend = 0;
ws->ndx = 0;
/* Create a socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
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 ERROR;
}
/* Set send and receive timeout values */
tv.tv_sec = CONFIG_WEBCLIENT_TIMEOUT;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (FAR const void *)&tv,
sizeof(struct timeval));
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (FAR const void *)&tv,
sizeof(struct timeval));
/* Get the server address from the host name */
server.sin_family = AF_INET;
server.sin_port = htons(ws->port);
ret = wget_gethostip(ws->hostname, &server.sin_addr);
if (ret < 0)
{
/* Could not resolve host (or malformed IP address) */
nwarn("WARNING: Failed to resolve hostname\n");
ret = -EHOSTUNREACH;
goto errout_with_errno;
}
/* 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(sockfd, (struct sockaddr *)&server,
sizeof(struct sockaddr_in));
if (ret < 0)
{
nerr("ERROR: connect failed: %d\n", errno);
goto errout;
}
/* Send the GET request */
dest = ws->buffer;
if (mode == WGET_MODE_POST)
{
dest = wget_strcpy(dest, g_httppost);
snprintf(port_str, sizeof(port_str), "%u", ws->port);
ret = tls_ops->connect(tls_ctx, ws->hostname, port_str,
CONFIG_WEBCLIENT_TIMEOUT, &conn.tls_conn);
}
else
{
dest = wget_strcpy(dest, g_httpget);
/* Create a socket */
conn.sockfd = socket(AF_INET, 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 = CONFIG_WEBCLIENT_TIMEOUT;
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));
/* Get the server address from the host name */
server.sin_family = AF_INET;
server.sin_port = htons(ws->port);
ret = wget_gethostip(ws->hostname, &server.sin_addr);
if (ret < 0)
{
/* Could not resolve host (or malformed IP address) */
nwarn("WARNING: Failed to resolve hostname\n");
ret = -EHOSTUNREACH;
goto errout_with_errno;
}
/* 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, (struct sockaddr *)&server,
sizeof(struct sockaddr_in));
if (ret == -1)
{
ret = -errno;
}
}
if (ret < 0)
{
nerr("ERROR: connect failed: %d\n", errno);
free(ws);
return ret;
}
/* 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 = wget_strcpy(dest, ws->filename);
dest = append(dest, ep, ws->filename);
#else
/* TODO: should we use wget_urlencode_strcpy? */
dest = wget_strcpy(dest, ws->filename);
dest = append(dest, ep, ws->filename);
#endif
*dest++ = ISO_SPACE;
dest = wget_strcpy(dest, g_http10);
dest = wget_strcpy(dest, g_httpcrnl);
dest = wget_strcpy(dest, g_httphost);
dest = wget_strcpy(dest, ws->hostname);
dest = wget_strcpy(dest, g_httpcrnl);
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 (mode == WGET_MODE_POST)
for (i = 0; i < ctx->nheaders; i++)
{
int post_len;
char post_size[8];
dest = wget_strcpy(dest, g_httpform);
dest = wget_strcpy(dest, g_httpcrnl);
dest = wget_strcpy(dest, g_httpcontsize);
/* Post content size */
post_len = strlen((char *)posts);
sprintf(post_size, "%d", post_len);
dest = wget_strcpy(dest, post_size);
dest = wget_strcpy(dest, g_httpcrnl);
dest = append(dest, ep, ctx->headers[i]);
dest = append(dest, ep, g_httpcrnl);
}
dest = wget_strcpy(dest, g_httpuseragentfields);
if (mode == WGET_MODE_POST)
if (ctx->bodylen)
{
dest = wget_strcpy(dest, (char *)posts);
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);
}
len = dest - buffer;
dest = append(dest, ep, g_httpuseragentfields);
do
if (dest == NULL)
{
ret = send(sockfd, buffer + ((dest - buffer) - len), len, 0);
if (ret < 0)
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)
{
nerr("ERROR: send failed: %d\n", errno);
goto errout;
}
FAR const void *input_buffer;
size_t input_buffer_size = ws->buflen;
len -= ret;
size_t todo = ctx->bodylen - sent;
if (input_buffer_size > todo)
{
input_buffer_size = todo;
}
input_buffer = ws->buffer;
ret = ctx->body_callback(ws->buffer,
&input_buffer_size,
&input_buffer,
ctx->body_callback_arg);
if (ret < 0)
{
nerr("ERROR: body_callback failed: %d\n", -ret);
goto errout_with_errno;
}
ret = conn_send_all(ctx, &conn, input_buffer,
input_buffer_size);
if (ret < 0)
{
break;
}
sent += input_buffer_size;
}
}
if (ret < 0)
{
nerr("ERROR: send failed: %d\n", -ret);
goto errout_with_errno;
}
while (len > 0);
/* 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)
@ -679,16 +884,16 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
redirected = false;
for (; ; )
{
ws->datend = recv(sockfd, ws->buffer, ws->buflen, 0);
ws->datend = conn_recv(ctx, &conn, ws->buffer, ws->buflen);
if (ws->datend < 0)
{
nerr("ERROR: recv failed: %d\n", errno);
goto errout;
ret = ws->datend;
nerr("ERROR: recv failed: %d\n", -ret);
goto errout_with_errno;
}
else if (ws->datend == 0)
{
ninfo("Connection lost\n");
close(sockfd);
break;
}
@ -697,7 +902,7 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
ws->offset = 0;
if (ws->state == WEBCLIENT_STATE_STATUSLINE)
{
ret = wget_parsestatus(ws);
ret = wget_parsestatus(ctx, ws);
if (ret < 0)
{
goto errout_with_errno;
@ -725,8 +930,21 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
* received file.
*/
callback(&ws->buffer, ws->offset, ws->datend,
&buflen, arg);
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
{
@ -738,23 +956,25 @@ static int wget_base(FAR const char *url, FAR char *buffer, int buflen,
goto errout;
}
close(sockfd);
break;
}
}
}
conn_close(ctx, &conn);
}
while (redirected);
free(ws);
return OK;
errout_with_errno:
errno = -ret;
errout:
close(sockfd);
ret = -errno;
errout_with_errno:
conn_close(ctx, &conn);
free(ws);
return ERROR;
return ret;
}
/****************************************************************************
@ -863,7 +1083,23 @@ int web_posts_strlen(FAR char **name, FAR char **value, int len)
int wget(FAR const char *url, FAR char *buffer, int buflen,
wget_callback_t callback, FAR void *arg)
{
return wget_base(url, buffer, buflen, callback, arg, NULL, WGET_MODE_GET);
struct webclient_context ctx;
int ret;
webclient_set_defaults(&ctx);
ctx.method = "GET";
ctx.url = url;
ctx.buffer = buffer;
ctx.buflen = buflen;
ctx.callback = callback;
ctx.sink_callback_arg = arg;
ret = webclient_perform(&ctx);
if (ret < 0)
{
errno = -ret;
return ERROR;
}
return OK;
}
/****************************************************************************
@ -873,6 +1109,48 @@ int wget(FAR const char *url, FAR char *buffer, int buflen,
int wget_post(FAR const char *url, FAR const char *posts, FAR char *buffer,
int buflen, wget_callback_t callback, FAR void *arg)
{
return wget_base(url, buffer, buflen, callback, arg, posts,
WGET_MODE_POST);
static const char *headers = g_httpform;
struct webclient_context ctx;
int ret;
webclient_set_defaults(&ctx);
ctx.method = "POST";
ctx.url = url;
ctx.buffer = buffer;
ctx.buflen = buflen;
ctx.callback = callback;
ctx.sink_callback_arg = arg;
ctx.headers = &headers;
ctx.nheaders = 1;
webclient_set_static_body(&ctx, posts, strlen(posts));
ret = webclient_perform(&ctx);
if (ret < 0)
{
errno = -ret;
return ERROR;
}
return OK;
}
/****************************************************************************
* Name: webclient_set_defaults
****************************************************************************/
void webclient_set_defaults(FAR struct webclient_context *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->method = "GET";
}
/****************************************************************************
* Name: webclient_set_static_body
****************************************************************************/
void webclient_set_static_body(FAR struct webclient_context *ctx,
FAR const void *body,
size_t bodylen)
{
ctx->body_callback = webclient_static_body_func;
ctx->body_callback_arg = (void *)body; /* discard const */
ctx->bodylen = bodylen;
}