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

client: h2

This adds h2 http support for the client api.

The public client api requires no changes, it will detect by
ALPN if the server can handle http/2, if so, it will use it.

Multiple client connections using the lws api will be mapped on
to the same single http/2 + tls socket using http/2 streams
that are serviced simultaneously where possible.
This commit is contained in:
Andy Green 2018-03-27 09:17:19 +08:00
parent 2d3fc52b73
commit 2a9b6f54c6
20 changed files with 742 additions and 164 deletions

View file

@ -1104,6 +1104,18 @@ If lws learns from the first response header that keepalive is not possible,
then it marks itself with that information and detaches any queued clients
to make their own individual connections as a fallback.
Lws can also intelligently combine multiple ongoing client connections to
the same host and port into a single http/2 connection with multiple
streams if the server supports it.
Unlike http/1 pipelining, with http/2 the client connections all occur
simultaneously using h2 stream multiplexing inside the one tcp + tls
connection.
You can turn off the h2 client support either by not building lws with
`-DLWS_WITH_HTTP2=1` or giving the `LCCSCF_NOT_H2` flag in the client
connection info struct `ssl_connection` member.
@section vhosts Using lws vhosts
If you set LWS_SERVER_OPTION_EXPLICIT_VHOSTS options flag when you create

View file

@ -79,25 +79,52 @@ lws_client_connect_2(struct lws *wsi)
struct lws *w = lws_container_of(d, struct lws,
dll_active_client_conns);
if (w->ah && !strcmp(adsin, lws_hdr_simple_ptr(w,
_WSI_TOKEN_CLIENT_PEER_ADDRESS)) &&
if (w->client_hostname_copy &&
!strcmp(adsin, w->client_hostname_copy) &&
#ifdef LWS_OPENSSL_SUPPORT
(wsi->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) ==
(w->use_ssl & (LCCSCF_NOT_H2 | 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 KA on server\n");
lwsl_notice("defeating pipelining due to no KA on server\n");
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 &&
(w->state == LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS ||
w->state == LWSS_HTTP2_CLIENT_ESTABLISHED)) {
lwsl_info("%s: just join h2 directly\n",
__func__);
lws_wsi_h2_adopt(w, wsi);
lws_vhost_unlock(wsi->vhost);
return wsi;
}
#endif
lwsl_info("applying %p to txn queue on %p (%d)\n", wsi, w,
w->state);
/*
* ...let's add ourselves to his transaction queue...
*/
lws_dll_lws_add_front(&wsi->dll_client_transaction_queue,
&w->dll_client_transaction_queue_head);
/*
* pipeline our headers out on him, and wait for our
* turn at client transaction_complete to take over
* parsing the rx.
* h1: pipeline our headers out on him,
* and wait for our turn at client transaction_complete
* to take over parsing the rx.
*/
wsi_piggy = w;
@ -111,6 +138,17 @@ lws_client_connect_2(struct lws *wsi)
create_new_conn:
/*
* clients who will create their own fresh connection keep a copy of
* the hostname they originally connected to, in case other connections
* want to use it too
*/
if (!wsi->client_hostname_copy)
wsi->client_hostname_copy =
strdup(lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS));
/*
* start off allowing ipv6 on connection if vhost allows it
*/
@ -165,6 +203,8 @@ create_new_conn:
#ifdef LWS_WITH_IPV6
if (wsi->ipv6) {
struct sockaddr_in6 *sa6 =
((struct sockaddr_in6 *)result->ai_addr);
if (n) {
/* lws_getaddrinfo46 failed, there is no usable result */
@ -193,11 +233,10 @@ create_new_conn:
break;
case AF_INET6:
memcpy(&sa46.sa6.sin6_addr,
&((struct sockaddr_in6 *)result->ai_addr)->sin6_addr,
memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr,
sizeof(struct in6_addr));
sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id;
sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo;
sa46.sa6.sin6_scope_id = sa6->sin6_scope_id;
sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo;
break;
default:
lwsl_err("Unknown address family\n");
@ -507,7 +546,9 @@ oom4:
lws_header_table_force_to_detachable_state(wsi);
if (wsi->mode == LWSCM_HTTP_CLIENT ||
wsi->mode == LWSCM_HTTP2_CLIENT ||
wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED ||
wsi->mode == LWSCM_HTTP2_CLIENT_ACCEPTED ||
wsi->mode == LWSCM_WSCL_WAITING_CONNECT) {
wsi->protocol->callback(wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
@ -520,6 +561,7 @@ oom4:
lws_remove_from_timeout_list(wsi);
lws_header_table_detach(wsi, 0);
lws_client_stash_destroy(wsi);
lws_free_set_NULL(wsi->client_hostname_copy);
lws_free(wsi);
return NULL;
@ -887,7 +929,7 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
* which protocol we are associated with since we can give it a
* list.
*/
if ((i->method || i->local_protocol_name) && local) {
if (/*(i->method || i->local_protocol_name) && */local) {
lwsl_info("binding to %s\n", local);
p = lws_vhost_name_to_protocol(wsi->vhost, local);
if (p)
@ -907,8 +949,11 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
#ifdef LWS_OPENSSL_SUPPORT
wsi->use_ssl = i->ssl_connection;
if (!i->method) /* !!! disallow ws for h2 right now */
wsi->use_ssl |= LCCSCF_NOT_H2;
#else
if (i->ssl_connection) {
if (i->ssl_connection & LCCSCF_USE_SSL) {
lwsl_err("libwebsockets not configured for ssl\n");
goto bail;
}

View file

@ -351,13 +351,13 @@ start_ws_handshake:
#ifdef LWS_OPENSSL_SUPPORT
/* we can retry this... just cook the SSL BIO the first time */
if (wsi->use_ssl && !wsi->ssl &&
if ((wsi->use_ssl & LCCSCF_USE_SSL) && !wsi->ssl &&
lws_ssl_client_bio_create(wsi) < 0) {
cce = "bio_create failed";
goto bail3;
}
if (wsi->use_ssl) {
if (wsi->use_ssl & LCCSCF_USE_SSL) {
n = lws_ssl_client_connect1(wsi);
if (!n)
return 0;
@ -372,7 +372,7 @@ start_ws_handshake:
case LWSCM_WSCL_WAITING_SSL:
if (wsi->use_ssl) {
if (wsi->use_ssl & LCCSCF_USE_SSL) {
n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf));
if (!n)
return 0;
@ -383,7 +383,30 @@ start_ws_handshake:
} else
wsi->ssl = NULL;
#endif
#if defined (LWS_WITH_HTTP2)
if (wsi->client_h2_alpn) {
/*
* We connected to the server and set up tls, and
* negotiated "h2".
*
* So this is it, we are an h2 master client connection
* now, not an h1 client connection.
*/
lwsl_info("client connection upgraded to h2\n");
lws_h2_configure_if_upgraded(wsi);
lws_union_transition(wsi, LWSCM_HTTP2_CLIENT);
wsi->state = LWSS_HTTP2_CLIENT_SEND_SETTINGS;
/* send the H2 preface to legitimize the connection */
if (lws_h2_issue_preface(wsi)) {
cce = "error sending h2 preface";
goto bail3;
}
break;
}
#endif
wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2;
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
context->timeout_secs);
@ -705,7 +728,10 @@ lws_client_interpret_server_handshake(struct lws *wsi)
if (!wsi->do_ws) {
/* we are being an http client...
*/
lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED);
if (wsi->client_h2_alpn)
lws_union_transition(wsi, LWSCM_HTTP2_CLIENT_ACCEPTED);
else
lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED);
wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED;
wsi->ah = ah;
ah->http_response = 0;
@ -722,27 +748,27 @@ lws_client_interpret_server_handshake(struct lws *wsi)
* content-type:.text/html
* content-length:.17703
* set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000
*
*
*
*/
wsi->http.connection_type = HTTP_CONNECTION_KEEP_ALIVE;
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
if (wsi->do_ws && !p) {
lwsl_info("no URI\n");
cce = "HS: URI missing";
goto bail3;
}
if (!p) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
wsi->http.connection_type = HTTP_CONNECTION_CLOSE;
}
if (!p) {
cce = "HS: URI missing";
lwsl_info("no URI\n");
goto bail3;
}
if (!wsi->client_h2_substream) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
if (wsi->do_ws && !p) {
lwsl_info("no URI\n");
cce = "HS: URI missing";
goto bail3;
}
if (!p) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
wsi->http.connection_type = HTTP_CONNECTION_CLOSE;
}
if (!p) {
cce = "HS: URI missing";
lwsl_info("no URI\n");
goto bail3;
}
} else
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS);
n = atoi(p);
if (ah)
ah->http_response = n;
@ -757,7 +783,7 @@ lws_client_interpret_server_handshake(struct lws *wsi)
/* Relative reference absolute path */
if (p[0] == '/') {
#ifdef LWS_OPENSSL_SUPPORT
ssl = wsi->use_ssl;
ssl = wsi->use_ssl & LCCSCF_USE_SSL;
#endif
ads = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
@ -780,7 +806,7 @@ lws_client_interpret_server_handshake(struct lws *wsi)
/* This doesn't try to calculate an absolute path,
* that will be left to the server */
#ifdef LWS_OPENSSL_SUPPORT
ssl = wsi->use_ssl;
ssl = wsi->use_ssl & LCCSCF_USE_SSL;
#endif
ads = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
@ -798,7 +824,7 @@ lws_client_interpret_server_handshake(struct lws *wsi)
}
#ifdef LWS_OPENSSL_SUPPORT
if (wsi->use_ssl && !ssl) {
if ((wsi->use_ssl & LCCSCF_USE_SSL) && !ssl) {
cce = "HS: Redirect attempted SSL downgrade";
goto bail3;
}
@ -823,9 +849,9 @@ lws_client_interpret_server_handshake(struct lws *wsi)
if (!wsi->do_ws) {
/* if keepalive is allowed, enable the queued pipeline guys */
/* if h1 KA is allowed, enable the queued pipeline guys */
if (w == wsi) { /* ie, coming to this for the first time */
if (!wsi->client_h2_alpn && !wsi->client_h2_substream && w == wsi) { /* ie, coming to this for the first time */
if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE)
wsi->keepalive_active = 1;
else {
@ -949,6 +975,13 @@ lws_client_interpret_server_handshake(struct lws *wsi)
return 0;
}
if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */
lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n",
__func__);
cce = "HS: h2 / ws upgrade unsupported";
goto bail3;
}
if (p && !strncmp(p, "401", 3)) {
lwsl_warn(
"lws_client_handshake: got bad HTTP response '%s'\n", p);
@ -1079,7 +1112,8 @@ lws_client_interpret_server_handshake(struct lws *wsi)
if (!wsi->vhost->protocols[n].callback) {
if (wsi->protocol)
lwsl_err("Failed to match protocol %s\n", wsi->protocol->name);
lwsl_err("Failed to match protocol %s\n",
wsi->protocol->name);
else
lwsl_err("No protocol on client\n");
goto bail2;

View file

@ -69,8 +69,13 @@ lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
int m;
#endif
// lwsl_notice("%s: state %d\n", __func__, wsi->state);
switch (wsi->state) {
#if defined(LWS_WITH_HTTP2)
case LWSS_HTTP2_CLIENT_ESTABLISHED:
case LWSS_HTTP2_CLIENT_SEND_SETTINGS:
case LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS:
case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
case LWSS_HTTP2_ESTABLISHED:

View file

@ -38,7 +38,7 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
unsigned char **p, unsigned char *end)
{
#ifdef LWS_WITH_HTTP2
if (wsi->mode == LWSCM_HTTP2_SERVING)
if (wsi->mode == LWSCM_HTTP2_SERVING || wsi->mode == LWSCM_HTTP2_CLIENT_ACCEPTED)
return lws_add_http2_header_by_name(wsi, name,
value, length, p, end);
#else
@ -66,7 +66,7 @@ int lws_finalize_http_header(struct lws *wsi, unsigned char **p,
unsigned char *end)
{
#ifdef LWS_WITH_HTTP2
if (wsi->mode == LWSCM_HTTP2_SERVING)
if (wsi->mode == LWSCM_HTTP2_SERVING || wsi->mode == LWSCM_HTTP2_CLIENT_ACCEPTED)
return 0;
#else
(void)wsi;
@ -105,7 +105,7 @@ lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token,
{
const unsigned char *name;
#ifdef LWS_WITH_HTTP2
if (wsi->mode == LWSCM_HTTP2_SERVING)
if (wsi->mode == LWSCM_HTTP2_SERVING || wsi->mode == LWSCM_HTTP2_CLIENT_ACCEPTED)
return lws_add_http2_header_by_token(wsi, token, value,
length, p, end);
#endif

View file

@ -118,6 +118,17 @@ lws_h2_dump_settings(struct http2_settings *set)
}
#endif
static struct lws_h2_protocol_send *
lws_h2_new_pps(enum lws_h2_protocol_send_type type)
{
struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps");
if (pps)
pps->type = type;
return pps;
}
void lws_h2_init(struct lws *wsi)
{
wsi->h2.h2n->set = wsi->vhost->set;
@ -153,6 +164,7 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
* connection error (Section 5.4.1) of type PROTOCOL_ERROR.
*/
if (sid <= h2n->highest_sid_opened) {
lwsl_info("%s: tried to open lower sid %d\n", __func__, sid);
lws_h2_goaway(nwsi, H2_ERR_PROTOCOL_ERROR, "Bad sid");
return NULL;
}
@ -214,6 +226,86 @@ bail1:
return NULL;
}
struct lws *
lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
{
struct lws *nwsi = lws_get_network_wsi(parent_wsi);
/* no more children allowed by parent */
if (parent_wsi->h2.child_count + 1 >
parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
lwsl_notice("reached concurrent stream limit\n");
return NULL;
}
/* sid is set just before issuing the headers, ensuring monoticity */
wsi->seen_nonpseudoheader = 0;
wsi->client_h2_substream = 1;
wsi->h2.initialized = 1;
wsi->h2.parent_wsi = parent_wsi;
/* new guy's sibling is whoever was the first child before */
wsi->h2.sibling_list = parent_wsi->h2.child_list;
/* first child is now the new guy */
parent_wsi->h2.child_list = wsi;
parent_wsi->h2.child_count++;
wsi->h2.my_priority = 16;
wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
wsi->h2.peer_tx_cr_est = nwsi->vhost->set.s[H2SET_INITIAL_WINDOW_SIZE];
if (lws_ensure_user_space(wsi))
goto bail1;
wsi->state = LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS;
wsi->mode = LWSCM_HTTP2_CLIENT_ACCEPTED;
lws_callback_on_writable(wsi);
wsi->vhost->conn_stats.h2_subs++;
return wsi;
bail1:
/* undo the insert */
parent_wsi->h2.child_list = wsi->h2.sibling_list;
parent_wsi->h2.child_count--;
if (wsi->user_space)
lws_free_set_NULL(wsi->user_space);
wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_DESTROY, NULL, NULL, 0);
lws_free(wsi);
return NULL;
}
int lws_h2_issue_preface(struct lws *wsi)
{
struct lws_h2_netconn *h2n = wsi->h2.h2n;
struct lws_h2_protocol_send *pps;
if (lws_issue_raw(wsi, (uint8_t *)preface, strlen(preface)) !=
(int)strlen(preface))
return 1;
wsi->state = LWSS_HTTP2_CLIENT_ESTABLISHED;
wsi->mode = LWSCM_HTTP2_CLIENT_ACCEPTED;
h2n->count = 0;
wsi->h2.tx_cr = 65535;
/*
* we must send a settings frame
*/
pps = lws_h2_new_pps(LWS_H2_PPS_MY_SETTINGS);
if (!pps)
return 1;
lws_pps_schedule(wsi, pps);
lwsl_info("%s: h2 client sending settings\n", __func__);
return 0;
}
struct lws *
lws_h2_wsi_from_id(struct lws *parent_wsi, unsigned int sid)
{
@ -253,17 +345,6 @@ lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pps)
lws_callback_on_writable(wsi);
}
static struct lws_h2_protocol_send *
lws_h2_new_pps(enum lws_h2_protocol_send_type type)
{
struct lws_h2_protocol_send *pps = lws_malloc(sizeof(*pps), "pps");
if (pps)
pps->type = type;
return pps;
}
int
lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason)
{
@ -519,7 +600,7 @@ int lws_h2_do_pps_send(struct lws *wsi)
struct lws_h2_protocol_send *pps = NULL;
struct lws *cwsi;
uint8_t set[LWS_PRE + 64], *p = &set[LWS_PRE], *q;
int n, m = 0;
int n, m = 0, flags = 0;
if (!h2n)
return 1;
@ -542,6 +623,7 @@ int lws_h2_do_pps_send(struct lws *wsi)
switch (pps->type) {
case LWS_H2_PPS_MY_SETTINGS:
/*
* if any of our settings varies from h2 "default defaults"
* then we must inform the peer
@ -554,7 +636,7 @@ int lws_h2_do_pps_send(struct lws *wsi)
m += sizeof(h2n->one_setting);
}
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
0, LWS_H2_STREAM_ID_MASTER, m,
flags, LWS_H2_STREAM_ID_MASTER, m,
&set[LWS_PRE]);
if (n != m) {
lwsl_info("send %d %d\n", n, m);
@ -883,17 +965,9 @@ lws_h2_parse_frame_header(struct lws *wsi)
"Settings length error");
break;
}
lwsl_info("scheduled settings ack PPS\n");
/* non-ACK coming in means we must ACK it */
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
return 0;
pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
if (!pps)
return 1;
lws_pps_schedule(wsi, pps);
break;
}
/* came to us with ACK set... not allowed to have payload */
@ -943,6 +1017,16 @@ lws_h2_parse_frame_header(struct lws *wsi)
return 1;
}
#if !defined(LWS_NO_CLIENT)
if (wsi->client_h2_alpn) {
if (h2n->sid) {
h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
lwsl_info("HEADERS: nwsi %p: sid %d mapped to wsi %p\n", wsi, h2n->sid, h2n->swsi);
}
goto update_end_headers;
}
#endif
if (!h2n->swsi) {
/* no more children allowed by parent */
if (wsi->h2.child_count + 1 >
@ -1020,6 +1104,7 @@ update_end_headers:
/* no END_HEADERS means CONTINUATION must come */
h2n->swsi->h2.END_HEADERS =
!!(h2n->flags & LWS_H2_FLAG_END_HEADERS);
lwsl_info("%p: END_HEADERS %d\n", h2n->swsi, h2n->swsi->h2.END_HEADERS);
if (h2n->swsi->h2.END_HEADERS)
h2n->cont_exp = 0;
lwsl_debug("END_HEADERS %d\n", h2n->swsi->h2.END_HEADERS);
@ -1089,6 +1174,89 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
}
switch (h2n->type) {
case LWS_H2_FRAME_TYPE_SETTINGS:
#if !defined(LWS_NO_CLIENT)
if (wsi->client_h2_alpn &&
!(h2n->flags & LWS_H2_FLAG_SETTINGS_ACK)) {
/* migrate original client ask on to substream 1 */
wsi->http.fop_fd = NULL;
/*
* we need to treat the headers from the upgrade as the
* first job. So these need to get shifted to sid 1.
*/
h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi, 1);
if (!h2n->swsi)
return 1;
h2n->sid = 1;
assert(lws_h2_wsi_from_id(wsi, 1) == h2n->swsi);
wsi->state = LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS;
wsi->mode = LWSCM_HTTP2_CLIENT_ACCEPTED;
h2n->swsi->state = LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS;
h2n->swsi->mode = LWSCM_HTTP2_CLIENT_ACCEPTED;
/* pass on the initial headers to SID 1 */
h2n->swsi->ah = wsi->ah;
h2n->swsi->client_h2_substream = 1;
h2n->swsi->protocol = wsi->protocol;
h2n->swsi->user_space = wsi->user_space;
h2n->swsi->user_space_externally_allocated =
wsi->user_space_externally_allocated;
wsi->user_space = NULL;
if (h2n->swsi->ah)
h2n->swsi->ah->wsi = h2n->swsi;
wsi->ah = NULL;
lwsl_info("%s: MIGRATING nwsi %p: swsi %p\n", __func__,
wsi, h2n->swsi);
h2n->swsi->h2.tx_cr =
h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
lwsl_info("initial tx credit on conn %p: %d\n",
h2n->swsi, h2n->swsi->h2.tx_cr);
h2n->swsi->h2.initialized = 1;
lws_callback_on_writable(h2n->swsi);
pps = lws_h2_new_pps(LWS_H2_PPS_ACK_SETTINGS);
if (!pps)
return 1;
lws_pps_schedule(wsi, pps);
lwsl_info("%s: scheduled settings ack PPS\n", __func__);
/* also attach any queued guys */
/* we have a transaction queue that wants to pipeline */
lws_vhost_lock(wsi->vhost);
lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1,
wsi->dll_client_transaction_queue_head.next) {
struct lws *w = lws_container_of(d, struct lws,
dll_client_transaction_queue);
if (w->mode == LWSCM_WSCL_ISSUE_HANDSHAKE2) {
lwsl_info("%s: client pipeq %p to be h2\n",
__func__, w);
/* remove ourselves from the client queue */
lws_dll_lws_remove(&w->dll_client_transaction_queue);
/* attach ourselves as an h2 stream */
lws_wsi_h2_adopt(wsi, w);
}
} lws_end_foreach_dll_safe(d, d1);
lws_vhost_unlock(wsi->vhost);
}
#endif
break;
case LWS_H2_FRAME_TYPE_CONTINUATION:
case LWS_H2_FRAME_TYPE_HEADERS:
@ -1130,6 +1298,14 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
lwsl_info("http req, wsi=%p, h2n->swsi=%p\n", wsi, h2n->swsi);
h2n->swsi->hdr_parsing_completed = 1;
if (h2n->swsi->client_h2_substream) {
if (lws_client_interpret_server_handshake(h2n->swsi)) {
lws_h2_rst_stream(h2n->swsi, H2_ERR_STREAM_CLOSED,
"protocol CLI_EST closed it");
break;
}
}
if (lws_hdr_extant(h2n->swsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
h2n->swsi->http.rx_content_length = atoll(
lws_hdr_simple_ptr(h2n->swsi,
@ -1185,6 +1361,11 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
break;
}
if (h2n->swsi->client_h2_substream) {
lwsl_info("%s: headers: client path\n", __func__);
break;
}
if (!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_PATH) ||
!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_METHOD) ||
!lws_hdr_total_length(h2n->swsi, WSI_TOKEN_HTTP_COLON_SCHEME) ||
@ -1238,6 +1419,25 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
if (h2n->swsi->h2.END_STREAM &&
h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL)
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
if (h2n->swsi->client_h2_substream &&
h2n->flags & LWS_H2_FLAG_END_STREAM) {
lwsl_info("%s: %p: DATA: end stream\n", __func__, h2n->swsi);
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_OPEN)
lws_h2_state(h2n->swsi, LWS_H2_STATE_HALF_CLOSED_REMOTE);
if (h2n->swsi->h2.h2_state == LWS_H2_STATE_HALF_CLOSED_LOCAL) {
lws_h2_state(h2n->swsi, LWS_H2_STATE_CLOSED);
lws_h2_rst_stream(h2n->swsi, H2_ERR_NO_ERROR,
"client done");
if (lws_http_transaction_completed_client(h2n->swsi))
lwsl_debug("tx completed returned close\n");
}
}
break;
case LWS_H2_FRAME_TYPE_PING:
@ -1350,7 +1550,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
struct lws_h2_netconn *h2n = wsi->h2.h2n;
struct lws_h2_protocol_send *pps;
unsigned char c, *oldin = in;
int n;
int n, m;
if (!h2n)
goto fail;
@ -1359,6 +1559,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
c = *in++;
// lwsl_notice("%s: 0x%x\n", __func__, c);
switch (wsi->state) {
case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
if (preface[h2n->count++] != c)
@ -1383,6 +1585,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
lws_pps_schedule(wsi, pps);
break;
case LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS:
case LWSS_HTTP2_CLIENT_ESTABLISHED:
case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
case LWSS_HTTP2_ESTABLISHED:
if (h2n->frame_state != LWS_H2_FRAME_HEADER_LENGTH)
@ -1509,35 +1713,63 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
break;
}
h2n->swsi->outer_will_close = 1;
/*
* choose the length for this go so that we end at
* the frame boundary, in the case there is already
* more waiting leave it for next time around
* We operate on a frame. The RX we have at
* hand may exceed the current frame.
*/
n = (int)inlen + 1;
if (n > (int)(h2n->length - h2n->count + 1)) {
n = h2n->length - h2n->count + 1;
lwsl_debug("---- restricting len to %d vs %ld\n", n, (long)inlen + 1);
}
n = lws_read(h2n->swsi, in - 1, n);
h2n->swsi->outer_will_close = 0;
/*
* can return 0 in POST body with content len
* exhausted somehow.
*/
if (n <= 0) {
in += h2n->length - h2n->count;
h2n->inside = h2n->length;
h2n->count = h2n->length - 1;
lwsl_debug("%s: lws_read told %d\n", __func__, n);
goto close_swsi_and_return;
}
inlen -= n - 1;
in += n - 1;
h2n->inside += n;
h2n->count += n - 1;
if (h2n->swsi->client_h2_substream) {
m = user_callback_handle_rxflow(
h2n->swsi->protocol->callback,
h2n->swsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
h2n->swsi->user_space, in - 1, n);
in += n - 1;
h2n->inside += n;
h2n->count += n - 1;
inlen -= n - 1;
if (m) {
lwsl_info("RECEIVE_CLIENT_HTTP closed it\n");
goto close_swsi_and_return;
}
break;
} else {
h2n->swsi->outer_will_close = 1;
/*
* choose the length for this go so that we end at
* the frame boundary, in the case there is already
* more waiting leave it for next time around
*/
n = lws_read(h2n->swsi, in - 1, n);
h2n->swsi->outer_will_close = 0;
/*
* can return 0 in POST body with content len
* exhausted somehow.
*/
if (n <= 0) {
in += h2n->length - h2n->count;
h2n->inside = h2n->length;
h2n->count = h2n->length - 1;
lwsl_debug("%s: lws_read told %d\n", __func__, n);
goto close_swsi_and_return;
}
inlen -= n - 1;
in += n - 1;
h2n->inside += n;
h2n->count += n - 1;
}
/* account for both network and stream wsi windows */
@ -1697,6 +1929,103 @@ fail:
return 1;
}
int
lws_h2_client_handshake(struct lws *wsi)
{
uint8_t buf[LWS_PRE + 1024], *start = &buf[LWS_PRE],
*p = start, *end = &buf[sizeof(buf) - 1];
char *meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD),
*uri = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI);
struct lws *nwsi = lws_get_network_wsi(wsi);
struct lws_h2_protocol_send *pps;
int n;
/*
* The identifier of a newly established stream MUST be numerically
* greater than all streams that the initiating endpoint has opened or
* reserved. This governs streams that are opened using a HEADERS frame
* and streams that are reserved using PUSH_PROMISE. An endpoint that
* receives an unexpected stream identifier MUST respond with a
* connection error (Section 5.4.1) of type PROTOCOL_ERROR.
*/
int sid = nwsi->h2.h2n->highest_sid_opened + 2;
nwsi->h2.h2n->highest_sid_opened = sid;
wsi->h2.my_sid = sid;
lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n",
__func__, wsi->h2.my_sid);
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
if (!pps)
return 1;
pps->u.update_window.sid = sid;
pps->u.update_window.credit = 4 * 65536;
wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
lws_pps_schedule(wsi, pps);
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
if (!pps)
return 1;
pps->u.update_window.sid = 0;
pps->u.update_window.credit = 4 * 65536;
wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
lws_pps_schedule(wsi, pps);
/* it's time for us to send our client stream headers */
if (!meth)
meth = "GET";
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_COLON_METHOD,
(unsigned char *)meth,
strlen(meth), &p, end))
return -1;
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_COLON_SCHEME,
(unsigned char *)"http", 4,
&p, end))
return -1;
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_COLON_PATH,
(unsigned char *)uri,
lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI),
&p, end))
return -1;
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_COLON_AUTHORITY,
(unsigned char *)lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN),
lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_ORIGIN),
&p, end))
return -1;
/* give userland a chance to append, eg, cookies */
if (wsi->protocol->callback(wsi,
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
wsi->user_space, &p, (end - p) - 12))
return -1;
if (lws_finalize_http_header(wsi, &p, end))
return -1;
n = lws_write(wsi, start, p - start,
LWS_WRITE_HTTP_HEADERS);
if (n != (p - start)) {
lwsl_err("_write returned %d from %ld\n", n,
(long)(p - start));
return -1;
}
lws_h2_state(wsi, LWS_H2_STATE_OPEN);
wsi->state = LWSS_HTTP2_CLIENT_ESTABLISHED;
return 0;
}
int
lws_h2_ws_handshake(struct lws *wsi)
{

View file

@ -119,7 +119,7 @@ int lws_h2_configure_if_upgraded(struct lws *wsi)
cstr[len] = '\0';
lwsl_info("negotiated '%s' using ALPN\n", cstr);
wsi->use_ssl = 1;
wsi->use_ssl |= LCCSCF_USE_SSL;
if (strncmp((char *)name, "http/1.1", 8) == 0)
return 0;

View file

@ -540,6 +540,8 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason, const char *
lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_CLOSE, 1);
#if !defined(LWS_NO_CLIENT)
lws_free_set_NULL(wsi->client_hostname_copy);
/* we are no longer an active client connection that can piggyback */
lws_dll_lws_remove(&wsi->dll_active_client_conns);
@ -770,7 +772,7 @@ just_kill_connection:
} lws_end_foreach_llp(w, h2.sibling_list);
}
if (wsi->upgraded_to_http2 || wsi->http2_substream) {
if (wsi->upgraded_to_http2 || wsi->http2_substream || wsi->client_h2_substream) {
lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi);
if (wsi->h2.child_list) {
@ -805,7 +807,8 @@ just_kill_connection:
wsi->h2.h2n->pps = NULL;
}
if (wsi->http2_substream && wsi->h2.parent_wsi) {
if ((wsi->client_h2_substream || wsi->http2_substream) &&
wsi->h2.parent_wsi) {
lwsl_info(" %p: disentangling from siblings\n", wsi);
lws_start_foreach_llp(struct lws **, w,
wsi->h2.parent_wsi->h2.child_list) {
@ -1372,7 +1375,7 @@ lws_get_network_wsi(struct lws *wsi)
return NULL;
#if defined(LWS_WITH_HTTP2)
if (!wsi->http2_substream)
if (!wsi->http2_substream && !wsi->client_h2_substream)
return wsi;
while (wsi->h2.parent_wsi)
@ -2183,7 +2186,7 @@ LWS_VISIBLE int
lws_is_ssl(struct lws *wsi)
{
#ifdef LWS_OPENSSL_SUPPORT
return wsi->use_ssl;
return wsi->use_ssl & LCCSCF_USE_SSL;
#else
(void)wsi;
return 0;
@ -3109,7 +3112,7 @@ lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len)
,
vh->name, vh->listen_port,
#ifdef LWS_OPENSSL_SUPPORT
vh->use_ssl,
vh->use_ssl & LCCSCF_USE_SSL,
#else
0,
#endif

View file

@ -3282,7 +3282,12 @@ enum lws_client_connect_ssl_connection_flags {
LCCSCF_PIPELINE = (1 << 16),
/**< Serialize / pipeline multiple client connections
* on a single connection where possible. */
* on a single connection where possible.
*
* 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
* */
};
/** struct lws_client_connect_info - parameters to connect with when using

View file

@ -53,6 +53,8 @@ int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len)
int m;
#endif
// lwsl_hexdump_notice(buf, len);
/*
* Detect if we got called twice without going through the
* event loop to handle pending. This would be caused by either
@ -510,10 +512,11 @@ send_raw:
case LWS_WRITE_PING:
#ifdef LWS_WITH_HTTP2
/*
* ws-over-h2 ends up here after the ws framing applied
* ws-over-h2 also ends up here after the ws framing applied
*/
if (wsi->mode == LWSCM_HTTP2_SERVING ||
wsi->mode == LWSCM_HTTP2_WS_SERVING) {
wsi->mode == LWSCM_HTTP2_WS_SERVING ||
wsi->mode == LWSCM_HTTP2_CLIENT_ACCEPTED) {
unsigned char flags = 0;
n = LWS_H2_FRAME_TYPE_DATA;
@ -626,7 +629,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream);
while (!lws_send_pipe_choked(wsi)) {
do {
if (wsi->trunc_len) {
if (lws_issue_raw(wsi, wsi->trunc_alloc +
@ -698,7 +701,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
#if defined(LWS_WITH_HTTP2)
m = lws_h2_tx_cr_get(wsi);
if (!m) {
lwsl_info("%s: came here with no tx credit", __func__);
lwsl_info("%s: came here with no tx credit\n", __func__);
return 0;
}
if ((lws_filepos_t)m < poss)
@ -842,7 +845,7 @@ all_sent:
return 1; /* >0 indicates completed */
}
}
} while (0); // while (!lws_send_pipe_choked(wsi))
lws_callback_on_writable(wsi);

View file

@ -465,10 +465,16 @@ lws_callback_on_writable(struct lws *wsi)
lwsl_info("%s: %p (mode %d)\n", __func__, wsi, wsi->mode);
if (wsi->mode != LWSCM_HTTP2_SERVING &&
wsi->mode != LWSCM_HTTP2_CLIENT &&
wsi->mode != LWSCM_HTTP2_CLIENT_ACCEPTED &&
wsi->mode != LWSCM_HTTP2_WS_SERVING)
goto network_sock;
if (wsi->h2.requested_POLLOUT) {
if (wsi->h2.requested_POLLOUT
#if !defined(LWS_NO_CLIENT)
&& !wsi->client_h2_alpn
#endif
) {
lwsl_info("already pending writable\n");
return 1;
}
@ -506,7 +512,11 @@ lws_callback_on_writable(struct lws *wsi)
/* for network action, act only on the network wsi */
wsi = network_wsi;
if (already)
if (already && !wsi->client_h2_alpn
#if !defined(LWS_NO_CLIENT)
&& !wsi->client_h2_substream
#endif
)
return 1;
network_sock:
#endif

View file

@ -521,6 +521,10 @@ enum lws_connection_states {
LWSS_HTTP_DEFERRING_ACTION = _LSF_CCB | 19 |
_LSF_POLLOUT,
LWSS_HTTP2_CLIENT_SEND_SETTINGS = 20 | _LSF_POLLOUT,
LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS = 21 | _LSF_POLLOUT,
LWSS_HTTP2_CLIENT_ESTABLISHED = 22 | _LSF_POLLOUT,
};
#define lws_state_is_ws(s) (!!((s) & _LSF_WEBSOCKET))
@ -595,6 +599,8 @@ enum connection_mode {
/* HTTP Client related */
LWSCM_HTTP_CLIENT = LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP,
LWSCM_HTTP_CLIENT_ACCEPTED, /* actual HTTP service going on */
LWSCM_HTTP2_CLIENT,
LWSCM_HTTP2_CLIENT_ACCEPTED,
LWSCM_WSCL_WAITING_CONNECT,
LWSCM_WSCL_WAITING_PROXY_REPLY,
LWSCM_WSCL_ISSUE_HANDSHAKE,
@ -1200,6 +1206,7 @@ struct lws_context {
unsigned int protocol_init_done:1;
unsigned int ssl_gate_accepts:1;
unsigned int doing_protocol_init;
unsigned int done_protocol_destroy_cb;
/*
* set to the Thread ID that's doing the service loop just before entry
* to poll indicates service thread likely idling in poll()
@ -1554,6 +1561,7 @@ enum lws_h2_states {
#define LWS_H2_STREAM_ID_MASTER 0
#define LWS_H2_SETTINGS_LEN 6
#define LWS_H2_FLAG_SETTINGS_ACK 1
enum http2_hpack_state {
HPKS_TYPE,
@ -1916,6 +1924,7 @@ struct lws {
unsigned char *preamble_rx;
#ifndef LWS_NO_CLIENT
struct client_info_stash *stash;
char *client_hostname_copy;
struct lws_dll_lws dll_active_client_conns;
struct lws_dll_lws dll_client_transaction_queue_head;
struct lws_dll_lws dll_client_transaction_queue;
@ -2011,6 +2020,8 @@ struct lws {
unsigned int keepalive_active:1;
unsigned int keepalive_rejected:1;
unsigned int client_pipeline:1;
unsigned int client_h2_alpn:1;
unsigned int client_h2_substream:1;
#endif
#ifdef LWS_WITH_HTTP_PROXY
unsigned int perform_rewrite:1;
@ -2019,7 +2030,7 @@ struct lws {
unsigned int extension_data_pending:1;
#endif
#ifdef LWS_OPENSSL_SUPPORT
unsigned int use_ssl:4;
unsigned int use_ssl;
#endif
#ifdef _WIN32
unsigned int sock_send_blocking:1;
@ -2281,6 +2292,11 @@ lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pss);
LWS_EXTERN const struct http2_settings lws_h2_defaults;
LWS_EXTERN int
lws_h2_ws_handshake(struct lws *wsi);
LWS_EXTERN int lws_h2_issue_preface(struct lws *wsi);
LWS_EXTERN int
lws_h2_client_handshake(struct lws *wsi);
LWS_EXTERN struct lws *
lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi);
#else
#define lws_h2_configure_if_upgraded(x)
#endif

View file

@ -52,6 +52,8 @@ lws_calllback_as_writeable(struct lws *wsi)
n = LWS_CALLBACK_CLIENT_WRITEABLE;
break;
case LWSCM_WSCL_ISSUE_HTTP_BODY:
case LWSCM_HTTP2_CLIENT:
case LWSCM_HTTP2_CLIENT_ACCEPTED:
n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE;
break;
case LWSCM_HTTP2_WS_SERVING:
@ -84,6 +86,9 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
struct lws_tokens eff_buf;
int ret, m;
#endif
lwsl_info("%s: %p\n", __func__, wsi);
vwsi->leave_pollout_active = 0;
vwsi->handling_pollout = 1;
/*
@ -124,7 +129,11 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
/*
* Priority 2: protocol packets
*/
if (wsi->upgraded_to_http2 && wsi->h2.h2n->pps) {
if ((wsi->upgraded_to_http2
#if !defined(LWS_NO_CLIENT)
|| wsi->client_h2_alpn
#endif
) && wsi->h2.h2n->pps) {
lwsl_info("servicing pps\n");
if (lws_h2_do_pps_send(wsi)) {
wsi->socket_is_permanently_unusable = 1;
@ -376,6 +385,7 @@ user_service:
}
if (wsi->mode != LWSCM_WSCL_ISSUE_HTTP_BODY &&
wsi->mode != LWSCM_HTTP2_CLIENT_ACCEPTED &&
!wsi->hdr_parsing_completed)
goto bail_ok;
@ -399,11 +409,15 @@ user_service_go_again:
*/
if (wsi->mode != LWSCM_HTTP2_SERVING &&
wsi->mode != LWSCM_HTTP2_WS_SERVING) {
wsi->mode != LWSCM_HTTP2_WS_SERVING &&
wsi->mode != LWSCM_HTTP2_CLIENT &&
wsi->mode != LWSCM_HTTP2_CLIENT_ACCEPTED) {
lwsl_info("%s: non http2\n", __func__);
goto notify;
}
wsi = lws_get_network_wsi(wsi);
wsi->h2.requested_POLLOUT = 0;
if (!wsi->h2.initialized) {
lwsl_info("pollout on uninitialized http2 conn\n");
@ -465,8 +479,7 @@ user_service_go_again:
}
w->h2.requested_POLLOUT = 0;
lwsl_info("%s: child %p (state %d)\n", __func__, (*wsi2),
(*wsi2)->state);
lwsl_info("%s: child %p (state %d)\n", __func__, w, w->state);
/* if we arrived here, even by looping, we checked choked */
w->could_have_pending = 0;
@ -477,13 +490,20 @@ user_service_go_again:
n = lws_write(w, (uint8_t *)w->h2.pending_status_body +
LWS_PRE,
strlen(w->h2.pending_status_body +
LWS_PRE), LWS_WRITE_HTTP_FINAL);
LWS_PRE), LWS_WRITE_HTTP_FINAL);
lws_free_set_NULL(w->h2.pending_status_body);
lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 end stream 1");
wa = &wsi->h2.child_list;
goto next_child;
}
if (w->state == LWSS_HTTP2_CLIENT_WAITING_TO_SEND_HEADERS) {
if (lws_h2_client_handshake(w))
return -1;
goto next_child;
}
if (w->state == LWSS_HTTP2_DEFERRING_ACTION) {
/*
@ -602,7 +622,7 @@ user_service_go_again:
}
if (lws_calllback_as_writeable(w) || w->h2.send_END_STREAM) {
lwsl_debug("Closing POLLOUT child\n");
lwsl_info("Closing POLLOUT child (end stream %d)\n", w->h2.send_END_STREAM);
lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS, "h2 pollout handle");
wa = &wsi->h2.child_list;
}
@ -1284,8 +1304,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd,
*/
wsi = NULL;
lws_start_foreach_ll(struct lws_vhost *, v,
context->vhost_list) {
lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) {
struct lws_timed_vh_protocol *nx;
if (v->timed_vh_protocol_list) {
lws_start_foreach_ll(struct lws_timed_vh_protocol *,
@ -1539,6 +1558,10 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd,
case LWSCM_HTTP2_SERVING:
case LWSCM_HTTP2_WS_SERVING:
case LWSCM_HTTP_CLIENT_ACCEPTED:
case LWSCM_HTTP2_CLIENT_ACCEPTED:
case LWSCM_HTTP2_CLIENT:
// lwsl_notice("%s: mode %d, state %d\n", __func__, wsi->mode, wsi->state);
/* 1: something requested a callback when it was OK to write */
@ -1669,8 +1692,8 @@ read:
break;
}
if (wsi->ah) {
lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi);
if (wsi->ah && wsi->ah->rxlen - wsi->ah->rxpos) {
lwsl_info("%s: %p: inherited ah rx %d\n", __func__, wsi, wsi->ah->rxlen - wsi->ah->rxpos);
eff_buf.token_len = wsi->ah->rxlen -
wsi->ah->rxpos;
eff_buf.token = (char *)wsi->ah->rx +
@ -1732,7 +1755,8 @@ read:
n = 0;
goto handled;
case LWS_SSL_CAPABLE_ERROR:
lwsl_info("Closing when error\n");
lwsl_info("%s: LWS_SSL_CAPABLE_ERROR\n",
__func__);
goto close_and_handled;
}
// lwsl_notice("Actual RX %d\n", eff_buf.token_len);
@ -1741,7 +1765,8 @@ read:
drain:
#ifndef LWS_NO_CLIENT
if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED &&
if ((wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED ||
wsi->mode == LWSS_HTTP2_CLIENT_ESTABLISHED) &&
!wsi->told_user_closed) {
/*
@ -1816,8 +1841,12 @@ drain:
eff_buf.token_len = 0;
} while (m);
if (wsi->ah) {
lwsl_debug("%s: %p: detaching\n", __func__, wsi);
if (wsi->ah
#if !defined(LWS_NO_CLIENT)
&& !wsi->client_h2_alpn
#endif
) {
lwsl_notice("%s: %p: detaching ah\n", __func__, wsi);
lws_header_table_force_to_detachable_state(wsi);
lws_header_table_detach(wsi, 0);
}

View file

@ -33,11 +33,21 @@ struct alpn_ctx {
};
static struct alpn_ctx protos = { (unsigned char *)
"\x08http/1.1", 3 + 9 };
#if defined(LWS_WITH_HTTP2)
"\x02h2"
#endif
"\x08http/1.1", 3 +
#if defined(LWS_WITH_HTTP2)
3 +
#endif
9 };
static struct alpn_ctx protos_h1 = { (unsigned char *)"\x08http/1.1", 3 + 9 };
int
lws_ssl_client_bio_create(struct lws *wsi)
{
struct alpn_ctx *apro = &protos;
X509_VERIFY_PARAM *param;
char hostname[128], *p;
@ -65,7 +75,10 @@ lws_ssl_client_bio_create(struct lws *wsi)
if (!wsi->ssl)
return -1;
SSL_set_alpn_select_cb(wsi->ssl, &protos);
if (wsi->use_ssl & LCCSCF_NOT_H2)
apro = &protos_h1;
SSL_set_alpn_select_cb(wsi->ssl, apro);
if (wsi->vhost->ssl_info_event_mask)
SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback);
@ -104,7 +117,10 @@ lws_tls_client_connect(struct lws *wsi)
if (n == 1) {
SSL_get0_alpn_selected(wsi->ssl, &prot, &len);
#if !defined(LWS_NO_CLIENT)
if (prot && !strcmp((char *)prot, "h2"))
wsi->client_h2_alpn = 1;
#endif
if (prot && !strcmp((char *)prot, "http/1.1"))
/*
* If alpn asserts it is http/1.1, KA is mandatory.

View file

@ -88,6 +88,9 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
#if defined(LWS_HAVE_SSL_set_alpn_protos) && defined(LWS_HAVE_SSL_get0_alpn_selected)
static const unsigned char client_alpn_protocols[] = {
#if defined(LWS_WITH_HTTP2)
2, 'h', '2',
#endif
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
#endif
@ -133,6 +136,10 @@ lws_ssl_client_bio_create(struct lws *wsi)
}
#if defined(LWS_HAVE_SSL_set_alpn_protos) && defined(LWS_HAVE_SSL_get0_alpn_selected)
if (wsi->use_ssl & LCCSCF_NOT_H2) {
plist += 3;
n -= 3;
}
SSL_set_alpn_protos(wsi->ssl, plist, n);
#endif
@ -234,7 +241,12 @@ lws_tls_client_connect(struct lws *wsi)
len = sizeof(a) - 1;
memcpy(a, (const char *)prot, len);
a[len] = '\0';
#if !defined(LWS_NO_CLIENT)
if (prot && !strcmp(a, "h2")) {
lwsl_info("%s: upgraded to H2\n", __func__);
wsi->client_h2_alpn = 1;
}
#endif
if (prot && !strcmp(a, "http/1.1"))
/*
* If alpn asserts it is http/1.1, KA is mandatory.
@ -245,7 +257,7 @@ lws_tls_client_connect(struct lws *wsi)
*/
wsi->keepalive_active = 1;
lwsl_notice("client connect OK\n");
lwsl_info("client connect OK\n");
#endif
return LWS_SSL_CAPABLE_DONE;
}

View file

@ -257,6 +257,8 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
lws_restart_ws_ping_pong_timer(wsi);
// lwsl_hexdump_err(buf, n);
/*
* if it was our buffer that limited what we read,
* check if SSL has additional data pending inside SSL buffers.

View file

@ -23,8 +23,9 @@
* info.ssl_connection member (this is independent of whether the connection
* is in ssl mode or not).
*
* Pipelined connections are slower (2.3s vs 1.6s for 8 connections), since the
* transfers are serialized, but it is much less resource-intensive.
* HTTP/1.0: Pipelining only possible if Keep-Alive: yes sent by server
* HTTP/1.1: always possible... serializes requests
* HTTP/2: always possible... all requests sent as individual streams in parallel
*/
#include <libwebsockets.h>
@ -34,6 +35,7 @@
#include <time.h>
#define COUNT 8
//#define STAGGERED_CONNECTIONS
struct user {
int index;
@ -58,7 +60,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
client_wsi[u->index] = NULL;
failed++;
if (++completed == COUNT) {
lwsl_user("Done: failed: %d\n", failed);
lwsl_err("Done: failed: %d\n", failed);
interrupted = 1;
}
break;
@ -93,9 +95,13 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
return 0; /* don't passthru */
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %d\n", u->index);
client_wsi[u->index] = NULL;
if (++completed == COUNT) {
lwsl_user("Done: failed: %d\n", failed);
if (!failed)
lwsl_user("Done: all OK\n");
else
lwsl_err("Done: failed: %d\n", failed);
interrupted = 1;
/* so we exit immediately */
lws_cancel_service(lws_get_context(wsi));
@ -120,13 +126,65 @@ sigint_handler(int sig)
interrupted = 1;
}
unsigned long long us(void)
{
struct timeval t;
gettimeofday(&t, NULL);
return (t.tv_sec * 1000000ull) + t.tv_usec;
}
static void
lws_try_client_connection(struct lws_context *context, int m)
{
struct lws_client_connect_info i;
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
#if 0
i.port = 7681;
i.address = "localhost";
#else
i.port = 443;
i.address = "warmcat.com";
#endif
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.ssl_connection = LCCSCF_PIPELINE | /* enables h1 or h2 connection sharing */
// LCCSCF_NOT_H2 | /* forces http/1 */
LCCSCF_ALLOW_SELFSIGNED | /* allow selfsigned cert */
LCCSCF_USE_SSL;
i.method = "GET";
i.protocol = protocols[0].name;
i.pwsi = &client_wsi[m];
user[m].index = m;
i.userdata = &user[m];
if (!lws_client_connect_via_info(&i)) {
failed++;
if (++completed == COUNT) {
lwsl_user("Done: failed: %d\n", failed);
interrupted = 1;
}
} else
lwsl_user("started connection %d\n", m);
}
int main(int argc, char **argv)
{
struct lws_context_creation_info info;
struct lws_client_connect_info i;
struct lws_context *context;
struct timeval start, end;
int n = 0;
unsigned long long start
#if defined(STAGGERED_CONNECTIONS)
, next
#endif
;
int n = 0, m;
signal(SIGINT, sigint_handler);
lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
@ -160,44 +218,40 @@ int main(int argc, char **argv)
return 1;
}
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
#if !defined(STAGGERED_CONNECTIONS)
/*
* just pile on all the connections at once, testing the queueing
*/
for (m = 0; m < (int)LWS_ARRAY_SIZE(client_wsi); m++)
lws_try_client_connection(context, m);
#else
next =
#endif
start = us();
m = 0;
while (n >= 0 && !interrupted) {
i.port = 443;
i.address = "warmcat.com";
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.ssl_connection = LCCSCF_PIPELINE /* enables http1.1 pipelining */ |
LCCSCF_USE_SSL;
i.method = "GET";
#if defined(STAGGERED_CONNECTIONS)
/*
* open the connections at 100ms intervals, with the last
* one being after 1s, testing queueing, and direct H2 stream
* addition stability
*/
if (us() > next && m < (int)LWS_ARRAY_SIZE(client_wsi)) {
i.protocol = protocols[0].name;
lws_try_client_connection(context, m++);
gettimeofday(&start, NULL);
for (n = 0; n < (int)LWS_ARRAY_SIZE(client_wsi); n++) {
i.pwsi = &client_wsi[n];
user[n].index = n;
i.userdata = &user[n];
if (!lws_client_connect_via_info(&i)) {
failed++;
if (++completed == COUNT) {
lwsl_user("Done: failed: %d\n", failed);
interrupted = 1;
}
if (m == (int)LWS_ARRAY_SIZE(client_wsi) - 1)
next = us() + 1000000;
else
next = us() + 100000;
}
#endif
n = lws_service(context, 1000);
}
while (n >= 0 && !interrupted)
n = lws_service(context, 1000);
gettimeofday(&end, NULL);
lwsl_user("Duration: %lldms\n",
(((end.tv_sec * 1000000ll) + end.tv_usec) -
((start.tv_sec * 1000000ll) + start.tv_usec)) / 1000);
lwsl_user("Duration: %lldms\n", (us() - start) / 1000);
lws_context_destroy(context);
lwsl_user("Completed\n");

View file

@ -61,6 +61,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
return 0; /* don't passthru */
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
client_wsi = NULL;
break;
@ -132,7 +133,7 @@ int main(int argc, char **argv)
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.ssl_connection = 1;
i.ssl_connection = /* LCCSCF_NOT_H2 | */ LCCSCF_USE_SSL;
i.method = "GET";
i.protocol = protocols[0].name;

View file

@ -82,7 +82,7 @@ int main(int argc, char **argv)
/* for LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
| LLL_INFO /* | LLL_PARSER */ /* | LLL_HEADER */
/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
/* | LLL_DEBUG */, NULL);
@ -114,7 +114,7 @@ int main(int argc, char **argv)
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.ssl_connection = 1;
i.ssl_connection = LCCSCF_USE_SSL;
i.protocol = protocols[0].name; /* "dumb-increment-protocol" */
i.pwsi = &client_wsi;

View file

@ -442,7 +442,9 @@ int main(int argc, char **argv)
lwsl_notice(" Using resource path \"%s\"\n", resource_path);
#if UV_VERSION_MAJOR > 0
uv_loop_init(&loop);
#endif
#if defined(TEST_DYNAMIC_VHOST)
uv_timer_init(&loop, &timeout_watcher);
#endif
@ -561,9 +563,9 @@ bail:
#endif
/* nothing left in the foreign loop, destroy it */
#if UV_VERSION_MAJOR > 0
uv_loop_close(&loop);
#endif
lwsl_notice("libwebsockets-test-server exited cleanly\n");
#ifndef _WIN32