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](/doc-assets/lws_sequencer.svg) + +`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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; +}