diff --git a/lib/http2.c b/lib/http2.c index ab08b137..24d5ba41 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -74,6 +74,7 @@ lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebs parent_wsi->u.http2.child_count++; wsi->u.http2.my_priority = 16; + wsi->u.http2.tx_credit = 65535; wsi->state = WSI_STATE_HTTP2_ESTABLISHED; wsi->mode = parent_wsi->mode; @@ -156,7 +157,13 @@ int lws_http2_frame_write(struct libwebsocket *wsi, int type, int flags, unsigne *p++ = sid; lwsl_info("%s: %p (eff %p). type %d, flags 0x%x, sid=%d, len=%d\n", - __func__, wsi, wsi_eff, type, flags, sid, len); + __func__, wsi, wsi_eff, type, flags, sid, len, wsi->u.http2.tx_credit); + + if (type == LWS_HTTP2_FRAME_TYPE_DATA) { + if (wsi->u.http2.tx_credit < len) + lwsl_err("%s: %p: sending payload len %d but tx_credit only %d!\n", len, wsi->u.http2.tx_credit); + wsi->u.http2.tx_credit -= len; + } n = lws_issue_raw(wsi_eff, &buf[-LWS_HTTP2_FRAME_HEADER_LENGTH], len + LWS_HTTP2_FRAME_HEADER_LENGTH); if (n >= LWS_HTTP2_FRAME_HEADER_LENGTH) @@ -195,6 +202,7 @@ lws_http2_parser(struct libwebsocket_context *context, lwsl_info("http2: %p: established\n", wsi); wsi->state = WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS; wsi->u.http2.count = 0; + wsi->u.http2.tx_credit = 65535; /* * we must send a settings frame -- empty one is OK... @@ -255,6 +263,8 @@ lws_http2_parser(struct libwebsocket_context *context, } break; case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: + wsi->u.http2.hpack_e_dep <<= 8; + wsi->u.http2.hpack_e_dep |= c; break; } if (wsi->u.http2.count != wsi->u.http2.length) @@ -264,6 +274,7 @@ lws_http2_parser(struct libwebsocket_context *context, wsi->u.http2.frame_state = 0; wsi->u.http2.count = 0; + swsi = wsi->u.http2.stream_wsi; /* set our initial window size */ if (!wsi->u.http2.initialized) { wsi->u.http2.tx_credit = wsi->u.http2.peer_settings.setting[LWS_HTTP2_SETTINGS__INITIAL_WINDOW_SIZE]; @@ -274,7 +285,7 @@ lws_http2_parser(struct libwebsocket_context *context, case LWS_HTTP2_FRAME_TYPE_HEADERS: /* service the http request itself */ lwsl_info("servicing initial http request, wsi=%p, stream wsi=%p\n", wsi, wsi->u.http2.stream_wsi); - n = lws_http_action(context, wsi->u.http2.stream_wsi); + n = lws_http_action(context, swsi); lwsl_info(" action result %d\n", n); break; case LWS_HTTP2_FRAME_TYPE_PING: @@ -283,6 +294,17 @@ lws_http2_parser(struct libwebsocket_context *context, lws_set_protocol_write_pending(context, wsi, LWS_PPS_HTTP2_PONG); } break; + case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: + wsi->u.http2.hpack_e_dep &= ~(1 << 31); + if ((long long)swsi->u.http2.tx_credit + (unsigned long long)wsi->u.http2.hpack_e_dep > (~(1 << 31))) + return 1; /* actually need to close swsi not the whole show */ + swsi->u.http2.tx_credit += wsi->u.http2.hpack_e_dep; + if (swsi->u.http2.waiting_tx_credit && swsi->u.http2.tx_credit > 0) { + lwsl_info("%s: %p: waiting_tx_credit -> wait on writeable\n", __func__, wsi); + swsi->u.http2.waiting_tx_credit = 0; + libwebsocket_callback_on_writable(context, swsi); + } + break; } break; } @@ -307,7 +329,6 @@ lws_http2_parser(struct libwebsocket_context *context, case 8: wsi->u.http2.stream_id <<= 8; wsi->u.http2.stream_id |= c; - wsi->u.http2.stream_wsi = wsi; break; } if (wsi->u.http2.frame_state == LWS_HTTP2_FRAME_HEADER_LENGTH) { /* frame header complete */ @@ -315,6 +336,9 @@ lws_http2_parser(struct libwebsocket_context *context, wsi->u.http2.type, wsi->u.http2.flags, wsi->u.http2.stream_id, wsi->u.http2.length); wsi->u.http2.count = 0; + wsi->u.http2.stream_wsi = wsi; + if (wsi->u.http2.stream_id) + wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id); switch (wsi->u.http2.type) { case LWS_HTTP2_FRAME_TYPE_SETTINGS: @@ -342,7 +366,6 @@ lws_http2_parser(struct libwebsocket_context *context, lwsl_info("LWS_HTTP2_FRAME_TYPE_HEADERS: stream_id = %d\n", wsi->u.http2.stream_id); if (!wsi->u.http2.stream_id) return 1; - wsi->u.http2.stream_wsi = lws_http2_wsi_from_id(wsi, wsi->u.http2.stream_id); if (!wsi->u.http2.stream_wsi) wsi->u.http2.stream_wsi = lws_create_server_child_wsi(context, wsi, wsi->u.http2.stream_id); @@ -374,6 +397,10 @@ update_end_headers: swsi->u.http2.hpack = HPKS_TYPE; lwsl_info("initial hpack state %d\n", swsi->u.http2.hpack); break; + case LWS_HTTP2_FRAME_TYPE_WINDOW_UPDATE: + if (wsi->u.http2.length != 4) + return 1; + break; } if (wsi->u.http2.length == 0) wsi->u.http2.frame_state = 0; @@ -391,6 +418,8 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc struct libwebsocket *swsi; int n, m = 0; + lwsl_debug("%s: %p: %d\n", __func__, wsi, wsi->pps); + switch (wsi->pps) { case LWS_PPS_HTTP2_MY_SETTINGS: for (n = 1; n < LWS_HTTP2_SETTINGS__COUNT; n++) diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 5b365165..5cf78eba 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -821,7 +821,7 @@ void lws_set_protocol_write_pending(struct libwebsocket_context *context, struct libwebsocket *wsi, enum lws_pending_protocol_send pend) { - lwsl_err("setting pps %d\n", pend); + lwsl_info("setting pps %d\n", pend); if (wsi->pps) lwsl_err("pps overwrite\n"); @@ -829,3 +829,20 @@ void lws_set_protocol_write_pending(struct libwebsocket_context *context, libwebsocket_rx_flow_control(wsi, 0); libwebsocket_callback_on_writable(context, wsi); } + +LWS_VISIBLE size_t +lws_get_peer_write_allowance(struct libwebsocket *wsi) +{ +#ifdef LWS_USE_HTTP2 + /* only if we are using HTTP2 on this connection */ + if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING) + return -1; + /* user is only interested in how much he can send, or that he can't */ + if (wsi->u.http2.tx_credit <= 0) + return 0; + + return wsi->u.http2.tx_credit; +#else + return -1; +#endif +} diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 0ed5592d..ca382ce4 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -153,9 +153,11 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len); #define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG - /* the struct libwebsocket_protocols has the id field present */ +/* the struct libwebsocket_protocols has the id field present */ #define LWS_FEATURE_PROTOCOLS_HAS_ID_FIELD +/* you can call lws_get_peer_write_allowance */ +#define LWS_FEATURE_PROTOCOLS_HAS_PEER_WRITE_ALLOWANCE enum libwebsocket_context_options { LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT = 2, @@ -1226,6 +1228,25 @@ libwebsocket_rx_flow_allow_all_protocol( LWS_VISIBLE LWS_EXTERN size_t libwebsockets_remaining_packet_payload(struct libwebsocket *wsi); +/* + * if the protocol does not have any guidence, returns -1. Currently only + * http2 connections get send window information from this API. But your code + * should use it so it can work properly with any protocol. + * + * If nonzero return is the amount of payload data the peer or intermediary has + * reported it has buffer space for. That has NO relationship with the amount + * of buffer space your OS can accept on this connection for a write action. + * + * This number represents the maximum you could send to the peer or intermediary + * on this connection right now without it complaining. + * + * lws manages accounting for send window updates and payload writes + * automatically, so this number reflects the situation at the peer or + * intermediary dynamically. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_get_peer_write_allowance(struct libwebsocket *wsi); + LWS_VISIBLE LWS_EXTERN struct libwebsocket * libwebsocket_client_connect(struct libwebsocket_context *clients, const char *address, diff --git a/lib/pollfd.c b/lib/pollfd.c index 63cdfc36..10fd8108 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -207,6 +207,19 @@ libwebsocket_callback_on_writable(struct libwebsocket_context *context, return 1; } + if (wsi->u.http2.tx_credit <= 0) { + /* + * other side is not able to cope with us sending + * anything so no matter if we have POLLOUT on our side. + * + * Delay waiting for our POLLOUT until peer indicates he has + * space for more using tx window command in http2 layer + */ + lwsl_info("%s: %p: waiting_tx_credit (%d)\n", __func__, wsi, wsi->u.http2.tx_credit); + wsi->u.http2.waiting_tx_credit = 1; + return 0; + } + network_wsi = lws_http2_get_network_wsi(wsi); already = network_wsi->u.http2.requested_POLLOUT; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index ac7e7c3e..da9c7a1c 100755 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -708,6 +708,7 @@ struct _lws_http2_related { unsigned int send_END_STREAM:1; unsigned int GOING_AWAY; unsigned int requested_POLLOUT:1; + unsigned int waiting_tx_credit:1; /* hpack */ enum http2_hpack_state hpack; @@ -720,7 +721,8 @@ struct _lws_http2_related { unsigned int huff:1; unsigned int value:1; - unsigned int tx_credit; + /* negative credit is mandated by the spec */ + int tx_credit; unsigned int my_stream_id; unsigned int child_count; int my_priority; diff --git a/lib/server.c b/lib/server.c index 1c6afd24..3e7e471b 100644 --- a/lib/server.c +++ b/lib/server.c @@ -426,9 +426,9 @@ upgrade_h2c: "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Upgrade: h2c\x0d\x0a\x0d\x0a"); - n = lws_issue_raw(wsi, (unsigned char *)wsi->protocol->name, - strlen(wsi->protocol->name)); - if (n != strlen(wsi->protocol->name)) { + n = lws_issue_raw(wsi, (unsigned char *)protocol_list, + strlen(protocol_list)); + if (n != strlen(protocol_list)) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } diff --git a/lib/ssl-http2.c b/lib/ssl-http2.c index 134231f2..832d9150 100644 --- a/lib/ssl-http2.c +++ b/lib/ssl-http2.c @@ -140,8 +140,6 @@ void lws_http2_configure_if_upgraded(struct libwebsocket *wsi) ah = wsi->u.hdr.ah; - wsi->mode = LWS_CONNMODE_HTTP2_SERVING; - /* union transition */ memset(&wsi->u, 0, sizeof(wsi->u));