1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

muxable client: make http support generic

h1 and h2 has a bunch of code supporting autobinding outgoing client connections
to be streams in, or queued as pipelined on, the same / existing single network
connection, if it's to the same endpoint.

Adapt this http-specific code and active connection tracking to be usable for
generic muxable protocols the same way.
This commit is contained in:
Andy Green 2019-09-23 04:23:07 -07:00
parent 94f1c7b0c1
commit fc295b7959
7 changed files with 203 additions and 170 deletions

View file

@ -48,7 +48,8 @@ enum lws_client_connect_ssl_connection_flags {
* HTTP/1.0: possible if Keep-Alive: yes sent by server
* HTTP/1.1: always possible... uses pipelining
* HTTP/2: always possible... uses parallel streams
* */
*/
LCCSCF_MUXABLE_STREAM = (1 << 17),
};
/** struct lws_client_connect_info - parameters to connect with when using

View file

@ -273,6 +273,16 @@ lws_atcrs_client_conn(const lws_abs_t *abs)
i.seq = abs->seq;
i.opaque_user_data = abs->opaque_user_data;
/*
* the protocol itself has some natural attributes we should pass on
*/
if (abs->ap->flags & LWS_AP_FLAG_PIPELINE_TRANSACTIONS)
i.ssl_connection |= LCCSCF_PIPELINE;
if (abs->ap->flags & LWS_AP_FLAG_MUXABLE_STREAM)
i.ssl_connection |= LCCSCF_MUXABLE_STREAM;
priv->wsi = lws_client_connect_via_info(&i);
if (!priv->wsi)
return 1;

View file

@ -1239,6 +1239,15 @@ lws_async_dns_deinit(lws_async_dns_t *dns);
int
lws_protocol_init_vhost(struct lws_vhost *vh, int *any);
int
_lws_generic_transaction_completed_active_conn(struct lws *wsi);
#define ACTIVE_CONNS_SOLO 0
#define ACTIVE_CONNS_MUXED 1
#define ACTIVE_CONNS_QUEUED 2
int
lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi);
#ifdef __cplusplus
};

View file

@ -1363,6 +1363,7 @@ lws_context_deprecate(struct lws_context *context, lws_reload_func cb)
#endif
#if defined(LWS_WITH_NETWORK)
struct lws_vhost *
lws_get_vhost_by_name(struct lws_context *context, const char *name)
{
@ -1375,4 +1376,103 @@ lws_get_vhost_by_name(struct lws_context *context, const char *name)
return NULL;
}
#if defined(LWS_WITH_CLIENT)
/*
* This is the logic checking to see if the new connection wsi should have a
* pipelining or muxing relationship with an existing "active connection" to
* the same endpoint under the same conditions.
*
* This was originally in the client code but since the list is held on the
* vhost (to ensure the same client tls ctx is involved) it's cleaner in vhost.c
*/
int
lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi)
{
const char *adsin;
adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
wsi->vhost->dll_cli_active_conns_owner.head) {
struct lws *w = lws_container_of(d, struct lws,
dll_cli_active_conns);
lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin,
w->cli_hostname_copy, wsi->c_port, w->c_port);
if (w != wsi && w->cli_hostname_copy &&
!strcmp(adsin, w->cli_hostname_copy) &&
#if defined(LWS_WITH_TLS)
(wsi->tls.use_ssl & LCCSCF_USE_SSL) ==
(w->tls.use_ssl & LCCSCF_USE_SSL) &&
#endif
wsi->c_port == w->c_port) {
/*
* There's already an active connection.
*
* The server may have told the existing active
* connection that it doesn't support pipelining...
*/
if (w->keepalive_rejected) {
lwsl_info("defeating pipelining due to no "
"keepalive on server\n");
goto solo;
}
#if defined (LWS_WITH_HTTP2)
/*
* h2: in usable state already: just use it without
* going through the queue
*/
if (w->client_h2_alpn &&
(lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS ||
lwsi_state(w) == LRS_ESTABLISHED)) {
lwsl_info("%s: just join h2 directly\n",
__func__);
wsi->client_h2_alpn = 1;
lws_wsi_h2_adopt(w, wsi);
lws_vhost_unlock(wsi->vhost); /* } ---------- */
return ACTIVE_CONNS_MUXED;
}
#endif
lwsl_info("apply %p to txn queue on %p state 0x%lx\n",
wsi, w, (unsigned long)w->wsistate);
/*
* ...let's add ourselves to his transaction queue...
* we are adding ourselves at the HEAD
*/
lws_dll2_add_head(&wsi->dll2_cli_txn_queue,
&w->dll2_cli_txn_queue_owner);
/*
* h1: pipeline our headers out on him,
* and wait for our turn at client transaction_complete
* to take over parsing the rx.
*/
lws_vhost_unlock(wsi->vhost); /* } ---------- */
*nwsi = w;
return ACTIVE_CONNS_QUEUED;
}
} lws_end_foreach_dll_safe(d, d1);
solo:
lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */
/* there is nobody already connected in the same way */
return ACTIVE_CONNS_SOLO;
}
#endif
#endif

