You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
9.2 KiB
400 lines
9.2 KiB
/*
|
|
* lws-api-test-lws_sequencer
|
|
*
|
|
* Written in 2019 by Andy Green <andy@warmcat.com>
|
|
*
|
|
* This file is made available under the Creative Commons CC0 1.0
|
|
* Universal Public Domain Dedication.
|
|
*
|
|
* This api test uses the lws_sequencer api to make five http client requests
|
|
* to libwebsockets.org in sequence, from inside the event loop. The fourth
|
|
* fourth http client request is directed to port 22 where it stalls
|
|
* triggering the lws_sequencer timeout flow. The fifth is given a nonexistant
|
|
* dns name and is expected to fail.
|
|
*/
|
|
|
|
#include <libwebsockets.h>
|
|
|
|
#include <signal.h>
|
|
|
|
static int interrupted, test_good = 0;
|
|
|
|
enum {
|
|
SEQ1,
|
|
SEQ2,
|
|
SEQ3_404,
|
|
SEQ4_TIMEOUT, /* we expect to timeout */
|
|
SEQ5_BAD_ADDRESS /* we expect the connection to fail */
|
|
};
|
|
|
|
/*
|
|
* This is the user defined struct whose space is allocated along with the
|
|
* sequencer when that is created.
|
|
*
|
|
* You'd put everything your sequencer needs to do its job in here.
|
|
*/
|
|
|
|
struct myseq {
|
|
struct lws_vhost *vhost;
|
|
struct lws *cwsi; /* client wsi for current step if any */
|
|
|
|
int state; /* which test we're on */
|
|
int http_resp;
|
|
};
|
|
|
|
/* sequencer messages specific to this sequencer */
|
|
|
|
enum {
|
|
SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
|
|
SEQ_MSG_CLIENT_DONE,
|
|
};
|
|
|
|
/* this is the sequence of GETs we will do */
|
|
|
|
static const char *url_paths[] = {
|
|
"https://libwebsockets.org/index.html",
|
|
"https://libwebsockets.org/lws.css",
|
|
"https://libwebsockets.org/404.html",
|
|
"https://libwebsockets.org:22", /* this causes us to time out */
|
|
"https://doesntexist.invalid/" /* fail early in connect */
|
|
};
|
|
|
|
|
|
static void
|
|
sigint_handler(int sig)
|
|
{
|
|
interrupted = 1;
|
|
}
|
|
|
|
/*
|
|
* This is the sequencer-aware http protocol handler. It monitors the client
|
|
* http action and queues messages for the sequencer when something definitive
|
|
* happens.
|
|
*/
|
|
|
|
static int
|
|
callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
|
void *in, size_t len)
|
|
{
|
|
struct myseq *s = (struct myseq *)user;
|
|
int seq_msg = SEQ_MSG_CLIENT_FAILED;
|
|
|
|
switch (reason) {
|
|
|
|
/* because we are protocols[0] ... */
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n",
|
|
in ? (char *)in : "(null)");
|
|
goto notify;
|
|
|
|
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
|
|
if (!s)
|
|
return 1;
|
|
s->http_resp = lws_http_client_http_response(wsi);
|
|
lwsl_info("Connected with server response: %d\n", s->http_resp);
|
|
break;
|
|
|
|
/* chunks of chunked content, with header removed */
|
|
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
|
|
lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
|
|
#if 0 /* enable to dump the html */
|
|
{
|
|
const char *p = in;
|
|
|
|
while (len--)
|
|
if (*p < 0x7f)
|
|
putchar(*p++);
|
|
else
|
|
putchar('.');
|
|
}
|
|
#endif
|
|
return 0; /* don't passthru */
|
|
|
|
/* uninterpreted http content */
|
|
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
|
|
{
|
|
char buffer[1024 + LWS_PRE];
|
|
char *px = buffer + LWS_PRE;
|
|
int lenx = sizeof(buffer) - LWS_PRE;
|
|
|
|
if (lws_http_client_read(wsi, &px, &lenx) < 0)
|
|
return -1;
|
|
}
|
|
return 0; /* don't passthru */
|
|
|
|
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
|
|
lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n",
|
|
wsi);
|
|
if (!s)
|
|
return 1;
|
|
/*
|
|
* We got a definitive transaction completion
|
|
*/
|
|
seq_msg = SEQ_MSG_CLIENT_DONE;
|
|
goto notify;
|
|
|
|
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
|
|
lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
|
|
if (!s)
|
|
return 1;
|
|
|
|
lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n",
|
|
__func__, wsi);
|
|
goto notify;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return lws_callback_http_dummy(wsi, reason, user, in, len);
|
|
|
|
notify:
|
|
/*
|
|
* We only inform the sequencer of a definitive outcome for our step.
|
|
*
|
|
* So once we have informed it, we detach ourselves from the sequencer
|
|
* and the sequencer from ourselves. Wsi may want to live on but after
|
|
* we got our result and moved on to the next test or completed, the
|
|
* sequencer doesn't want to hear from it again.
|
|
*/
|
|
if (!s)
|
|
return 1;
|
|
|
|
lws_set_wsi_user(wsi, NULL);
|
|
s->cwsi = NULL;
|
|
lws_seq_queue_event(lws_seq_from_user(s), seq_msg,
|
|
NULL, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct lws_protocols protocols[] = {
|
|
{ "seq-test-http", callback_http, 0, 0, },
|
|
{ NULL, NULL, 0, 0 }
|
|
};
|
|
|
|
|
|
static int
|
|
sequencer_start_client(struct myseq *s)
|
|
{
|
|
struct lws_client_connect_info i;
|
|
const char *prot, *path1;
|
|
char uri[128], path[128];
|
|
int n;
|
|
|
|
lws_strncpy(uri, url_paths[s->state], sizeof(uri));
|
|
|
|
memset(&i, 0, sizeof i);
|
|
i.context = lws_seq_get_context(lws_seq_from_user(s));
|
|
|
|
if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) {
|
|
lwsl_err("%s: uri error %s\n", __func__, uri);
|
|
}
|
|
|
|
if (!strcmp(prot, "https"))
|
|
i.ssl_connection = LCCSCF_USE_SSL;
|
|
|
|
path[0] = '/';
|
|
n = 1;
|
|
if (path1[0] == '/')
|
|
n = 0;
|
|
lws_strncpy(&path[n], path1, sizeof(path) - 1);
|
|
|
|
i.path = path;
|
|
i.host = i.address;
|
|
i.origin = i.address;
|
|
i.method = "GET";
|
|
i.vhost = s->vhost;
|
|
i.userdata = s;
|
|
|
|
i.protocol = protocols[0].name;
|
|
i.local_protocol_name = protocols[0].name;
|
|
i.pwsi = &s->cwsi;
|
|
|
|
if (!lws_client_connect_via_info(&i)) {
|
|
lwsl_notice("%s: connecting to %s://%s:%d%s failed\n",
|
|
__func__, prot, i.address, i.port, path);
|
|
|
|
/* we couldn't even get started with the client connection */
|
|
|
|
lws_seq_queue_event(lws_seq_from_user(s),
|
|
SEQ_MSG_CLIENT_FAILED, NULL, NULL);
|
|
|
|
return 1;
|
|
}
|
|
|
|
lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC);
|
|
|
|
lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__,
|
|
s->cwsi, prot, i.address, i.port, path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The sequencer callback handles queued sequencer messages in the order they
|
|
* were queued. The messages are presented from the event loop thread context
|
|
* even if they were queued from a different thread.
|
|
*/
|
|
|
|
static lws_seq_cb_return_t
|
|
sequencer_cb(struct lws_sequencer *seq, void *user, int event,
|
|
void *data, void *aux)
|
|
{
|
|
struct myseq *s = (struct myseq *)user;
|
|
|
|
switch ((int)event) {
|
|
case LWSSEQ_CREATED: /* our sequencer just got started */
|
|
s->state = SEQ1; /* first thing we'll do is the first url */
|
|
goto step;
|
|
|
|
case LWSSEQ_DESTROYED:
|
|
/*
|
|
* This sequencer is about to be destroyed. If we have any
|
|
* other assets in play, detach them from us.
|
|
*/
|
|
if (s->cwsi)
|
|
lws_set_wsi_user(s->cwsi, NULL);
|
|
|
|
interrupted = 1;
|
|
break;
|
|
|
|
case LWSSEQ_TIMED_OUT: /* current step timed out */
|
|
if (s->state == SEQ4_TIMEOUT) {
|
|
lwsl_user("%s: test %d got expected timeout\n",
|
|
__func__, s->state);
|
|
goto done;
|
|
}
|
|
lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
|
|
return LWSSEQ_RET_DESTROY;
|
|
|
|
case SEQ_MSG_CLIENT_FAILED:
|
|
if (s->state == SEQ5_BAD_ADDRESS) {
|
|
/*
|
|
* in this specific case, we expect to fail
|
|
*/
|
|
lwsl_user("%s: test %d failed as expected\n",
|
|
__func__, s->state);
|
|
goto done;
|
|
}
|
|
|
|
lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
|
|
|
|
return LWSSEQ_RET_DESTROY;
|
|
|
|
case SEQ_MSG_CLIENT_DONE:
|
|
if (s->state >= SEQ4_TIMEOUT) {
|
|
/*
|
|
* In these specific cases, done would be a failure,
|
|
* we expected to timeout or fail
|
|
*/
|
|
lwsl_user("%s: seq failed at step %d\n", __func__,
|
|
s->state);
|
|
|
|
return LWSSEQ_RET_DESTROY;
|
|
}
|
|
lwsl_user("%s: seq done step %d (resp %d)\n", __func__,
|
|
s->state, s->http_resp);
|
|
|
|
done:
|
|
lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
|
|
s->state++;
|
|
if (s->state == LWS_ARRAY_SIZE(url_paths)) {
|
|
/* the sequence has completed */
|
|
lwsl_user("%s: sequence completed OK\n", __func__);
|
|
|
|
test_good = 1;
|
|
|
|
return LWSSEQ_RET_DESTROY;
|
|
}
|
|
|
|
step:
|
|
sequencer_start_client(s);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return LWSSEQ_RET_CONTINUE;
|
|
}
|
|
|
|
int
|
|
main(int argc, const char **argv)
|
|
{
|
|
int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
|
|
struct lws_context_creation_info info;
|
|
struct lws_context *context;
|
|
struct lws_sequencer *seq;
|
|
struct lws_vhost *vh;
|
|
lws_seq_info_t i;
|
|
struct myseq *s;
|
|
const char *p;
|
|
|
|
/* the normal lws init */
|
|
|
|
signal(SIGINT, sigint_handler);
|
|
|
|
if ((p = lws_cmdline_option(argc, argv, "-d")))
|
|
logs = atoi(p);
|
|
|
|
lws_set_log_level(logs, NULL);
|
|
lwsl_user("LWS API selftest: lws_sequencer\n");
|
|
|
|
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
|
|
info.port = CONTEXT_PORT_NO_LISTEN;
|
|
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
|
|
LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
|
|
info.protocols = protocols;
|
|
|
|
#if defined(LWS_WITH_MBEDTLS)
|
|
/*
|
|
* OpenSSL uses the system trust store. mbedTLS has to be told which
|
|
* CA to trust explicitly.
|
|
*/
|
|
info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
|
|
#endif
|
|
|
|
context = lws_create_context(&info);
|
|
if (!context) {
|
|
lwsl_err("lws init failed\n");
|
|
return 1;
|
|
}
|
|
|
|
vh = lws_create_vhost(context, &info);
|
|
if (!vh) {
|
|
lwsl_err("Failed to create first vhost\n");
|
|
goto bail1;
|
|
}
|
|
|
|
/*
|
|
* Create the sequencer... when the event loop starts, it will
|
|
* receive the LWSSEQ_CREATED callback
|
|
*/
|
|
|
|
memset(&i, 0, sizeof(i));
|
|
i.context = context;
|
|
i.user_size = sizeof(struct myseq);
|
|
i.puser = (void **)&s;
|
|
i.cb = sequencer_cb;
|
|
i.name = "seq";
|
|
|
|
seq = lws_seq_create(&i);
|
|
if (!seq) {
|
|
lwsl_err("%s: unable to create sequencer\n", __func__);
|
|
goto bail1;
|
|
}
|
|
s->vhost = vh;
|
|
|
|
/* the usual lws event loop */
|
|
|
|
while (n >= 0 && !interrupted)
|
|
n = lws_service(context, 0);
|
|
|
|
bail1:
|
|
lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
|
|
|
|
lws_context_destroy(context);
|
|
|
|
return !test_good;
|
|
}
|