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

h2: LCCSCF_H2_MANUAL_RXFLOW and refactor txcr

This changes the approach of tx credit management to set the
initial stream tx credit window to zero.  This is the only way
with RFC7540 to gain the ability to selectively precisely rx
flow control incoming streams.

At the time the headers are sent, a WINDOW_UPDATE is sent with
the initial tx credit towards us for that specific stream.  By
default, this acts as before with a 256KB window added for both
the stream and the nwsi, and additional window management sent
as stuff is received.

It's now also possible to set a member in the client info
struct and a new option LCCSCF_H2_MANUAL_RXFLOW to precisely
manage both the initial tx credit for a specific stream and
the ongoing rate limit by meting out further tx credit
manually.

Add another minimal example http-client-h2-rxflow demonstrating how
to force a connection's peer's initial budget to transmit to us
and control it during the connection lifetime to restrict the amount
of incoming data we have to buffer.
This commit is contained in:
Andy Green 2019-12-26 02:35:41 +00:00
parent f33b3443e3
commit 5dcf1b1ad9
17 changed files with 865 additions and 138 deletions

View file

@ -44,8 +44,9 @@ enum lws_client_connect_ssl_connection_flags {
LCCSCF_H2_QUIRK_OVERFLOWS_TXCR = (1 << 6),
LCCSCF_H2_AUTH_BEARER = (1 << 7),
LCCSCF_H2_HEXIFY_AUTH_TOKEN = (1 << 8),
LCCSCF_HTTP_MULTIPART_MIME = (1 << 9),
LCCSCF_HTTP_X_WWW_FORM_URLENCODED = (1 << 10),
LCCSCF_H2_MANUAL_RXFLOW = (1 << 9),
LCCSCF_HTTP_MULTIPART_MIME = (1 << 10),
LCCSCF_HTTP_X_WWW_FORM_URLENCODED = (1 << 11),
LCCSCF_PIPELINE = (1 << 16),
/**< Serialize / pipeline multiple client connections
@ -145,6 +146,11 @@ struct lws_client_connect_info {
* Currently only the idle parts are applied to the connection.
*/
int manual_initial_tx_credit;
/**< if LCCSCF_H2_MANUAL_REFLOW is set, this becomes the initial tx
* credit for the stream.
*/
uint8_t sys_tls_client_cert;
/**< 0 means no client cert. 1+ means apply lws_system client cert 0+
* to the client connection.

View file

@ -337,6 +337,19 @@ struct lws_token_limits {
unsigned short token_limit[WSI_TOKEN_COUNT]; /**< max chars for this token */
};
enum lws_h2_settings {
H2SET_HEADER_TABLE_SIZE = 1,
H2SET_ENABLE_PUSH,
H2SET_MAX_CONCURRENT_STREAMS,
H2SET_INITIAL_WINDOW_SIZE,
H2SET_MAX_FRAME_SIZE,
H2SET_MAX_HEADER_LIST_SIZE,
H2SET_RESERVED7,
H2SET_ENABLE_CONNECT_PROTOCOL, /* defined in mcmanus-httpbis-h2-ws-02 */
H2SET_COUNT /* always last */
};
/**
* lws_token_to_string() - returns a textual representation of a hdr token index
*
@ -802,5 +815,50 @@ lws_http_compression_apply(struct lws *wsi, const char *name,
*/
LWS_VISIBLE LWS_EXTERN int
lws_http_is_redirected_to_get(struct lws *wsi);
/**
* lws_h2_update_peer_txcredit() - manually update stream peer tx credit
*
* \param wsi: the h2 child stream whose peer credit to change
* \param sid: the stream ID, or LWS_H2_STREAM_SID for the wsi stream ID
* \param bump: signed change to confer upon peer tx credit for sid
*
* In conjunction with LCCSCF_H2_MANUAL_RXFLOW flag, allows the user code to
* selectively starve the remote peer of the ability to send us data on a client
* connection.
*
* Normally lws sends an initial window size for the peer to send to it of 0,
* but during the header phase it sends a WINDOW_UPDATE to increase the amount
* available. LCCSCF_H2_MANUAL_RXFLOW restricts this initial increase in tx
* credit for the stream, before it has been asked to send us anything, to the
* amount specified in the client info .manual_initial_tx_credit member, and
* this api can be called to send the other side permission to send us up to
* \p bump additional bytes.
*
* The nwsi tx credit is updated automatically for exactly what was sent to us
* on a stream with LCCSCF_H2_MANUAL_RXFLOW flag, but the stream's own tx credit
* must be handled manually by user code via this api.
*
* Returns 0 for success or nonzero for failure.
*/
#define LWS_H2_STREAM_SID -1
LWS_VISIBLE LWS_EXTERN int
lws_h2_update_peer_txcredit(struct lws *wsi, int sid, int bump);
/**
* lws_h2_get_peer_txcredit_estimate() - return peer tx credit estimate
*
* \param wsi: the h2 child stream whose peer credit estimate to return
*
* Returns the estimated amount of tx credit at the peer, in other words the
* number of bytes the peer is authorized to send to us.
*
* It's an 'estimate' because we don't know how much is already in flight
* towards us and actually already used.
*/
LWS_VISIBLE LWS_EXTERN int
lws_h2_get_peer_txcredit_estimate(struct lws *wsi);
///@}

View file

@ -145,6 +145,10 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)
wsi->ocport = wsi->c_port = i->port;
wsi->sys_tls_client_cert = i->sys_tls_client_cert;
#if defined(LWS_ROLE_H2)
wsi->h2.manual_initial_tx_credit = (int32_t)i->manual_initial_tx_credit;
#endif
wsi->protocol = &wsi->vhost->protocols[0];
wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE);

View file