View file

@ -751,6 +751,63 @@ lws_get_context(const struct lws *wsi)
return wsi->context;
}
#if defined(LWS_WITH_CLIENT)
int
_lws_generic_transaction_completed_active_conn(struct lws *wsi)
{
struct lws *wsi_eff = lws_client_wsi_effective(wsi);
/*
* Are we constitutionally capable of having a queue, ie, we are on
* the "active client connections" list?
*
* If not, that's it for us.
*/
if (lws_dll2_is_detached(&wsi->dll_cli_active_conns))
return 0; /* no new transaction */
/* if this was a queued guy, close him and remove from queue */
if (wsi->transaction_from_pipeline_queue) {
lwsl_debug("closing queued wsi %p\n", wsi_eff);
/* so the close doesn't trigger a CCE */
wsi_eff->already_did_cce = 1;
__lws_close_free_wsi(wsi_eff,
LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE,
"queued client done");
}
/* after the first one, they can only be coming from the queue */
wsi->transaction_from_pipeline_queue = 1;
wsi->hdr_parsing_completed = 0;
/* is there a new tail after removing that one? */
wsi_eff = lws_client_wsi_effective(wsi);
/*
* Do we have something pipelined waiting?
* it's OK if he hasn't managed to send his headers yet... he's next
* in line to do that...
*/
if (wsi_eff == wsi) {
/*
* Nothing pipelined... we should hang around a bit
* in case something turns up...
*/
lwsl_info("%s: nothing pipelined waiting\n", __func__);
lwsi_set_state(wsi, LRS_IDLING);
lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5);
return 0; /* no new transaction right now */
}
return 1; /* new transaction */
}
#endif
LWS_VISIBLE int LWS_WARN_UNUSED_RESULT
lws_raw_transaction_completed(struct lws *wsi)
{

View file

@ -258,12 +258,9 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,
char ni[48];
int m;
#ifdef LWS_WITH_IPV6
#if defined(__ANDROID__)
#if defined(LWS_WITH_IPV6) && defined(__ANDROID__)
ipv6only = 0;
#endif
#endif
/*
* async dns calls back here for everybody who cares when it gets a
@ -688,16 +685,14 @@ failed1:
struct lws *
lws_client_connect_2_dnsreq(struct lws *wsi)
{
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
const char *adsin;
#endif
const char *cce = "", *meth = NULL, *ads;
const char *meth = NULL, *ads;
struct addrinfo *result = NULL;
#if defined(LWS_WITH_IPV6)
struct sockaddr_in addr;
const char *iface;
#endif
int n, port = 0;
struct lws *w;
if (lwsi_state(wsi) == LRS_WAITING_DNS) {
lwsl_notice("%s: LRS_WAITING_DNS\n", __func__);
@ -705,113 +700,37 @@ lws_client_connect_2_dnsreq(struct lws *wsi)
return wsi;
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
if (!wsi->http.ah && !wsi->stash) {
cce = "ah was NULL at cc2";
lwsl_err("%s\n", cce);
goto oom4;
}
/* we can only piggyback GET or POST */
if (wsi->stash)
meth = wsi->stash->cis[CIS_METHOD];
else
meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
if (meth && strcmp(meth, "GET") && strcmp(meth, "POST")) {
lwsl_debug("%s: new conn on meth\n", __func__);
goto create_new_conn;
}
/* we only pipeline connections that said it was okay */
if (!wsi->client_pipeline) {
lwsl_debug("%s: new conn on no pipeline flag\n", __func__);
goto create_new_conn;
goto solo;
}
/*
* let's take a look first and see if there are any already-active
* client connections we can piggy-back on.
*/
/* only pipeline things we associate with being a stream */
adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
if (meth && strcmp(meth, "RAW") && strcmp(meth, "GET") &&
strcmp(meth, "POST"))
goto solo;
lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */
/* consult active connections to find out disposition */
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, lws_dll2_get_head(
&wsi->vhost->dll_cli_active_conns_owner)) {
struct lws *w = lws_container_of(d, struct lws,
dll_cli_active_conns);
// lwsl_notice("%s: check %s %s %d %d\n", __func__, adsin,
// w->cli_hostname_copy, wsi->c_port, w->c_port);
if (w != wsi && w->cli_hostname_copy &&
!strcmp(adsin, w->cli_hostname_copy) &&
#if defined(LWS_WITH_TLS)
(wsi->tls.use_ssl & LCCSCF_USE_SSL) ==
(w->tls.use_ssl & LCCSCF_USE_SSL) &&
#endif
wsi->c_port == w->c_port) {
/* someone else is already connected to the right guy */
/* do we know for a fact pipelining won't fly? */
if (w->keepalive_rejected) {
lwsl_info("defeating pipelining due to no "
"keepalive on server\n");
lws_vhost_unlock(wsi->vhost); /* } ---------- */
goto create_new_conn;
}
#if defined (LWS_WITH_HTTP2)
/*
* h2: in usable state already: just use it without
* going through the queue
*/
if (w->client_h2_alpn &&
(lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS ||
lwsi_state(w) == LRS_ESTABLISHED)) {
lwsl_info("%s: just join h2 directly\n",
__func__);
wsi->client_h2_alpn = 1;
lws_wsi_h2_adopt(w, wsi);
lws_vhost_unlock(wsi->vhost); /* } ---------- */
return wsi;
}
#endif
lwsl_info("apply %p to txn queue on %p state 0x%lx\n",
wsi, w, (unsigned long)w->wsistate);
/*
* ...let's add ourselves to his transaction queue...
* we are adding ourselves at the HEAD
*/
lws_dll2_add_head(&wsi->dll2_cli_txn_queue,
&w->dll2_cli_txn_queue_owner);
/*
* h1: pipeline our headers out on him,
* and wait for our turn at client transaction_complete
* to take over parsing the rx.
*/
lws_vhost_unlock(wsi->vhost); /* } ---------- */
return lws_client_connect_4_established(wsi, w, 0);
}
} lws_end_foreach_dll_safe(d, d1);
lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */
create_new_conn:
#endif
switch (lws_vhost_active_conns(wsi, &w)) {
case ACTIVE_CONNS_SOLO:
break;
case ACTIVE_CONNS_MUXED:
return wsi;
case ACTIVE_CONNS_QUEUED:
return lws_client_connect_4_established(wsi, w, 0);
}
solo:
wsi->addrinfo_idx = 0;
/*
@ -840,7 +759,8 @@ create_new_conn:
* piggyback on our transaction queue
*/
if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) &&
if (meth && (!strcmp(meth, "RAW") || !strcmp(meth, "GET") ||
!strcmp(meth, "POST")) &&
lws_dll2_is_detached(&wsi->dll2_cli_txn_queue) &&
lws_dll2_is_detached(&wsi->dll_cli_active_conns)) {
lws_vhost_lock(wsi->vhost);
@ -961,35 +881,12 @@ next_step:
#endif
return lws_client_connect_3_connect(wsi, ads, result, n, NULL);
oom4:
if (lwsi_role_client(wsi) && wsi->protocol /* && lwsi_state_est(wsi) */)
lws_inform_client_conn_fail(wsi,(void *)cce, strlen(cce));
/* take care that we might be inserted in fds already */
if (wsi->position_in_fds_table != LWS_NO_FDS_POS)
goto failed1;
/*
* We can't be an active client connection any more, if we thought
* that was what we were going to be doing. It should be if we are
* failing by oom4 path, we are still called by
* lws_client_connect_via_info() and will be returning NULL to that,
* so nobody else should have had a chance to queue on us.
*/
{
struct lws_vhost *vhost = wsi->vhost;
lws_vhost_lock(vhost);
__lws_free_wsi(wsi);
lws_vhost_unlock(vhost);
}
return NULL;
#if defined(LWS_WITH_SYS_ASYNC_DNS)
failed1:
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
return NULL;
#endif
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)

