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:
parent
94f1c7b0c1
commit
fc295b7959
7 changed files with 203 additions and 170 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue