diff --git a/CMakeLists.txt b/CMakeLists.txt
index 09ab766c3..8a37ff9ed 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -909,6 +909,7 @@ if (LWS_WITH_NETWORK)
lib/core-net/network.c
lib/core-net/vhost.c
lib/core-net/pollfd.c
+ lib/core-net/sequencer.c
lib/core-net/service.c
lib/core-net/stats.c
lib/core-net/wsi.c
diff --git a/READMEs/README.lws_sequencer.md b/READMEs/README.lws_sequencer.md
new file mode 100644
index 000000000..d8ae8604b
--- /dev/null
+++ b/READMEs/README.lws_sequencer.md
@@ -0,0 +1,112 @@
+# `lws_sequencer_t` introduction
+
+Often a single network action like a client GET is just part of a
+larger series of actions, perhaps involving different connections.
+
+Since lws operates inside an event loop, if the outer sequencing
+does not, it can be awkward to synchronize these steps with what's
+happening on the network with a particular connection on the event
+loop thread.
+
+
+
+`lws_sequencer_t` provides a generic way to stage multi-step
+operations from inside the event loop. Because it participates
+in the event loop similar to a wsi, it always operates from the
+service thread context and can access structures that share the
+service thread without locking. It can also provide its own
+higher-level timeout handling.
+
+Naturally you can have many of them running in the same event
+loop operating independently.
+
+Sequencers themselves bind to a pt (per-thread) service thread,
+by default there's only one of these and it's the same as saying
+they bind to an `lws_context`. The sequencer callback may create
+wsi which in turn are bound to a vhost, but the sequencer itself
+is above all that.
+
+## Sequencer timeouts
+
+The sequencer additionally maintains its own second-resolution timeout
+checked by lws for the step being sequenced... this is independent of
+any lws wsi timeouts which tend to be set and reset for very short-term
+timeout protection inside one transaction.
+
+The sequencer timeout operates separately and above any wsi timeout, and
+is typically only reset by the sequencer callback when it receives an
+event indicating a step completed or failed, or it sets up the next sequence
+step.
+
+If the sequencer timeout expires, then the sequencer receives a queued
+`LWSSEQ_TIMED_OUT` message informing it, and it can take corrective action
+or schedule a retry of the step. This message is queued and sent normally
+under the service thread context and in order of receipt.
+
+Unlike lws timeouts which force the wsi to close, the sequencer timeout
+only sends the message. This allows the timeout to be used to, eg, wait
+out a retry cooloff period and then start the retry when the
+`LWSSEQ_TIMED_OUT` is received, according to the state of the sequencer.
+
+## Creating an `lws_sequencer_t`
+
+```
+lws_sequencer_t *
+lws_sequencer_create(struct lws_context *context, int tsi, void *user_data,
+ lws_seq_event_cb cb);
+```
+
+When created, in lws the sequencer objects are bound to a 'per-thread',
+which is by default the same as to say bound to the `lws_context`. You
+can tag them with an opaque user data pointer, and they are also bound to
+a user-specified callback which handles sequencer events
+
+```
+typedef int (*lws_seq_event_cb)(struct lws_sequencer *seq, void *user_data,
+ lws_seq_events_t event, void *data);
+```
+
+`lws_sequencer_t` objects are private to lws and opaque to the user. A small
+set of apis lets you perform operations on the pointer returned by the
+create api.
+
+## Queueing events on a sequencer
+
+Each sequencer object can be passed "events", which are held on a per-sequencer
+queue and handled strictly in the order they arrived on subsequent event loops.
+`LWSSEQ_CREATED` and `LWSSEQ_DESTROYED` events are produced by lws reflecting
+the sequencer's lifecycle, but otherwise the event indexes have a user-defined
+meaning and are queued on the sequencer by user code for eventual consumption
+by user code in the sequencer callback.
+
+Pending events are removed from the sequencer queues and sent to the sequencer
+callback from inside the event loop at a rate of one per event loop wait.
+
+## Destroying sequencers
+
+`lws_sequencer_t` objects are cleaned up during context destruction if they are
+still around.
+
+Normally the sequencer callback receives a queued message that
+informs it that it's either failed at the current step, or succeeded and that
+was the last step, and requests that it should be destroyed by returning
+`LWSSEQ_RET_DESTROY` from the sequencer callback.
+
+## Lifecycle considerations
+
+Sequencers may spawn additional assets like client wsi as part of the sequenced
+actions... the lifecycle of the sequencer and the assets overlap but do not
+necessarily depend on each other... that is a wsi created by the sequencer may
+outlive the sequencer.
+
+It's important therefore to detach assets from the sequencer and the sequencer
+from the assets when each step is over and the asset is "out of scope" for the
+sequencer. It doesn't necessarily mean closing the assets, just making sure
+pointers are invalidated. For example, if a client wsi held a pointer to the
+sequencer as its `.user_data`, when the wsi is out of scope for the sequencer
+it can set it to NULL, eg, `lws_set_wsi_user(wsi, NULL);`.
+
+Under some conditions wsi may want to hang around a bit to see if there is a
+subsequent client wsi transaction they can be reused on. They will clean
+themselves up when they time out.
+
diff --git a/doc-assets/lws_sequencer.svg b/doc-assets/lws_sequencer.svg
new file mode 100644
index 000000000..c846ae934
--- /dev/null
+++ b/doc-assets/lws_sequencer.svg
@@ -0,0 +1,955 @@
+
+
+
diff --git a/include/libwebsockets.h b/include/libwebsockets.h
index 6c7f91acf..aa4057868 100644
--- a/include/libwebsockets.h
+++ b/include/libwebsockets.h
@@ -530,6 +530,7 @@ struct lws;
#include
#include
#include
+#include
#include
#include
diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h
index 77328f0d2..614a988fd 100644
--- a/include/libwebsockets/lws-misc.h
+++ b/include/libwebsockets/lws-misc.h
@@ -285,6 +285,9 @@ lws_dll2_clear(struct lws_dll2 *d);
LWS_VISIBLE LWS_EXTERN void
lws_dll2_owner_clear(struct lws_dll2_owner *d);
+void
+lws_dll2_add_before(struct lws_dll2 *d, struct lws_dll2 *after);
+
/*
* these are safe against the current container object getting deleted,
* since the hold his next in a temp and go to that next. ___tmp is
@@ -485,7 +488,7 @@ LWS_VISIBLE LWS_EXTERN void *
lws_wsi_user(struct lws *wsi);
/**
- * lws_wsi_set_user() - set the user data associated with the client connection
+ * lws_set_wsi_user() - set the user data associated with the client connection
* \param wsi: lws connection
* \param user: user data
*
diff --git a/include/libwebsockets/lws-sequencer.h b/include/libwebsockets/lws-sequencer.h
new file mode 100644
index 000000000..0a2219039
--- /dev/null
+++ b/include/libwebsockets/lws-sequencer.h
@@ -0,0 +1,173 @@
+/*
+ * libwebsockets - small server side websockets and web server implementation
+ *
+ * Copyright (C) 2010-2019 Andy Green
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ *
+ * included from libwebsockets.h
+ *
+ * lws_sequencer is intended to help implement sequences that:
+ *
+ * - outlive a single connection lifetime,
+ * - are not associated with a particular protocol,
+ * - are not associated with a particular vhost,
+ * - must receive and issue events inside the event loop
+ *
+ * lws_sequencer-s are bound to a pt (per-thread) which for the default case of
+ * one service thread is the same as binding to an lws_context.
+ */
+
+typedef enum {
+ LWSSEQ_CREATED, /* sequencer created */
+ LWSSEQ_DESTROYED, /* sequencer destroyed */
+ LWSSEQ_TIMED_OUT, /* sequencer timeout */
+
+ LWSSEQ_USER_BASE = 100 /* define your events from here */
+} lws_seq_events_t;
+
+typedef enum lws_seq_cb_return {
+ LWSSEQ_RET_CONTINUE,
+ LWSSEQ_RET_DESTROY
+} lws_seq_cb_return_t;
+
+typedef struct lws_sequencer lws_sequencer_t; /* opaque */
+
+/*
+ * handler for this sequencer. Return 0 if OK else nonzero to destroy the
+ * sequencer. LWSSEQ_DESTROYED will be called back to the handler so it can
+ * close / destroy any private assets associated with the sequence.
+ *
+ * The callback may return either LWSSEQ_RET_CONTINUE for the sequencer to
+ * resume or LWSSEQ_RET_DESTROY to indicate the sequence is finished.
+ *
+ * Event indexes consist of some generic ones but mainly user-defined ones
+ * starting from LWSSEQ_USER_BASE.
+ */
+typedef lws_seq_cb_return_t (*lws_seq_event_cb)(struct lws_sequencer *seq,
+ void *user, int event, void *data);
+
+/**
+ * lws_sequencer_create() - create and bind sequencer to a pt
+ *
+ * \param context: lws_context
+ * \param tsi: thread service index, 0 is safe anything else depends
+ * multiple service threads being set up
+ * \param user_size: size of the additional heap allocation to allocate after
+ * the lws sequencer object to hold user data associated
+ * with the sequence. The start of this extra allocation
+ * is passed to the sequencer callback and in \p *puser
+ * \param puser: pointer to a void * that will be set to the start of the
+ * extra user heap allocation whose size was set by
+ * user_size. The user area pointed to here is all zeroed
+ * after successful sequencer creation.
+ * \param cb: callback for events on this sequencer
+ *
+ * This binds an abstract sequencer to a per-thread (by default, the single
+ * event loop of an lws_context). After the event loop starts, the sequencer
+ * will receive an LWSSEQ_CREATED event on its callback from the event loop
+ * context, where it can begin its sequence flow.
+ *
+ * Lws itself will only call the callback subsequently with LWSSEQ_DESTROYED
+ * when the sequencer is being destroyed.
+ *
+ * pt locking is used to protect the related data structures.
+ */
+LWS_VISIBLE LWS_EXTERN lws_sequencer_t *
+lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,
+ void **puser, lws_seq_event_cb cb);
+
+/**
+ * lws_sequencer_destroy() - destroy the sequencer
+ *
+ * \param seq: pointer to the the opaque sequencer pointer returned by
+ * lws_sequencer_create()
+ *
+ * This proceeds to destroy the sequencer, calling LWSSEQ_DESTROYED and then
+ * freeing the sequencer object itself. The pointed-to seq pointer will be
+ * set to NULL.
+ */
+LWS_VISIBLE LWS_EXTERN void
+lws_sequencer_destroy(lws_sequencer_t **seq);
+
+/**
+ * lws_sequencer_event() - queue an event on the given sequencer
+ *
+ * \param seq: the opaque sequencer pointer returned by lws_sequencer_create()
+ * \param e: the event index to queue
+ * \param data: associated opaque (to lws) data to provide the callback
+ *
+ * This queues the event on a given sequencer. Queued events are delivered one
+ * per sequencer each subsequent time around the event loop, so the cb is called
+ * from the event loop thread context.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data);
+
+/**
+ * lws_sequencer_timeout() - set a timeout by which the sequence must have
+ * completed by a different event or inform the
+ * sequencer
+ *
+ * \param seq: The sequencer to set the timeout on
+ * \param secs: How many seconds in the future to fire the timeout (0 = disable)
+ *
+ * This api allows the sequencer to ask to be informed if it has not completed
+ * or disabled its timeout after secs seconds. Lws will send a LWSSEQ_TIMED_OUT
+ * event to the sequencer if the timeout expires.
+ *
+ * Typically the sequencer sets the timeout when starting a step, then waits to
+ * hear a queued event informing it the step completed or failed. The timeout
+ * provides a way to deal with the case the step neither completed nor failed
+ * within the timeout period.
+ *
+ * Lws wsi timeouts are not really suitable for this since they are focused on
+ * short-term protocol timeout protection and may be set and reset many times
+ * in one transaction. Wsi timeouts also enforce closure of the wsi when they
+ * trigger, sequencer timeouts have no side effect except to queue the
+ * LWSSEQ_TIMED_OUT message and leave it to the sequencer to decide how to
+ * react appropriately.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_sequencer_timeout(lws_sequencer_t *seq, int secs);
+
+/**
+ * lws_sequencer_from_user(): get the lws_sequencer_t pointer from the user ptr
+ *
+ * \param u: the sequencer user allocation returned by lws_sequencer_create() or
+ * provided in the sequencer callback
+ *
+ * This gets the lws_sequencer_t * from the sequencer user allocation pointer.
+ * Actually these are allocated at the same time in one step, with the user
+ * allocation immediately after the lws_sequencer_t, so lws can compute where
+ * the lws_sequencer_t is from having the user allocation pointer. Since the
+ * size of the lws_sequencer_t is unknown to user code, this helper does it for
+ * you.
+ */
+LWS_VISIBLE LWS_EXTERN lws_sequencer_t *
+lws_sequencer_from_user(void *u);
+
+/**
+ * lws_sequencer_secs_since_creation(): elapsed seconds since sequencer created
+ *
+ * \param seq: pointer to the lws_sequencer_t
+ *
+ * Returns the number of seconds elapsed since the lws_sequencer_t was
+ * created. This is useful to calculate sequencer timeouts for the current
+ * step considering a global sequencer lifetime limit.
+ */
+LWS_VISIBLE LWS_EXTERN int
+lws_sequencer_secs_since_creation(lws_sequencer_t *seq);
diff --git a/lib/core-net/private.h b/lib/core-net/private.h
index b93826f56..14c5c50a5 100644
--- a/lib/core-net/private.h
+++ b/lib/core-net/private.h
@@ -279,7 +279,10 @@ struct lws_context_per_thread {
struct lws_dll dll_timeout_head;
struct lws_dll dll_hrtimer_head;
- struct lws_dll2_owner dll_buflist_owner; /* guys with pending rxflow */
+ struct lws_dll2_owner dll_buflist_owner; /* guys with pending rxflow */
+ struct lws_dll2_owner seq_owner; /* list of lws_sequencer-s */
+ struct lws_dll2_owner seq_pend_owner; /* lws_seq-s with pending evts */
+ struct lws_dll2_owner seq_to_owner; /* lws_seq-s with sorted timeout */
#if defined(LWS_WITH_TLS)
struct lws_pt_tls tls;
@@ -914,6 +917,9 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len);
LWS_EXTERN void
lws_remove_from_timeout_list(struct lws *wsi);
+LWS_EXTERN int
+lws_sequencer_timeout_check(struct lws_context_per_thread *pt, time_t now);
+
LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
lws_client_connect_2(struct lws *wsi);
@@ -1026,6 +1032,9 @@ __lws_same_vh_protocol_remove(struct lws *wsi);
LWS_EXTERN void
lws_same_vh_protocol_insert(struct lws *wsi, int n);
+int
+lws_pt_do_pending_sequencer_events(struct lws_context_per_thread *pt);
+
LWS_EXTERN int
lws_broadcast(struct lws_context *context, int reason, void *in, size_t len);
diff --git a/lib/core-net/sequencer.c b/lib/core-net/sequencer.c
new file mode 100644
index 000000000..405747d2d
--- /dev/null
+++ b/lib/core-net/sequencer.c
@@ -0,0 +1,311 @@
+/*
+ * libwebsockets - lib/core-net/sequencer.c
+ *
+ * Copyright (C) 2019 Andy Green
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation:
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include "core/private.h"
+
+/*
+ * per pending event
+ */
+typedef struct lws_seq_event {
+ struct lws_dll2 seq_event_list;
+
+ void *data;
+ lws_seq_events_t e;
+} lws_seq_event_t;
+
+/*
+ * per sequencer
+ */
+typedef struct lws_sequencer {
+ struct lws_dll2 seq_list;
+ struct lws_dll2 seq_pend_list;
+ struct lws_dll2 seq_to_list;
+
+ struct lws_dll2_owner seq_event_owner;
+ struct lws_context_per_thread *pt;
+ lws_seq_event_cb cb;
+
+ time_t time_created;
+ time_t timeout; /* 0 or time we timeout */
+
+ char going_down;
+} lws_sequencer_t;
+
+#define QUEUE_SANITY_LIMIT 10
+
+lws_sequencer_t *
+lws_sequencer_create(struct lws_context *context, int tsi, size_t user_size,
+ void **puser, lws_seq_event_cb cb)
+{
+ struct lws_context_per_thread *pt = &context->pt[tsi];
+ lws_sequencer_t *seq = lws_zalloc(sizeof(*seq) + user_size, __func__);
+
+ if (!seq)
+ return NULL;
+
+ seq->cb = cb;
+ seq->pt = pt;
+
+ *puser = (void *)&seq[1];
+
+ /* add the sequencer to the pt */
+
+ lws_pt_lock(pt, __func__); /* ---------------------------------- pt { */
+
+ lws_dll2_add_tail(&seq->seq_list, &pt->seq_owner);
+
+ lws_pt_unlock(pt); /* } pt ------------------------------------------ */
+
+ time(&seq->time_created);
+
+ /* try to queue the creation cb */
+
+ if (lws_sequencer_event(seq, LWSSEQ_CREATED, NULL)) {
+ lws_dll2_remove(&seq->seq_list);
+ lws_free(seq);
+
+ return NULL;
+ }
+
+ return seq;
+}
+
+static int
+seq_ev_destroy(struct lws_dll2 *d, void *user)
+{
+ lws_seq_event_t *seqe = lws_container_of(d, lws_seq_event_t,
+ seq_event_list);
+
+ lws_dll2_remove(&seqe->seq_event_list);
+ lws_free(seqe);
+
+ return 0;
+}
+
+void
+lws_sequencer_destroy(lws_sequencer_t **pseq)
+{
+ lws_sequencer_t *seq = *pseq;
+
+ /* defeat another thread racing to add events while we are destroying */
+ seq->going_down = 1;
+
+ lws_pt_lock(seq->pt, __func__); /* -------------------------- pt { */
+
+ lws_dll2_remove(&seq->seq_to_list);
+ lws_dll2_remove(&seq->seq_pend_list);
+ /* remove and destroy any pending events */
+ lws_dll2_foreach_safe(&seq->seq_event_owner, NULL, seq_ev_destroy);
+
+ lws_pt_unlock(seq->pt); /* } pt ---------------------------------- */
+
+ seq->cb(seq, (void *)&seq[1], LWSSEQ_DESTROYED, NULL);
+
+ lws_free_set_NULL(seq);
+}
+
+int
+lws_sequencer_event(lws_sequencer_t *seq, lws_seq_events_t e, void *data)
+{
+ lws_seq_event_t *seqe = lws_zalloc(sizeof(*seqe), __func__);
+
+ if (!seqe || seq->going_down)
+ return 1;
+
+ seqe->e = e;
+ seqe->data = data;
+
+ lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
+
+ if (seq->seq_event_owner.count > QUEUE_SANITY_LIMIT) {
+ lwsl_err("%s: more than %d events queued\n", __func__,
+ QUEUE_SANITY_LIMIT);
+ }
+
+ lws_dll2_add_tail(&seqe->seq_event_list, &seq->seq_event_owner);
+
+ /* if not already on the pending list, add us */
+ if (lws_dll2_is_detached(&seq->seq_pend_list))
+ lws_dll2_add_tail(&seq->seq_pend_list, &seq->pt->seq_pend_owner);
+
+ lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
+
+ return 0;
+}
+
+/*
+ * seq should have at least one pending event (he was on the pt's list of
+ * sequencers with pending events). Send the top event in the queue.
+ */
+
+static int
+lws_sequencer_next_event(struct lws_dll2 *d, void *user)
+{
+ lws_sequencer_t *seq = lws_container_of(d, lws_sequencer_t,
+ seq_pend_list);
+ lws_seq_event_t *seqe;
+ struct lws_dll2 *dh;
+ int n;
+
+ /* we should be on the pending list, right? */
+ assert(seq->seq_event_owner.count);
+
+ /* events are only added at tail, so no race possible yet... */
+
+ dh = lws_dll2_get_head(&seq->seq_event_owner);
+ seqe = lws_container_of(dh, lws_seq_event_t, seq_event_list);
+
+ n = seq->cb(seq, (void *)&seq[1], seqe->e, seqe->data);
+
+ /* ... have to lock here though, because we will change the list */
+
+ lws_pt_lock(seq->pt, __func__); /* ----------------------------- pt { */
+
+ /* detach event from sequencer event list and free it */
+ lws_dll2_remove(&seqe->seq_event_list);
+ lws_free(seqe);
+
+ /*
+ * if seq has no more pending, remove from pt's list of sequencers
+ * with pending events
+ */
+ if (!seq->seq_event_owner.count)
+ lws_dll2_remove(&seq->seq_pend_list);
+
+ lws_pt_unlock(seq->pt); /* } pt ------------------------------------- */
+
+ if (n) {
+ lwsl_info("%s: destroying seq by request\n", __func__);
+ lws_sequencer_destroy(&seq);
+
+ return LWSSEQ_RET_DESTROY;
+ }
+
+ return LWSSEQ_RET_CONTINUE;
+}
+
+/*
+ * nonpublic helper for the pt to call one event per pending sequencer, if any
+ * are pending
+ */
+
+int
+lws_pt_do_pending_sequencer_events(struct lws_context_per_thread *pt)
+{
+ if (!pt->seq_pend_owner.count)
+ return 0;
+
+ return lws_dll2_foreach_safe(&pt->seq_pend_owner, NULL,
+ lws_sequencer_next_event);
+}
+
+/* set secs to zero to remove timeout */
+
+int
+lws_sequencer_timeout(lws_sequencer_t *seq, int secs)
+{
+ lws_dll2_remove(&seq->seq_to_list);
+
+ if (!secs) {
+ /* we are clearing the timeout */
+ seq->timeout = 0;
+
+ return 0;
+ }
+
+ time(&seq->timeout);
+ seq->timeout += secs;
+
+ /*
+ * we sort the pt's list of sequencers with pending timeouts, so it's
+ * cheap to check it every second
+ */
+
+ lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+ seq->pt->seq_to_owner.head) {
+ lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t,
+ seq_to_list);
+
+ assert(s->timeout); /* shouldn't be on the list otherwise */
+ if (s->timeout >= seq->timeout) {
+ /* drop us in before this guy */
+ lws_dll2_add_before(&seq->seq_to_list,
+ &s->seq_to_list);
+
+ return 0;
+ }
+ } lws_end_foreach_dll_safe(p, tp);
+
+ /*
+ * Either nobody on the list yet to compare him to, or he's the
+ * longest timeout... stick him at the tail end
+ */
+
+ lws_dll2_add_tail(&seq->seq_to_list, &seq->pt->seq_to_owner);
+
+ return 0;
+}
+
+/*
+ * nonpublic helper to check for and handle sequencer timeouts for a whole pt
+ */
+
+int
+lws_sequencer_timeout_check(struct lws_context_per_thread *pt, time_t now)
+{
+ lws_start_foreach_dll_safe(struct lws_dll2 *, p, tp,
+ pt->seq_to_owner.head) {
+ lws_sequencer_t *s = lws_container_of(p, lws_sequencer_t,
+ seq_to_list);
+
+ assert(s->timeout); /* shouldn't be on the list otherwise */
+ if (s->timeout <= now) {
+ /* seq has timed out... remove him from timeout list */
+ lws_sequencer_timeout(s, 0);
+ /* queue the message to inform the sequencer */
+ lws_sequencer_event(s, LWSSEQ_TIMED_OUT, NULL);
+ } else
+ /*
+ * No need to look further if we met one later than now:
+ * the list is sorted in ascending time order
+ */
+ return 0;
+
+ } lws_end_foreach_dll_safe(p, tp);
+
+ return 0;
+}
+
+lws_sequencer_t *
+lws_sequencer_from_user(void *u)
+{
+ return &((lws_sequencer_t *)u)[-1];
+}
+
+int
+lws_sequencer_secs_since_creation(lws_sequencer_t *seq)
+{
+ time_t now;
+
+ time(&now);
+
+ return now - seq->time_created;
+}
diff --git a/lib/core-net/service.c b/lib/core-net/service.c
index 190bc23f4..6a22530da 100644
--- a/lib/core-net/service.c
+++ b/lib/core-net/service.c
@@ -368,7 +368,14 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi)
#endif
/*
- * 3) If there is any wsi with rxflow buffered and in a state to process
+ * 3) If any pending sequencer events, do not wait in poll
+ */
+
+ if (pt->seq_pend_owner.count)
+ return 0;
+
+ /*
+ * 4) If there is any wsi with rxflow buffered and in a state to process
* it, we should not wait in poll
*/
@@ -380,7 +387,7 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi)
return 0;
/*
- * 4) If any guys with http compression to spill, we shouldn't wait in
+ * 5) If any guys with http compression to spill, we shouldn't wait in
* poll but hurry along and service them
*/
@@ -639,6 +646,9 @@ lws_service_periodic_checks(struct lws_context *context,
context->last_timeout_check_s = now - 1;
}
+ lws_sequencer_timeout_check(pt, now);
+ lws_pt_do_pending_sequencer_events(pt);
+
if (!lws_compare_time_t(context, context->last_timeout_check_s, now))
return 0;
diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c
index b3c4f90ce..4a54054f1 100644
--- a/lib/core-net/vhost.c
+++ b/lib/core-net/vhost.c
@@ -366,8 +366,10 @@ lws_protocol_init(struct lws_context *context)
if (vh->protocols[n].callback(&wsi,
LWS_CALLBACK_PROTOCOL_INIT, NULL,
(void *)pvo, 0)) {
- lws_free(vh->protocol_vh_privs[n]);
- vh->protocol_vh_privs[n] = NULL;
+ if (vh->protocol_vh_privs[n]) {
+ lws_free(vh->protocol_vh_privs[n]);
+ vh->protocol_vh_privs[n] = NULL;
+ }
lwsl_err("%s: protocol %s failed init\n",
__func__, vh->protocols[n].name);
diff --git a/lib/core/lws_dll2.c b/lib/core/lws_dll2.c
index dad96bb38..4981aac2d 100644
--- a/lib/core/lws_dll2.c
+++ b/lib/core/lws_dll2.c
@@ -65,6 +65,40 @@ lws_dll2_add_head(struct lws_dll2 *d, struct lws_dll2_owner *owner)
owner->count++;
}
+/*
+ * add us to the list that 'after' is in, just before him
+ */
+
+void
+lws_dll2_add_before(struct lws_dll2 *d, struct lws_dll2 *after)
+{
+ lws_dll2_owner_t *owner = after->owner;
+
+ if (!lws_dll2_is_detached(d)) {
+ assert(0); /* only wholly detached things can be added */
+ return;
+ }
+
+ d->owner = owner;
+
+ /* we need to point to after */
+
+ d->next = after;
+
+ /* guy that used to point to after, needs to point to us */
+
+ if (after->prev)
+ after->prev->next = d;
+ else
+ owner->head = d;
+
+ /* then after needs to point back to us */
+
+ after->prev = d;
+
+ owner->count++;
+}
+
void
lws_dll2_add_tail(struct lws_dll2 *d, struct lws_dll2_owner *owner)
{
diff --git a/lib/plat/unix/unix-service.c b/lib/plat/unix/unix-service.c
index 217b2e264..a94f6f5fb 100644
--- a/lib/plat/unix/unix-service.c
+++ b/lib/plat/unix/unix-service.c
@@ -134,6 +134,13 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
lws_pt_unlock(pt);
+ /*
+ * if there are any pending sequencer events, handle the next one
+ * for all sequencers with pending events. If nothing to do returns
+ * immediately.
+ */
+ lws_pt_do_pending_sequencer_events(pt);
+
m = 0;
#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
m |= !!pt->ws.rx_draining_ext_list;
diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c
index 431032ffa..18b0d9ad2 100644
--- a/lib/roles/http/client/client-handshake.c
+++ b/lib/roles/http/client/client-handshake.c
@@ -815,6 +815,11 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
if (p)
lws_strncpy(alpn, p, sizeof(alpn));
+ if (!port) {
+ port = 443;
+ ssl = 1;
+ }
+
lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n",
address, port, path, ssl);
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt b/minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt
new file mode 100644
index 000000000..cfb9b98ee
--- /dev/null
+++ b/minimal-examples/api-tests/api-test-lws_sequencer/CMakeLists.txt
@@ -0,0 +1,77 @@
+cmake_minimum_required(VERSION 2.8)
+include(CheckCSourceCompiles)
+
+set(SAMP lws-api-test-lws_sequencer)
+set(SRCS main.c)
+
+# If we are being built as part of lws, confirm current build config supports
+# reqconfig, else skip building ourselves.
+#
+# If we are being built externally, confirm installed lws was configured to
+# support reqconfig, else error out with a helpful message about the problem.
+#
+MACRO(require_lws_config reqconfig _val result)
+
+ if (DEFINED ${reqconfig})
+ if (${reqconfig})
+ set (rq 1)
+ else()
+ set (rq 0)
+ endif()
+ else()
+ set(rq 0)
+ endif()
+
+ if (${_val} EQUAL ${rq})
+ set(SAME 1)
+ else()
+ set(SAME 0)
+ endif()
+
+ if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
+ if (${_val})
+ message("${SAMP}: skipping as lws being built without ${reqconfig}")
+ else()
+ message("${SAMP}: skipping as lws built with ${reqconfig}")
+ endif()
+ set(${result} 0)
+ else()
+ if (LWS_WITH_MINIMAL_EXAMPLES)
+ set(MET ${SAME})
+ else()
+ CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
+ if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
+ set(HAS_${reqconfig} 0)
+ else()
+ set(HAS_${reqconfig} 1)
+ endif()
+ if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
+ set(MET 1)
+ else()
+ set(MET 0)
+ endif()
+ endif()
+ if (NOT MET)
+ if (${_val})
+ message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
+ else()
+ message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
+ endif()
+ endif()
+ endif()
+ENDMACRO()
+
+set(requirements 1)
+require_lws_config(LWS_ROLE_H1 1 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+
+if (requirements)
+ add_executable(${SAMP} ${SRCS})
+
+ if (websockets_shared)
+ target_link_libraries(${SAMP} websockets_shared)
+ add_dependencies(${SAMP} websockets_shared)
+ else()
+ target_link_libraries(${SAMP} websockets)
+ endif()
+endif()
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer b/minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer
new file mode 100644
index 000000000..67de1292c
--- /dev/null
+++ b/minimal-examples/api-tests/api-test-lws_sequencer/libwebsockets.org.cer
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCA/CgAwIBAgIQKy5u6tl1NmwUim7bo3yMBzANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwMjEy
+MDAwMDAwWhcNMjkwMjExMjM1OTU5WjCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZh
+bGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAI7CAhnhoFmk6zg1jSz9AdDTScBkxwtiBUUWOqigwAwCfx3M28Sh
+bXcDow+G+eMGnD4LgYqbSRutA776S9uMIO3Vzl5ljj4Nr0zCsLdFXlIvNN5IJGS0
+Qa4Al/e+Z96e0HqnU4A7fK31llVvl0cKfIWLIpeNs4TgllfQcBhglo/uLQeTnaG6
+ytHNe+nEKpooIZFNb5JPJaXyejXdJtxGpdCsWTWM/06RQ1A/WZMebFEh7lgUq/51
+UHg+TLAchhP6a5i84DuUHoVS3AOTJBhuyydRReZw3iVDpA3hSqXttn7IzW3uLh0n
+c13cRTCAquOyQQuvvUSH2rnlG51/ruWFgqUCAwEAAaOCAWUwggFhMB8GA1UdIwQY
+MBaAFLuvfgI9+qbxPISOre44mOzZMjLUMB0GA1UdDgQWBBSQr2o6lFoL2JDqElZz
+30O0Oija5zAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNV
+HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgG
+BmeBDAECATBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNv
+bS9DT01PRE9SU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDBxBggrBgEFBQcB
+AQRlMGMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9E
+T1JTQUFkZFRydXN0Q0EuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5jb21v
+ZG9jYS5jb20wDQYJKoZIhvcNAQEMBQADggIBAE4rdk+SHGI2ibp3wScF9BzWRJ2p
+mj6q1WZmAT7qSeaiNbz69t2Vjpk1mA42GHWx3d1Qcnyu3HeIzg/3kCDKo2cuH1Z/
+e+FE6kKVxF0NAVBGFfKBiVlsit2M8RKhjTpCipj4SzR7JzsItG8kO3KdY3RYPBps
+P0/HEZrIqPW1N+8QRcZs2eBelSaz662jue5/DJpmNXMyYE7l3YphLG5SEXdoltMY
+dVEVABt0iN3hxzgEQyjpFv3ZBdRdRydg1vs4O2xyopT4Qhrf7W8GjEXCBgCq5Ojc
+2bXhc3js9iPc0d1sjhqPpepUfJa3w/5Vjo1JXvxku88+vZbrac2/4EjxYoIQ5QxG
+V/Iz2tDIY+3GH5QFlkoakdH368+PUq4NCNk+qKBR6cGHdNXJ93SrLlP7u3r7l+L4
+HyaPs9Kg4DdbKDsx5Q5XLVq4rXmsXiBmGqW5prU5wfWYQ//u+aen/e7KJD2AFsQX
+j4rBYKEMrltDR5FL1ZoXX/nUh8HCjLfn4g8wGTeGrODcQgPmlKidrv0PJFGUzpII
+0fxQ8ANAe4hZ7Q7drNJ3gjTcBpUC2JD5Leo31Rpg0Gcg19hCC0Wvgmje3WYkN5Ap
+lBlGGSW4gNfL1IYoakRwJiNiqZ+Gb7+6kHDSVneFeO/qJakXzlByjAA6quPbYzSf
++AZxAeKCINT+b72x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFdDCCBFygAwIBAgIQJ2buVutJ846r13Ci/ITeIjANBgkqhkiG9w0BAQwFADBv
+MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk
+ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF
+eHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFow
+gYUxCzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
+BgNVBAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMSswKQYD
+VQQDEyJDT01PRE8gUlNBIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAkehUktIKVrGsDSTdxc9EZ3SZKzejfSNw
+AHG8U9/E+ioSj0t/EFa9n3Byt2F/yUsPF6c947AEYe7/EZfH9IY+Cvo+XPmT5jR6
+2RRr55yzhaCCenavcZDX7P0N+pxs+t+wgvQUfvm+xKYvT3+Zf7X8Z0NyvQwA1onr
+ayzT7Y+YHBSrfuXjbvzYqOSSJNpDa2K4Vf3qwbxstovzDo2a5JtsaZn4eEgwRdWt
+4Q08RWD8MpZRJ7xnw8outmvqRsfHIKCxH2XeSAi6pE6p8oNGN4Tr6MyBSENnTnIq
+m1y9TBsoilwie7SrmNnu4FGDwwlGTm0+mfqVF9p8M1dBPI1R7Qu2XK8sYxrfV8g/
+vOldxJuvRZnio1oktLqpVj3Pb6r/SVi+8Kj/9Lit6Tf7urj0Czr56ENCHonYhMsT
+8dm74YlguIwoVqwUHZwK53Hrzw7dPamWoUi9PPevtQ0iTMARgexWO/bTouJbt7IE
+IlKVgJNp6I5MZfGRAy1wdALqi2cVKWlSArvX31BqVUa/oKMoYX9w0MOiqiwhqkfO
+KJwGRXa/ghgntNWutMtQ5mv0TIZxMOmm3xaG4Nj/QN370EKIf6MzOi5cHkERgWPO
+GHFrK+ymircxXDpqR+DDeVnWIBqv8mqYqnK8V0rSS527EPywTEHl7R09XiidnMy/
+s1Hap0flhFMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBStvZh6NLQm9/rEJlTvA73g
+JMtUGjAdBgNVHQ4EFgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQD
+AgGGMA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0gBAowCDAGBgRVHSAAMEQGA1UdHwQ9
+MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9BZGRUcnVzdEV4dGVy
+bmFsQ0FSb290LmNybDA1BggrBgEFBQcBAQQpMCcwJQYIKwYBBQUHMAGGGWh0dHA6
+Ly9vY3NwLnVzZXJ0cnVzdC5jb20wDQYJKoZIhvcNAQEMBQADggEBAGS/g/FfmoXQ
+zbihKVcN6Fr30ek+8nYEbvFScLsePP9NDXRqzIGCJdPDoCpdTPW6i6FtxFQJdcfj
+Jw5dhHk3QBN39bSsHNA7qxcS1u80GH4r6XnTq1dFDK8o+tDb5VCViLvfhVdpfZLY
+Uspzgb8c8+a4bmYRBbMelC1/kZWSWfFMzqORcUx8Rww7Cxn2obFshj5cqsQugsv5
+B5a6SE2Q8pTIqXOi6wZ7I53eovNNVZ96YUWYGGjHXkBrI/V5eu+MtWuLt29G9Hvx
+PUsE2JOAWVrgQSQdso8VYFhH2+9uRv0V9dlfmrPb2LjkQLPNlzmuhbsdjrzch5vR
+pu/xO28QOG8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
+MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
+IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
+MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
+FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
+bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
+dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
+H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
+uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
+mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
+a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
+E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
+WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
+VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
+Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
+cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
+IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
+AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
+YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
+6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
+Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
+c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
+mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
+-----END CERTIFICATE-----
diff --git a/minimal-examples/api-tests/api-test-lws_sequencer/main.c b/minimal-examples/api-tests/api-test-lws_sequencer/main.c
new file mode 100644
index 000000000..cbb3209d3
--- /dev/null
+++ b/minimal-examples/api-tests/api-test-lws_sequencer/main.c
@@ -0,0 +1,392 @@
+/*
+ * lws-api-test-lws_sequencer
+ *
+ * Written in 2019 by Andy Green
+ *
+ * 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
+
+#include
+
+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_context *context;
+ 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_sequencer_event(lws_sequencer_from_user(s), seq_msg, 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 = s->context;
+
+ 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_sequencer_event(lws_sequencer_from_user(s),
+ SEQ_MSG_CLIENT_FAILED, NULL);
+
+ return 1;
+ }
+
+ lws_sequencer_timeout(lws_sequencer_from_user(s), 3);
+
+ 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)
+{
+ 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_sequencer_timeout(lws_sequencer_from_user(s), 0);
+ 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;
+ lws_sequencer_t *seq;
+ struct lws_vhost *vh;
+ 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
+ */
+
+ seq = lws_sequencer_create(context, 0, sizeof(struct myseq),
+ (void **)&s, sequencer_cb);
+ if (!seq) {
+ lwsl_err("%s: unable to create sequencer\n", __func__);
+ goto bail1;
+ }
+ s->context = context;
+ s->vhost = vh;
+
+ /* the usual lws event loop */
+
+ while (n >= 0 && !interrupted)
+ n = lws_service(context, 1000);
+
+bail1:
+ lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
+
+ lws_context_destroy(context);
+
+ return !test_good;
+}