View file

@ -619,6 +619,7 @@ int LWS_WARN_UNUSED_RESULT
lws_http_transaction_completed_client(struct lws *wsi)
{
struct lws *wsi_eff = lws_client_wsi_effective(wsi);
int n;
lwsl_info("%s: wsi: %p, wsi_eff: %p (%s)\n", __func__, wsi, wsi_eff,
wsi_eff->protocol->name);
@ -631,55 +632,13 @@ lws_http_transaction_completed_client(struct lws *wsi)
return -1;
}
/*
* Are we constitutionally capable of having a queue, ie, we are on
* the "active client connections" list?
*
* If not, that's it for us.
*/
if (lws_dll2_is_detached(&wsi->dll_cli_active_conns))
return -1;
/* if this was a queued guy, close him and remove from queue */
if (wsi->transaction_from_pipeline_queue) {
lwsl_debug("closing queued wsi %p\n", wsi_eff);
/* so the close doesn't trigger a CCE */
wsi_eff->already_did_cce = 1;
__lws_close_free_wsi(wsi_eff,
LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE,
"queued client done");
}
n = _lws_generic_transaction_completed_active_conn(wsi);
_lws_header_table_reset(wsi->http.ah);
/* after the first one, they can only be coming from the queue */
wsi->transaction_from_pipeline_queue = 1;
wsi->http.rx_content_length = 0;
wsi->hdr_parsing_completed = 0;
/* is there a new tail after removing that one? */
wsi_eff = lws_client_wsi_effective(wsi);
/*
* Do we have something pipelined waiting?
* it's OK if he hasn't managed to send his headers yet... he's next
* in line to do that...
*/
if (wsi_eff == wsi) {
/*
* Nothing pipelined... we should hang around a bit
* in case something turns up...
*/
lwsl_info("%s: nothing pipelined waiting\n", __func__);
lwsi_set_state(wsi, LRS_IDLING);
lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5);
if (!n)
return 0;
}
/*
* H1: we can serialize the queued guys into the same ah