@ -436,7 +436,7 @@ lws_get_peer_write_allowance(struct lws *wsi)
{
if (!wsi->role_ops->tx_credit)
return -1;
return wsi->role_ops->tx_credit(wsi);
return wsi->role_ops->tx_credit(wsi, LWSTXCR_US_TO_PEER);
}
LWS_VISIBLE void
@ -1074,10 +1074,10 @@ lws_wsi_mux_dump_waiting_children(struct lws *wsi)
wsi = wsi->mux.child_list;
while (wsi) {
lwsl_info(" %c %p %s %s\n",
lwsl_info(" %c %p: sid %u: %s %s\n",
wsi->mux.requested_POLLOUT ? '*' : ' ',
wsi, wsi->role_ops->name, wsi->protocol ?
wsi->protocol->name : "noprotocol");
wsi, wsi->mux.my_sid, wsi->role_ops->name,
wsi->protocol ? wsi->protocol->name : "noprotocol");
wsi = wsi->mux.sibling_list;
}

View file

@ -61,7 +61,7 @@ const struct http2_settings lws_h2_defaults_esp32 = { {
/* H2SET_HEADER_TABLE_SIZE */ 512,
/* H2SET_ENABLE_PUSH */ 0,
/* H2SET_MAX_CONCURRENT_STREAMS */ 8,
/* H2SET_INITIAL_WINDOW_SIZE */ 65535,
/* H2SET_INITIAL_WINDOW_SIZE */ 0,
/* H2SET_MAX_FRAME_SIZE */ 16384,
/* H2SET_MAX_HEADER_LIST_SIZE */ 512,
/* H2SET_RESERVED7 */ 0,

View file

@ -133,7 +133,8 @@ lws_h2_new_pps(enum lws_h2_protocol_send_type type)
void lws_h2_init(struct lws *wsi)
{
wsi->h2.h2n->set = wsi->vhost->h2.set;
wsi->h2.h2n->our_set = wsi->vhost->h2.set;
wsi->h2.h2n->peer_set = lws_h2_defaults;
}
void
@ -149,6 +150,59 @@ lws_h2_state(struct lws *wsi, enum lws_h2_states s)
wsi->h2.h2_state = (uint8_t)s;
}
#if defined(_DEBUG)
static void
lws_h2_describe_txcredit(struct lws *wsi, const char *at)
{
lwsl_info("%s: %p: %s: sid %d: peer-to-us: %d, us-to-peer: %d\n",
__func__, wsi, at, wsi->mux.my_sid, wsi->h2.peer_tx_cr_est,
wsi->h2.tx_cr);
}
#else
#define lws_h2_describe_txcredit(x, y) { (void)x; }
#endif
int
lws_h2_update_peer_txcredit(struct lws *wsi, int sid, int bump)
{
struct lws_h2_protocol_send *pps;
assert(wsi);
if (sid == -1)
sid = wsi->mux.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 = bump;
wsi->h2.peer_tx_cr_est += bump;
lws_h2_describe_txcredit(wsi, __func__);
lws_pps_schedule(wsi, pps);
return 0;
}
int
lws_h2_get_peer_txcredit_estimate(struct lws *wsi)
{
lws_h2_describe_txcredit(wsi, __func__);
return (int)wsi->h2.peer_tx_cr_est;
}
static int
lws_h2_update_peer_txcredit_thresh(struct lws *wsi, int sid, int threshold, int bump)
{
if (wsi->h2.peer_tx_cr_est > threshold)
return 0;
return lws_h2_update_peer_txcredit(wsi, sid, bump);
}
struct lws *
lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
unsigned int sid)
@ -174,7 +228,7 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
/* no more children allowed by parent */
if (parent_wsi->mux.child_count + 1 >
parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
lwsl_notice("reached concurrent stream limit\n");
return NULL;
}
@ -191,9 +245,9 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
wsi->mux_substream = 1;
wsi->seen_nonpseudoheader = 0;
wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
wsi->h2.tx_cr = nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
wsi->h2.peer_tx_cr_est =
nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE];
nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
lwsi_set_state(wsi, LRS_ESTABLISHED);
lwsi_set_role(wsi, lwsi_role(parent_wsi));
@ -209,10 +263,11 @@ lws_wsi_server_new(struct lws_vhost *vh, struct lws *parent_wsi,
/* get the ball rolling */
lws_validity_confirmed(wsi);
lwsl_info("%s: %p new ch %p, sid %d, usersp=%p, tx cr %d, "
"peer_credit %d (nwsi tx_cr %d)\n",
__func__, parent_wsi, wsi, sid, wsi->user_space,
wsi->h2.tx_cr, wsi->h2.peer_tx_cr_est, nwsi->h2.tx_cr);
lwsl_info("%s: %p new ch %p, sid %d, usersp=%p\n", __func__,
parent_wsi, wsi, sid, wsi->user_space);
lws_h2_describe_txcredit(nwsi, __func__);
lws_h2_describe_txcredit(wsi, __func__);
return wsi;
@ -239,7 +294,7 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
/* no more children allowed by parent */
if (parent_wsi->mux.child_count + 1 >
parent_wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
parent_wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
lwsl_notice("reached concurrent stream limit\n");
return NULL;
}
@ -254,9 +309,11 @@ lws_wsi_h2_adopt(struct lws *parent_wsi, struct lws *wsi)
lws_wsi_mux_insert(wsi, parent_wsi, wsi->mux.my_sid);
wsi->h2.tx_cr = nwsi->h2.h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
wsi->h2.tx_cr = nwsi->h2.h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
wsi->h2.peer_tx_cr_est =
nwsi->vhost->h2.set.s[H2SET_INITIAL_WINDOW_SIZE];
nwsi->h2.h2n->our_set.s[H2SET_INITIAL_WINDOW_SIZE];
lws_h2_describe_txcredit(wsi, __func__);
if (lws_ensure_user_space(wsi))
goto bail1;
@ -384,7 +441,7 @@ lws_h2_rst_stream(struct lws *wsi, uint32_t err, const char *reason)
int
lws_h2_settings(struct lws *wsi, struct http2_settings *settings,
unsigned char *buf, int len)
unsigned char *buf, int len)
{
struct lws *nwsi = lws_get_network_wsi(wsi);
unsigned int a, b;
@ -587,10 +644,10 @@ static void lws_h2_set_bin(struct lws *wsi, int n, unsigned char *buf)
{
*buf++ = n >> 8;
*buf++ = n;
*buf++ = wsi->h2.h2n->set.s[n] >> 24;
*buf++ = wsi->h2.h2n->set.s[n] >> 16;
*buf++ = wsi->h2.h2n->set.s[n] >> 8;
*buf = wsi->h2.h2n->set.s[n];
*buf++ = wsi->h2.h2n->our_set.s[n] >> 24;
*buf++ = wsi->h2.h2n->our_set.s[n] >> 16;
*buf++ = wsi->h2.h2n->our_set.s[n] >> 8;
*buf = wsi->h2.h2n->our_set.s[n];
}
/* we get called on the network connection */
@ -630,9 +687,9 @@ int lws_h2_do_pps_send(struct lws *wsi)
* then we must inform the peer
*/
for (n = 1; n < H2SET_COUNT; n++)
if (h2n->set.s[n] != lws_h2_defaults.s[n]) {
if (h2n->our_set.s[n] != lws_h2_defaults.s[n]) {
lwsl_debug("sending SETTING %d 0x%x\n", n,
(unsigned int)wsi->h2.h2n->set.s[n]);
(unsigned int)wsi->h2.h2n->our_set.s[n]);
lws_h2_set_bin(wsi, n, &set[LWS_PRE + m]);
m += sizeof(h2n->one_setting);
}
@ -645,6 +702,27 @@ int lws_h2_do_pps_send(struct lws *wsi)
}
break;
case LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW:
q = &set[LWS_PRE];
*q++ = H2SET_INITIAL_WINDOW_SIZE >> 8;
*q++ = H2SET_INITIAL_WINDOW_SIZE;
*q++ = pps->u.update_window.credit >> 24;
*q++ = pps->u.update_window.credit >> 16;
*q++ = pps->u.update_window.credit >> 8;
*q = pps->u.update_window.credit;
lwsl_debug("%s: resetting initial window to %d\n", __func__,
(int)pps->u.update_window.credit);
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS,
flags, LWS_H2_STREAM_ID_MASTER, 6,
&set[LWS_PRE]);
if (n != 6) {
lwsl_info("send %d %d\n", n, m);
goto bail;
}
break;
case LWS_H2_PPS_ACK_SETTINGS:
/* send ack ... always empty */
n = lws_h2_frame_write(wsi, LWS_H2_FRAME_TYPE_SETTINGS, 1,
@ -654,6 +732,7 @@ int lws_h2_do_pps_send(struct lws *wsi)
lwsl_err("ack tells %d\n", n);
goto bail;
}
wsi->h2_acked_settings = 0;
/* this is the end of the preface dance then? */
if (lwsi_state(wsi) == LRS_H2_AWAIT_SETTINGS) {
lwsi_set_state(wsi, LRS_ESTABLISHED);
@ -677,7 +756,7 @@ int lws_h2_do_pps_send(struct lws *wsi)
lwsl_info("%s: inherited headers %p\n", __func__,
h2n->swsi->http.ah);
h2n->swsi->h2.tx_cr =
h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
h2n->our_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;
@ -843,7 +922,7 @@ lws_h2_parse_frame_header(struct lws *wsi)
if (h2n->type == LWS_H2_FRAME_TYPE_COUNT)
return 0;
if (h2n->length > h2n->set.s[H2SET_MAX_FRAME_SIZE]) {
if (h2n->length > h2n->our_set.s[H2SET_MAX_FRAME_SIZE]) {
/*
* peer sent us something bigger than we told
* it we would allow
@ -1102,13 +1181,18 @@ lws_h2_parse_frame_header(struct lws *wsi)
if (!h2n->swsi) {
/* no more children allowed by parent */
if (wsi->mux.child_count + 1 >
wsi->h2.h2n->set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
wsi->h2.h2n->our_set.s[H2SET_MAX_CONCURRENT_STREAMS]) {
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR,
"Another stream not allowed");
return 1;
}
/*
* The peer has sent us a HEADERS implying the creation
* of a new stream
*/
h2n->swsi = lws_wsi_server_new(wsi->vhost, wsi,
h2n->sid);
if (!h2n->swsi) {
@ -1118,22 +1202,13 @@ lws_h2_parse_frame_header(struct lws *wsi)
return 1;
}
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
if (!pps)
goto cleanup_wsi;
pps->u.update_window.sid = h2n->sid;
pps->u.update_window.credit = 4 * 65536;
h2n->swsi->h2.peer_tx_cr_est +=
pps->u.update_window.credit;
lws_pps_schedule(wsi, pps);
h2n->swsi->h2.initialized = 1;
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
if (!pps)
if (lws_h2_update_peer_txcredit(h2n->swsi, h2n->swsi->mux.my_sid, 4 * 65536))
goto cleanup_wsi;
if (lws_h2_update_peer_txcredit(wsi, 0, 4 * 65536))
goto cleanup_wsi;
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);
}
/*
@ -1265,14 +1340,6 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
if (h2n->sid > h2n->highest_sid)
h2n->highest_sid = h2n->sid;
/* set our initial window size */
if (!wsi->h2.initialized) {
wsi->h2.tx_cr = h2n->set.s[H2SET_INITIAL_WINDOW_SIZE];
lwsl_info("initial tx credit on master %p: %d\n", wsi,
wsi->h2.tx_cr);
wsi->h2.initialized = 1;
}
if (h2n->collected_priority && (h2n->dep & ~(1u << 31)) == h2n->sid) {
lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, "depends on own sid");
return 0;
@ -1327,6 +1394,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
wsi->user_space_externally_allocated;
h2n->swsi->opaque_user_data = wsi->opaque_user_data;
wsi->opaque_user_data = NULL;
h2n->swsi->h2.manual_initial_tx_credit =
wsi->h2.manual_initial_tx_credit;
wsi->user_space = NULL;
@ -1337,11 +1406,20 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
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->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
lwsl_info("%s: initial tx credit on conn %p: %d\n",
__func__, h2n->swsi, h2n->swsi->h2.tx_cr);
h2n->swsi->h2.initialized = 1;
/* set our initial window size */
if (!wsi->h2.initialized) {
wsi->h2.tx_cr = h2n->peer_set.s[H2SET_INITIAL_WINDOW_SIZE];
lwsl_info("%s: initial tx credit for us to "
"write on master %p: %d\n", __func__,
wsi, wsi->h2.tx_cr);
wsi->h2.initialized = 1;
}
lws_callback_on_writable(h2n->swsi);
if (!wsi->h2_acked_settings ||
@ -1653,6 +1731,8 @@ lws_h2_parse_end_of_frame(struct lws *wsi)
n = eff_wsi->h2.tx_cr;
eff_wsi->h2.tx_cr += h2n->hpack_e_dep;
lws_h2_describe_txcredit(eff_wsi, "WINDOW_UPDATE in");
if (n <= 0 && eff_wsi->h2.tx_cr <= 0)
/* it helps, but won't change sendability for anyone */
break;
@ -1815,7 +1895,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
h2n->one_setting[n] = c;
if (n != LWS_H2_SETTINGS_LEN - 1)
break;
lws_h2_settings(wsi, &h2n->set,
lws_h2_settings(wsi, &h2n->peer_set,
h2n->one_setting,
LWS_H2_SETTINGS_LEN);
break;
@ -1920,13 +2000,17 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
if (!h2n->swsi->protocol) {
lwsl_err("%s: swsi %pdoesn't have protocol\n", __func__, h2n->swsi);
m = 1;
} else
} else {
h2n->swsi->h2.peer_tx_cr_est -= n;
wsi->h2.peer_tx_cr_est -= n;
lws_h2_describe_txcredit(h2n->swsi, __func__);
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;
@ -1998,38 +2082,42 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
in += n - 1;
h2n->inside += n;
h2n->count += n - 1;
h2n->swsi->h2.peer_tx_cr_est -= n;
wsi->h2.peer_tx_cr_est -= n;
}
do_windows:
/* account for both network and stream wsi windows */
wsi->h2.peer_tx_cr_est -= n;
h2n->swsi->h2.peer_tx_cr_est -= n;
#if defined(LWS_WITH_CLIENT)
if (!(h2n->swsi->flags & LCCSCF_H2_MANUAL_RXFLOW))
#endif
{
/*
* The default behaviour is we just keep
* cranking the other side's tx credit
* back up, for simple bulk transfer as
* fast as we can take it
*/
// lwsl_notice(" peer_tx_cr_est %d, parent %d\n",
// h2n->swsi->h2.peer_tx_cr_est, wsi->h2.peer_tx_cr_est);
m = n; //(2 * h2n->length) + 65536;
if (h2n->swsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 65536) {
pps = lws_h2_new_pps(LWS_H2_PPS_UPDATE_WINDOW);
if (!pps)
return 1;
pps->u.update_window.sid = h2n->sid;
pps->u.update_window.credit = (2 * h2n->length + 65536);
h2n->swsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
lws_pps_schedule(wsi, pps);
/* update both the stream and nwsi */
lws_h2_update_peer_txcredit_thresh(h2n->swsi,
h2n->sid, m, m);
lws_h2_update_peer_txcredit_thresh(wsi, 0, m, m);
}
if (wsi->h2.peer_tx_cr_est < (int)(2 * h2n->length) + 65536) {
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 = (2 * h2n->length + 65536);
wsi->h2.peer_tx_cr_est += pps->u.update_window.credit;
lws_pps_schedule(wsi, pps);
#if defined(LWS_WITH_CLIENT)
else {
/*
* If he's handling it himself, only
* repair the nwsi credit but allow the
* stream credit to run down until the
* user code deals with it
*/
lws_h2_update_peer_txcredit(wsi, 0, n);
}
// lwsl_notice("%s: count %d len %d\n", __func__, (int)h2n->count, (int)h2n->length);
#endif
break;
case LWS_H2_FRAME_TYPE_PRIORITY:
@ -2174,7 +2262,6 @@ lws_h2_client_handshake(struct lws *wsi)
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, m;
/*
* The identifier of a newly established stream MUST be numerically
@ -2192,22 +2279,6 @@ lws_h2_client_handshake(struct lws *wsi)
lwsl_info("%s: CLIENT_WAITING_TO_SEND_HEADERS: pollout (sid %d)\n",
__func__, wsi->mux.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);
p = start = buf = pt->serv_buf + LWS_PRE;
end = start + (wsi->context->pt_serv_buf_size / 2) - LWS_PRE - 1;
@ -2302,6 +2373,22 @@ lws_h2_client_handshake(struct lws *wsi)
return -1;
}
/*
* Normally let's charge up the peer tx credit a bit. But if
* MANUAL_REFLOW is set, just set it to the initial credit given in
* the client create info
*/
n = 4 * 65536;
if (wsi->flags & LCCSCF_H2_MANUAL_RXFLOW)
n = wsi->h2.manual_initial_tx_credit;
if (lws_h2_update_peer_txcredit(wsi, sid, n))
return 1;
if (lws_h2_update_peer_txcredit(nwsi, 0, n))
return 1;
lws_h2_state(wsi, LWS_H2_STATE_OPEN);
lwsi_set_state(wsi, LRS_ESTABLISHED);

View file

@ -73,7 +73,10 @@ const struct http2_settings lws_h2_stock_settings = { {
*/
/* H2SET_ENABLE_PUSH */ 0,
/* H2SET_MAX_CONCURRENT_STREAMS */ 24,
/* H2SET_INITIAL_WINDOW_SIZE */ 65535,
/* H2SET_INITIAL_WINDOW_SIZE */ 0,
/*< This is managed by explicit WINDOW_UPDATE. Because otherwise no
* way to precisely control it when we do want to.
*/
/* H2SET_MAX_FRAME_SIZE */ 16384,
/* H2SET_MAX_HEADER_LIST_SIZE */ 4096,
/*< This advisory setting informs a peer of the maximum size of
@ -338,7 +341,7 @@ int rops_handle_POLLOUT_h2(struct lws *wsi)
return LWS_HP_RET_USER_SERVICE;
/*
* Priority 2: H2 protocol packets
* Priority 1: H2 protocol packets
*/
if ((wsi->upgraded_to_http2
#if defined(LWS_WITH_CLIENT)
@ -364,7 +367,7 @@ int rops_handle_POLLOUT_h2(struct lws *wsi)
return LWS_HP_RET_BAIL_OK; /* leave POLLOUT active */
}
/* Priority 4: if we are closing, not allowed to send more data frags
/* Priority 2: if we are closing, not allowed to send more data frags
* which means user callback or tx ext flush banned now
*/
if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
@ -567,9 +570,19 @@ rops_pt_init_destroy_h2(struct lws_context *context,
static lws_fileofs_t
rops_tx_credit_h2(struct lws *wsi)
rops_tx_credit_h2(struct lws *wsi, char peer_to_us)
{
return lws_h2_tx_cr_get(wsi);
struct lws *nwsi = lws_get_network_wsi(wsi);
int n;
if (peer_to_us == LWSTXCR_US_TO_PEER)
return lws_h2_tx_cr_get(wsi);
n = wsi->h2.peer_tx_cr_est;
if (n > nwsi->h2.peer_tx_cr_est)
n = nwsi->h2.peer_tx_cr_est;
return n;
}
static int
@ -710,12 +723,16 @@ rops_callback_on_writable_h2(struct lws *wsi)
* Delay waiting for our POLLOUT until peer indicates he has
* space for more using tx window command in http2 layer
*/
lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi,
wsi->h2.tx_cr);
lwsl_info("%s: %p: skint (%d)\n", __func__, wsi, wsi->h2.tx_cr);
wsi->h2.skint = 1;
return 0;
}
if (wsi->h2.skint)
lwsl_info("%s: %p: unskint (%d)\n", __func__, wsi, wsi->h2.tx_cr);
wsi->h2.skint = 0;
#if defined(LWS_WITH_CLIENT)
network_wsi = lws_get_network_wsi(wsi);
@ -804,10 +821,10 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
wsi = lws_get_network_wsi(wsi);
wsi->mux.requested_POLLOUT = 0;
if (!wsi->h2.initialized) {
lwsl_info("pollout on uninitialized http2 conn\n");
return 0;
}
// if (!wsi->h2.initialized) {
// lwsl_info("pollout on uninitialized http2 conn\n");
// return 0;
// }
lws_wsi_mux_dump_waiting_children(wsi);
@ -961,6 +978,32 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
if (lwsi_state(w) == LRS_ISSUING_FILE) {
/* is this for DATA or for control messages? */
if (!lws_h2_tx_cr_get(w)) {
/*
* Other side is not able to cope with us sending any DATA,
* so no matter if we have POLLOUT on our side if it's
* DATA we want to send.
*
* Delay waiting for our POLLOUT until peer indicates he has
* space for more using tx window command in http2 layer
*/
lwsl_info("%s: %p: skint (%d)\n", __func__,
w, w->h2.tx_cr);
w->h2.skint = 1;
wa = &wsi->mux.child_list;
goto next_child;
}
if (w->h2.skint)
lwsl_info("%s: %p: unskint (%d)\n", __func__,
w, w->h2.tx_cr);
wsi->h2.skint = 0;
((volatile struct lws *)w)->leave_pollout_active = 0;
/* >0 == completion, <0 == error
@ -1147,7 +1190,7 @@ rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn)
/* HTTP2 union */
lws_hpack_dynamic_size(wsi,
wsi->h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]);
wsi->h2.h2n->our_set.s[H2SET_HEADER_TABLE_SIZE]);
wsi->h2.tx_cr = 65535;
lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi);

View file

@ -25,19 +25,6 @@
extern struct lws_role_ops role_ops_h2;
#define lwsi_role_h2(wsi) (wsi->role_ops == &role_ops_h2)
enum lws_h2_settings {
H2SET_HEADER_TABLE_SIZE = 1,
H2SET_ENABLE_PUSH,
H2SET_MAX_CONCURRENT_STREAMS,
H2SET_INITIAL_WINDOW_SIZE,
H2SET_MAX_FRAME_SIZE,
H2SET_MAX_HEADER_LIST_SIZE,
H2SET_RESERVED7,
H2SET_ENABLE_CONNECT_PROTOCOL, /* defined in mcmanus-httpbis-h2-ws-02 */
H2SET_COUNT /* always last */
};
struct http2_settings {
uint32_t s[H2SET_COUNT];
};
@ -216,6 +203,7 @@ enum lws_h2_protocol_send_type {
LWS_H2_PPS_GOAWAY,
LWS_H2_PPS_RST_STREAM,
LWS_H2_PPS_UPDATE_WINDOW,
LWS_H2_PPS_SETTINGS_INITIAL_UPDATE_WINDOW
};
struct lws_h2_protocol_send {
@ -261,7 +249,8 @@ struct lws_h2_ghost_sid {
* fills it but it belongs to the logical child.
*/
struct lws_h2_netconn {
struct http2_settings set;
struct http2_settings our_set;
struct http2_settings peer_set;
struct hpack_dynamic_table hpack_dyn_table;
uint8_t ping_payload[8];
uint8_t one_setting[LWS_H2_SETTINGS_LEN];
@ -322,6 +311,8 @@ struct _lws_h2_related {
int tx_cr;
int peer_tx_cr_est;
int32_t manual_initial_tx_credit;
uint8_t END_STREAM:1;
uint8_t END_HEADERS:1;
uint8_t send_END_STREAM:1;
@ -329,6 +320,7 @@ struct _lws_h2_related {
uint8_t GOING_AWAY;
uint8_t skint:1;
uint8_t initialized:1;
uint8_t told_initial:1;
uint8_t h2_state; /* RFC7540 state of the connection */
};

View file

@ -2132,9 +2132,9 @@ upgrade_h2c:
/* HTTP2 union */
lws_h2_settings(wsi, &wsi->h2.h2n->set, (unsigned char *)tbuf, n);
lws_h2_settings(wsi, &wsi->h2.h2n->peer_set, (unsigned char *)tbuf, n);
lws_hpack_dynamic_size(wsi, wsi->h2.h2n->set.s[
lws_hpack_dynamic_size(wsi, wsi->h2.h2n->peer_set.s[
H2SET_HEADER_TABLE_SIZE]);
strcpy(tbuf, "HTTP/1.1 101 Switching Protocols\x0d\x0a"
@ -2781,7 +2781,7 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
poss = wsi->protocol->tx_packet_size;
if (wsi->role_ops->tx_credit) {
lws_filepos_t txc = wsi->role_ops->tx_credit(wsi);
lws_filepos_t txc = wsi->role_ops->tx_credit(wsi, LWSTXCR_US_TO_PEER);
if (!txc) {
lwsl_info("%s: came here with no tx credit\n",

View file

@ -200,7 +200,9 @@ struct lws_role_ops {
/* do effective callback on writeable */
int (*callback_on_writable)(struct lws *wsi);
/* connection-specific tx credit in bytes */
lws_fileofs_t (*tx_credit)(struct lws *wsi);
#define LWSTXCR_US_TO_PEER 0
#define LWSTXCR_PEER_TO_US 1
lws_fileofs_t (*tx_credit)(struct lws *wsi, char peer_to_us);
/* role-specific write formatting */
int (*write_role_protocol)(struct lws *wsi, unsigned char *buf,
size_t len, enum lws_write_protocol *wp);

View file

@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-http-client-h2-rxflow)
set(SRCS minimal-http-client.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H2 1 requirements)
require_lws_config(LWS_WITH_CLIENT 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View file

@ -0,0 +1,53 @@
# lws minimal http client-h2-rxflow
The application reads from a server with tightly controlled and rate-limited
receive flow control using h2 tx credit.
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-l| Connect to https://localhost:7681 and accept selfsigned cert
--server <name>|set server name to connect to
--path <path>|URL path to access on server
-k|Apply tls option LCCSCF_ALLOW_INSECURE
-j|Apply tls option LCCSCF_ALLOW_SELFSIGNED
-m|Apply tls option LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK
-e|Apply tls option LCCSCF_ALLOW_EXPIRED
-v|Connection validity use 3s / 10s instead of default 5m / 5m10s
--nossl| disable ssl connection
-f <initial credit>|Indicate we will manually manage tx credit and set a new connection-specific initial tx credit
RX is constrained to 1024 bytes every 250ms
```
$ ./lws-minimal-http-client-h2-rxflow --server phys.org --path "/" -f 1024
[2019/12/26 13:32:59:6801] U: LWS minimal http client [-d<verbosity>] [-l] [--h1]
[2019/12/26 13:33:00:5087] N: system_notify_cb: manual peer tx credit 1024
[2019/12/26 13:33:01:7390] U: Connected to 72.251.236.55, http response: 200
[2019/12/26 13:33:01:7441] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:01:0855] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:02:3367] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:02:5858] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:02:8384] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:02:0886] U: RECEIVE_CLIENT_HTTP_READ: read 1024
...
[2019/12/26 13:33:46:1152] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:47:3650] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:47:6150] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:47:8666] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:47:1154] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:48:3656] U: RECEIVE_CLIENT_HTTP_READ: read 1024
[2019/12/26 13:33:48:6157] U: RECEIVE_CLIENT_HTTP_READ: read 380
[2019/12/26 13:33:48:6219] U: LWS_CALLBACK_COMPLETED_CLIENT_HTTP
[2019/12/26 13:33:48:7050] U: Completed: OK
```

View file

@ -0,0 +1,302 @@
/*
* lws-minimal-http-client-h2-rxflow
*
* Written in 2010-2019 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This demonstrates the a minimal http client using lws.
*
* It visits https://warmcat.com/ and receives the html page there. You
* can dump the page data by changing the #if 0 below.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted, bad = 1, status, each = 1024;
static struct lws *client_wsi;
static const lws_retry_bo_t retry = {
.secs_since_valid_ping = 3,
.secs_since_valid_hangup = 10,
};
struct pss {
lws_sorted_usec_list_t sul;
struct lws *wsi;
};
/*
* Once we're established, we ask the server for another 1KB every 250ms
* until we have it all.
*/
static void
drain_cb(lws_sorted_usec_list_t *sul)
{
struct pss *pss = lws_container_of(sul, struct pss, sul);
lws_h2_update_peer_txcredit(pss->wsi, LWS_H2_STREAM_SID, each);
lws_sul_schedule(lws_get_context(pss->wsi), 0, &pss->sul, drain_cb,
250 * LWS_US_PER_MS);
}
static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct pss *pss = (struct pss *)user;
switch (reason) {
/* because we are protocols[0] ... */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
interrupted = 1;
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
{
char buf[128];
lws_get_peer_simple(wsi, buf, sizeof(buf));
status = lws_http_client_http_response(wsi);
lwsl_user("Connected to %s, http response: %d\n",
buf, status);
}
pss->wsi = wsi;
lws_sul_schedule(lws_get_context(wsi), 0, &pss->sul, drain_cb,
250 * LWS_US_PER_MS);
break;
/* chunks of chunked content, with header removed */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
#if 0 /* enable to dump the html */
{
const char *p = in;
while (len--)
if (*p < 0x7f)
putchar(*p++);
else
putchar('.');
}
#endif
return 0; /* don't passthru */
/* uninterpreted http content */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
{
char buffer[1024 + LWS_PRE];
char *px = buffer + LWS_PRE;
int lenx = sizeof(buffer) - LWS_PRE;
if (lws_http_client_read(wsi, &px, &lenx) < 0)
return -1;
}
return 0; /* don't passthru */
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
interrupted = 1;
bad = status != 200;
lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
break;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
interrupted = 1;
bad = status != 200;
lws_sul_schedule(lws_get_context(wsi), 0, &pss->sul, NULL,
LWS_SET_TIMER_USEC_CANCEL);
lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static const struct lws_protocols protocols[] = {
{
"http",
callback_http,
sizeof(struct pss),
0,
},
{ NULL, NULL, 0, 0 }
};
static void
sigint_handler(int sig)
{
interrupted = 1;
}
struct args {
int argc;
const char **argv;
};
static int
system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = mgr->parent;
struct lws_client_connect_info i;
struct args *a = lws_context_user(context);
const char *p;
if (current != LWS_SYSTATE_OPERATIONAL || target != LWS_SYSTATE_OPERATIONAL)
return 0;
lwsl_info("%s: operational\n", __func__);
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
if (!lws_cmdline_option(a->argc, a->argv, "-n"))
i.ssl_connection = LCCSCF_USE_SSL;
if (lws_cmdline_option(a->argc, a->argv, "-l")) {
i.port = 7681;
i.address = "localhost";
i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
} else {
i.port = 443;
i.address = "warmcat.com";
}
if (lws_cmdline_option(a->argc, a->argv, "--nossl"))
i.ssl_connection = 0;
i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
i.alpn = "h2";
if (lws_cmdline_option(a->argc, a->argv, "--h1"))
i.alpn = "http/1.1";
if ((p = lws_cmdline_option(a->argc, a->argv, "-p")))
i.port = atoi(p);
if (lws_cmdline_option(a->argc, a->argv, "-j"))
i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
if (lws_cmdline_option(a->argc, a->argv, "-k"))
i.ssl_connection |= LCCSCF_ALLOW_INSECURE;
if (lws_cmdline_option(a->argc, a->argv, "-m"))
i.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
if (lws_cmdline_option(a->argc, a->argv, "-e"))
i.ssl_connection |= LCCSCF_ALLOW_EXPIRED;
if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) {
i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW;
i.manual_initial_tx_credit = atoi(p);
lwsl_notice("%s: manual peer tx credit %d\n", __func__,
i.manual_initial_tx_credit);
}
if ((p = lws_cmdline_option(a->argc, a->argv, "--each")))
each = atoi(p);
/* the default validity check is 5m / 5m10s... -v = 3s / 10s */
if (lws_cmdline_option(a->argc, a->argv, "-v"))
i.retry_and_idle_policy = &retry;
if ((p = lws_cmdline_option(a->argc, a->argv, "--server")))
i.address = p;
if ((p = lws_cmdline_option(a->argc, a->argv, "--path")))
i.path = p;
else
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.method = "GET";
i.protocol = protocols[0].name;
i.pwsi = &client_wsi;
return !lws_client_connect_via_info(&i);
}
int main(int argc, const char **argv)
{
lws_state_notify_link_t notifier = { {}, system_notify_cb, "app" };
lws_state_notify_link_t *na[] = { &notifier, NULL };
struct lws_context_creation_info info;
struct lws_context *context;
struct args args;
int n = 0;
// uint8_t memcert[4096];
args.argc = argc;
args.argv = argv;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS minimal http client [-d<verbosity>] [-l] [--h1]\n");
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
info.protocols = protocols;
info.user = &args;
info.register_notifier_list = na;
/*
* since we know this lws context is only ever going to be used with
* one client wsis / fds / sockets at a time, let lws know it doesn't
* have to use the default allocations for fd tables up to ulimit -n.
* It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
* will use.
*/
info.fd_limit_per_thread = 1 + 1 + 1;
#if defined(LWS_WITH_MBEDTLS)
/*
* OpenSSL uses the system trust store. mbedTLS has to be told which
* CA to trust explicitly.
*/
info.client_ssl_ca_filepath = "./warmcat.com.cer";
#endif
#if 0
n = open("./warmcat.com.cer", O_RDONLY);
if (n >= 0) {
info.client_ssl_ca_mem_len = read(n, memcert, sizeof(memcert));
info.client_ssl_ca_mem = memcert;
close(n);
n = 0;
memcert[info.client_ssl_ca_mem_len++] = '\0';
}
#endif
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View file

@ -0,0 +1,33 @@
#!/bin/bash
#
# $1: path to minimal example binaries...
# if lws is built with -DLWS_WITH_MINIMAL_EXAMPLES=1
# that will be ./bin from your build dir
#
# $2: path for logs and results. The results will go
# in a subdir named after the directory this script
# is in
#
# $3: offset for test index count
#
# $4: total test count
#
# $5: path to ./minimal-examples dir in lws
#
# Test return code 0: OK, 254: timed out, other: error indication
. $5/selftests-library.sh
COUNT_TESTS=4
dotest $1 $2 warmcat
dotest $1 $2 warmcat-h1 --h1
spawn "" $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
dotest $1 $2 localhost -l
spawn $SPID $5/http-server/minimal-http-server-tls $1/lws-minimal-http-server-tls
dotest $1 $2 localhost-h1 -l --h1
kill $SPID 2>/dev/null
wait $SPID 2>/dev/null
exit $FAILS

View file

@ -0,0 +1,58 @@
-----BEGIN CERTIFICATE-----
MIIFUDCCBDigAwIBAgISA4mJfIm3iCGbU9+o8YQa+4nUMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA5MDcwNzA5MjNaFw0x
OTEyMDYwNzA5MjNaMBYxFDASBgNVBAMTC3dhcm1jYXQuY29tMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwnEoH9JW3GvpadpxHGZPb5wv1Q6KfAIMWtdq
YCOfotFxaYULuzHVxmrTTgmEqJr+eBqUBkXKmGuRR/9UipOmTu5j02qFyWHotFdF
ZGyp//8z+Rle9Qt1nL68oNIZLDtWkybh5x00b1uo4eyEszXUaa0aLqKP3lH7Q4jI
aSVARZ8snrJR640Gp3ByudvNTYkGz469bpWzRC/8wSNtzzY02DvHs1GxQx9tMXw+
BbtUxeP7lpYFKEFBjgZaIKLv+4g8ItJIuO7gMSzG2JfpQHxdhrlhxpx7dsaMUcyM
nnYXysNL5JG3KEMhkxbtdpCaEQ8jLSPbl/rnF/+mgce+lSjMuQIDAQABo4ICYjCC
Al4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSI9ai12zLFeNTEDHKI9Ghkqcpa2TAf
BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
MBYGA1UdEQQPMA2CC3dhcm1jYXQuY29tMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
cHQub3JnMIIBBgYKKwYBBAHWeQIEAgSB9wSB9ADyAHcAY/Lbzeg7zCzPC3KEJ1dr
M6SNYXePvXWmOLHHaFRL2I0AAAFtCsVHHAAABAMASDBGAiEAy0q1cR4VwPL3iviL
cBWN67kjJRXk+DwhodmeoM3kb3gCIQC2soAHFs0Umo+0RNdFrL41+hMuidh2cXbb
Ovc6nh5tOQB3AOJpS64m6OlACeiGG7Y7g9Q+5/50iPukjyiTAZ3d8dv+AAABbQrF
R48AAAQDAEgwRgIhANqKQm4t9by263CJ7/DLOaZCjtcK29KgJjPwhv08UMn1AiEA
h35nGTASR8/E7xz+56ZUleqD7U1ABFgWZRZskIzsFO8wDQYJKoZIhvcNAQELBQAD
ggEBADDJBVbKe2LPHmi8k2vxErB3Y0Ty+3gwgPEXKYtEvQ7tos89eE+QmOXAzH5J
GwRarFf7kzmKeJv04tMebiEtshpap47oJfxCxfrtpja8hP8Cdu/v/Ae6eEzu3yet
0N08GJdxQKfgCFaoGUptbaF2RCIZS12SVcX4TPpdP+xaiZdmIx4dGM6tReQ8+y8B
10b4Hi2+d/zW0W1z6+FAemU6yleWriJDUik5oas9XZF5LAAMDb/WgF5eIB6P9CUG
LuAO8lWlk9nBgXvMLTxZ74SJb17H4kFEIrIjvABNshz5gBW8xw9nfr5YIfANtwEj
BDsq06Df3UORYVs/j3T97gPAEZ4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View file

@ -42,7 +42,6 @@ struct cliuser {
static int interrupted, completed, failed, numbered, stagger_idx;
static struct lws *client_wsi[COUNT];
static struct cliuser cliuser[COUNT];
static lws_sorted_usec_list_t sul_stagger;
static struct lws_client_connect_info i;
struct lws_context *context;
@ -51,7 +50,7 @@ static int
callback_http(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct cliuser *u = (struct cliuser *)cliuser;
int idx = (int)(long)lws_get_opaque_user_data(wsi);
switch (reason) {
@ -64,7 +63,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
client_wsi[u->index] = NULL;
client_wsi[idx] = NULL;
failed++;
if (++completed == COUNT) {
lwsl_err("Done: failed: %d\n", failed);
@ -74,8 +73,7 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
/* chunks of chunked content, with header removed */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n",
u->index, (int)len);
lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n", idx, (int)len);
#if 0 /* enable to dump the html */
{
const char *p = in;
@ -103,8 +101,8 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %p: idx %d\n",
wsi, u->index);
client_wsi[u->index] = NULL;
wsi, idx);
client_wsi[idx] = NULL;
if (++completed == COUNT) {
if (!failed)
lwsl_user("Done: all OK\n");
@ -117,13 +115,14 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
break;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
if (u && client_wsi[u->index]) {
lwsl_info("%s: closed: %p\n", __func__, client_wsi[idx]);
if (client_wsi[idx]) {
/*
* If it completed normally, it will have been set to
* NULL then already. So we are dealing with an
* abnormal, failing, close
*/
client_wsi[u->index] = NULL;
client_wsi[idx] = NULL;
failed++;
if (++completed == COUNT) {
lwsl_err("Done: failed: %d\n", failed);
@ -194,8 +193,7 @@ lws_try_client_connection(struct lws_client_connect_info *i, int m)
i->path = "/";
i->pwsi = &client_wsi[m];
cliuser[m].index = m;
i->userdata = &cliuser[m];
i->opaque_user_data = (void *)(long)m;
if (!lws_client_connect_via_info(i)) {
failed++;
@ -290,7 +288,9 @@ int main(int argc, const char **argv)
}
i.context = context;
i.ssl_connection = LCCSCF_USE_SSL;
i.ssl_connection = LCCSCF_USE_SSL |
LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
/* enables h1 or h2 connection sharing */
if (lws_cmdline_option(argc, argv, "-p"))

View file

@ -194,7 +194,10 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
if (lws_cmdline_option(a->argc, a->argv, "--nossl"))
i.ssl_connection = 0;
i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
i.alpn = "h2";
if (lws_cmdline_option(a->argc, a->argv, "--h1"))
i.alpn = "http/1.1";
@ -218,6 +221,13 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
if (lws_cmdline_option(a->argc, a->argv, "-e"))
i.ssl_connection |= LCCSCF_ALLOW_EXPIRED;
if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) {
i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW;
i.manual_initial_tx_credit = atoi(p);
lwsl_notice("%s: manual peer tx credit %d\n", __func__,
i.manual_initial_tx_credit);
}
/* the default validity check is 5m / 5m10s... -v = 3s / 10s */
if (lws_cmdline_option(a->argc, a->argv, "-v"))