diff --git a/READMEs/README.coding.md b/READMEs/README.coding.md index 98660734..74c57565 100644 --- a/READMEs/README.coding.md +++ b/READMEs/README.coding.md @@ -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 diff --git a/lib/client/client-handshake.c b/lib/client/client-handshake.c index c3bc1473..18b23ddc 100644 --- a/lib/client/client-handshake.c +++ b/lib/client/client-handshake.c @@ -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; } diff --git a/lib/client/client.c b/lib/client/client.c index 3827d3ed..e7b2a795 100644 --- a/lib/client/client.c +++ b/lib/client/client.c @@ -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; diff --git a/lib/handshake.c b/lib/handshake.c index 1f8a928c..77d0cc1f 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -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: diff --git a/lib/header.c b/lib/header.c index 4c6fabc8..7c2ef9b7 100644 --- a/lib/header.c +++ b/lib/header.c @@ -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 diff --git a/lib/http2/http2.c b/lib/http2/http2.c index 7ece1fb1..009df817 100644 --- a/lib/http2/http2.c +++ b/lib/http2/http2.c @@ -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) { diff --git a/lib/http2/ssl-http2.c b/lib/http2/ssl-http2.c index f80b95e5..035708fc 100644 --- a/lib/http2/ssl-http2.c +++ b/lib/http2/ssl-http2.c @@ -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; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 0a3d4469..486a5073 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -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 diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 855ee273..91fef0f0 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -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 diff --git a/lib/output.c b/lib/output.c index be3a4e73..1b6b356d 100644 --- a/lib/output.c +++ b/lib/output.c @@ -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); diff --git a/lib/pollfd.c b/lib/pollfd.c index 809fad6a..d3b26679 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -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 diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 1ddcd0fe..6504fe1b 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -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 diff --git a/lib/service.c b/lib/service.c index a520abcd..11201846 100644 --- a/lib/service.c +++ b/lib/service.c @@ -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); } diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index 37e11602..7f674736 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -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. diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c index 87758101..86682618 100644 --- a/lib/tls/openssl/openssl-client.c +++ b/lib/tls/openssl/openssl-client.c @@ -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; } diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c index 686e8417..2d43c7ae 100644 --- a/lib/tls/openssl/ssl.c +++ b/lib/tls/openssl/ssl.c @@ -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. diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index d204cbe6..a1a213de 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -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 @@ -34,6 +35,7 @@ #include #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"); diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c index 67b00768..6796346f 100644 --- a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c @@ -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; diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c index 1892a357..5c82597a 100644 --- a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c +++ b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c @@ -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; diff --git a/test-apps/test-server-v2.0.c b/test-apps/test-server-v2.0.c index 1531beaa..ee2c032e 100644 --- a/test-apps/test-server-v2.0.c +++ b/test-apps/test-server-v2.0.c @@ -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