2 * lws-api-test-lws_sequencer
4 * Written in 2019 by Andy Green <andy@warmcat.com>
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
9 * This api test uses the lws_sequencer api to make five http client requests
10 * to libwebsockets.org in sequence, from inside the event loop. The fourth
11 * fourth http client request is directed to port 22 where it stalls
12 * triggering the lws_sequencer timeout flow. The fifth is given a nonexistant
13 * dns name and is expected to fail.
16 #include <libwebsockets.h>
20 static int interrupted, test_good = 0;
26 SEQ4_TIMEOUT, /* we expect to timeout */
27 SEQ5_BAD_ADDRESS /* we expect the connection to fail */
31 * This is the user defined struct whose space is allocated along with the
32 * sequencer when that is created.
34 * You'd put everything your sequencer needs to do its job in here.
38 struct lws_vhost *vhost;
39 struct lws *cwsi; /* client wsi for current step if any */
41 int state; /* which test we're on */
45 /* sequencer messages specific to this sequencer */
48 SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
52 /* this is the sequence of GETs we will do */
54 static const char *url_paths[] = {
55 "https://libwebsockets.org/index.html",
56 "https://libwebsockets.org/lws.css",
57 "https://libwebsockets.org/404.html",
58 "https://libwebsockets.org:22", /* this causes us to time out */
59 "https://doesntexist.invalid/" /* fail early in connect */
64 sigint_handler(int sig)
70 * This is the sequencer-aware http protocol handler. It monitors the client
71 * http action and queues messages for the sequencer when something definitive
76 callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
79 struct myseq *s = (struct myseq *)user;
80 int seq_msg = SEQ_MSG_CLIENT_FAILED;
84 /* because we are protocols[0] ... */
85 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
86 lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n",
87 in ? (char *)in : "(null)");
90 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
93 s->http_resp = lws_http_client_http_response(wsi);
94 lwsl_info("Connected with server response: %d\n", s->http_resp);
97 /* chunks of chunked content, with header removed */
98 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
99 lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
100 #if 0 /* enable to dump the html */
111 return 0; /* don't passthru */
113 /* uninterpreted http content */
114 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
116 char buffer[1024 + LWS_PRE];
117 char *px = buffer + LWS_PRE;
118 int lenx = sizeof(buffer) - LWS_PRE;
120 if (lws_http_client_read(wsi, &px, &lenx) < 0)
123 return 0; /* don't passthru */
125 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
126 lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n",
131 * We got a definitive transaction completion
133 seq_msg = SEQ_MSG_CLIENT_DONE;
136 case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
137 lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
141 lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n",
149 return lws_callback_http_dummy(wsi, reason, user, in, len);
153 * We only inform the sequencer of a definitive outcome for our step.
155 * So once we have informed it, we detach ourselves from the sequencer
156 * and the sequencer from ourselves. Wsi may want to live on but after
157 * we got our result and moved on to the next test or completed, the
158 * sequencer doesn't want to hear from it again.
163 lws_set_wsi_user(wsi, NULL);
165 lws_seq_queue_event(lws_seq_from_user(s), seq_msg,
171 static const struct lws_protocols protocols[] = {
172 { "seq-test-http", callback_http, 0, 0, },
178 sequencer_start_client(struct myseq *s)
180 struct lws_client_connect_info i;
181 const char *prot, *path1;
182 char uri[128], path[128];
185 lws_strncpy(uri, url_paths[s->state], sizeof(uri));
187 memset(&i, 0, sizeof i);
188 i.context = lws_seq_get_context(lws_seq_from_user(s));
190 if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) {
191 lwsl_err("%s: uri error %s\n", __func__, uri);
194 if (!strcmp(prot, "https"))
195 i.ssl_connection = LCCSCF_USE_SSL;
201 lws_strncpy(&path[n], path1, sizeof(path) - 1);
205 i.origin = i.address;
210 i.protocol = protocols[0].name;
211 i.local_protocol_name = protocols[0].name;
214 if (!lws_client_connect_via_info(&i)) {
215 lwsl_notice("%s: connecting to %s://%s:%d%s failed\n",
216 __func__, prot, i.address, i.port, path);
218 /* we couldn't even get started with the client connection */
220 lws_seq_queue_event(lws_seq_from_user(s),
221 SEQ_MSG_CLIENT_FAILED, NULL, NULL);
226 lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC);
228 lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__,
229 s->cwsi, prot, i.address, i.port, path);
235 * The sequencer callback handles queued sequencer messages in the order they
236 * were queued. The messages are presented from the event loop thread context
237 * even if they were queued from a different thread.
240 static lws_seq_cb_return_t
241 sequencer_cb(struct lws_sequencer *seq, void *user, int event,
242 void *data, void *aux)
244 struct myseq *s = (struct myseq *)user;
246 switch ((int)event) {
247 case LWSSEQ_CREATED: /* our sequencer just got started */
248 s->state = SEQ1; /* first thing we'll do is the first url */
251 case LWSSEQ_DESTROYED:
253 * This sequencer is about to be destroyed. If we have any
254 * other assets in play, detach them from us.
257 lws_set_wsi_user(s->cwsi, NULL);
262 case LWSSEQ_TIMED_OUT: /* current step timed out */
263 if (s->state == SEQ4_TIMEOUT) {
264 lwsl_user("%s: test %d got expected timeout\n",
268 lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
269 return LWSSEQ_RET_DESTROY;
271 case SEQ_MSG_CLIENT_FAILED:
272 if (s->state == SEQ5_BAD_ADDRESS) {
274 * in this specific case, we expect to fail
276 lwsl_user("%s: test %d failed as expected\n",
281 lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
283 return LWSSEQ_RET_DESTROY;
285 case SEQ_MSG_CLIENT_DONE:
286 if (s->state >= SEQ4_TIMEOUT) {
288 * In these specific cases, done would be a failure,
289 * we expected to timeout or fail
291 lwsl_user("%s: seq failed at step %d\n", __func__,
294 return LWSSEQ_RET_DESTROY;
296 lwsl_user("%s: seq done step %d (resp %d)\n", __func__,
297 s->state, s->http_resp);
300 lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
302 if (s->state == LWS_ARRAY_SIZE(url_paths)) {
303 /* the sequence has completed */
304 lwsl_user("%s: sequence completed OK\n", __func__);
308 return LWSSEQ_RET_DESTROY;
312 sequencer_start_client(s);
318 return LWSSEQ_RET_CONTINUE;
322 main(int argc, const char **argv)
324 int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
325 struct lws_context_creation_info info;
326 struct lws_context *context;
328 struct lws_vhost *vh;
333 /* the normal lws init */
335 signal(SIGINT, sigint_handler);
337 if ((p = lws_cmdline_option(argc, argv, "-d")))
340 lws_set_log_level(logs, NULL);
341 lwsl_user("LWS API selftest: lws_sequencer\n");
343 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
344 info.port = CONTEXT_PORT_NO_LISTEN;
345 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
346 LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
347 info.protocols = protocols;
349 #if defined(LWS_WITH_MBEDTLS)
351 * OpenSSL uses the system trust store. mbedTLS has to be told which
352 * CA to trust explicitly.
354 info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
357 context = lws_create_context(&info);
359 lwsl_err("lws init failed\n");
363 vh = lws_create_vhost(context, &info);
365 lwsl_err("Failed to create first vhost\n");
370 * Create the sequencer... when the event loop starts, it will
371 * receive the LWSSEQ_CREATED callback
374 memset(&i, 0, sizeof(i));
376 i.user_size = sizeof(struct myseq);
377 i.puser = (void **)&s;
381 seq = lws_seq_create(&i);
383 lwsl_err("%s: unable to create sequencer\n", __func__);
388 /* the usual lws event loop */
390 while (n >= 0 && !interrupted)
391 n = lws_service(context, 0);
394 lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
396 lws_context_destroy(context);