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

h2: unify immortal stream tracking across SSE and ws substreams

It was already correct but add helpers to isolate and deduplicate
processing adding and closing a generically immortal stream.

Change the default 31s h2 network connection timeout to be settable
by .keepalive_timeout if nonzero.

Add a public api allowing a client h2 stream to transition to
half-closed LOCAL (by sending a 0-byte DATA with END_STREAM) and
mark itself as immortal to create a read-only long-poll stream
if the server allows it.

Add a vhost server option flag LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL
which allows the vhost to treat half-closed remotes as immortal long
poll streams.
This commit is contained in:
Andy Green 2019-09-13 10:33:24 +01:00
parent 4e8497b28a
commit ba754c4cb2
18 changed files with 599 additions and 60 deletions

View file

@ -0,0 +1,55 @@
# h2 long poll in lws
lws server and client can support "immortal" streams that are
not subject to normal timeouts under a special condition. These
are read-only (to the client).
Network connections that contain at least one immortal stream
are themselves not subject to timeouts until the last immortal
stream they are carrying closes.
Because of this, it's recommended there is some other way of
confirming that the client is still active.
## Setting up lws server for h2 long poll
Vhosts that wish to allow clients to serve these immortal
streams need to set the info.options flag `LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL`
at vhost creation time. The JSON config equivalent is to set
```
"h2-half-closed-long-poll": "1"
```
on the vhost. That's all that is needed.
Streams continue to act normally for timeout with the exception
client streams are allowed to signal they are half-closing by
sending a zero-length DATA frame with END_STREAM set. These
streams are allowed to exist outside of any timeout and data
can be sent on them at will in the server -> client direction.
## Setting client streams for long poll
An API is provided to allow established h2 client streams to
transition to immortal mode and send the END_STREAM to the server
to indicate it.
```
int
lws_h2_client_stream_long_poll_rxonly(struct lws *wsi);
```
## Example applications
You can confirm the long poll flow simply using example applications.
Build and run `http-server/minimal-http-server-h2-long-poll` in one
terminal.
In another, build the usual `http-client/minimal-http-client` example
and run it with the flags `-l --long-poll`
The client will connect to the server and transition to the immortal mode.
The server sends a timestamp every minute to the client, and that will
stay up without timeouts.

View file

@ -212,6 +212,12 @@
/**< (VH) Indicates the connections using this vhost should ignore
* h2 WINDOW_UPDATE from broken peers and fix them up */
#define LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL (1ll << 32)
/**< (VH) Tell the vhost to treat half-closed remote clients as
* entered into an immortal (ie, not subject to normal timeouts) long
* poll mode.
*/
/****** add new things just above ---^ ******/
@ -398,8 +404,9 @@ struct lws_context_creation_info {
/**< VHOST: pointer to optional linked list of per-vhost
* options made accessible to protocols */
int keepalive_timeout;
/**< VHOST: (default = 0 = 5s) seconds to allow remote
* client to hold on to an idle HTTP/1.1 connection */
/**< VHOST: (default = 0 = 5s, 31s for http/2) seconds to allow remote
* client to hold on to an idle HTTP/1.1 connection. Timeout lifetime
* applied to idle h2 network connections */
const char *log_filepath;
/**< VHOST: filepath to append logs to... this is opened before
* any dropping of initial privileges */

View file

@ -745,6 +745,24 @@ lws_http_headers_detach(struct lws *wsi);
LWS_VISIBLE LWS_EXTERN int
lws_http_mark_sse(struct lws *wsi);
/**
* lws_h2_client_stream_long_poll_rxonly() - h2 stream to immortal read-only
*
* \param wsi: h2 stream client wsi
*
* Send END_STREAM-flagged zero-length DATA frame to set client stream wsi into
* half-closed (local) and remote into half-closed (remote). Set the client
* stream wsi to be immortal (not subject to timeouts).
*
* Used if the remote server supports immortal long poll to put the stream into
* a read-only state where it can wait as long as needed for rx.
*
* Returns 0 if the process (which happens asynchronously) started or non-zero
* if it wasn't an h2 stream.
*/
LWS_VISIBLE LWS_EXTERN int
lws_h2_client_stream_long_poll_rxonly(struct lws *wsi);
/**
* lws_http_compression_apply() - apply an http compression transform
*

View file

@ -254,6 +254,11 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,
lws_addrinfo_clean(wsi);
#endif
#if defined(LWS_WITH_HTTP2)
if (wsi->h2_stream_immortal)
lws_http_close_immortal(wsi);
#endif
/* if we have children, close them first */
if (wsi->child_list) {
wsi2 = wsi->child_list;

View file

@ -676,8 +676,9 @@ struct lws {
unsigned int hdr_parsing_completed:1;
unsigned int http2_substream:1;
unsigned int upgraded_to_http2:1;
unsigned int h2_stream_carries_ws:1;
unsigned int h2_stream_carries_sse:1;
unsigned int h2_stream_immortal:1;
unsigned int h2_stream_carries_ws:1; /* immortal set as well */
unsigned int h2_stream_carries_sse:1; /* immortal set as well */
unsigned int seen_nonpseudoheader:1;
unsigned int listener:1;
unsigned int user_space_externally_allocated:1;
@ -1086,20 +1087,25 @@ lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int len, int meth);
#define lws_access_log(_a)
#endif
LWS_EXTERN int
void
lws_http_mark_immortal(struct lws *wsi);
void
lws_http_close_immortal(struct lws *wsi);
int
lws_cgi_kill_terminated(struct lws_context_per_thread *pt);
LWS_EXTERN void
void
lws_cgi_remove_and_kill(struct lws *wsi);
LWS_EXTERN void
void
lws_plat_delete_socket_from_fds(struct lws_context *context,
struct lws *wsi, int m);
LWS_EXTERN void
void
lws_plat_insert_socket_into_fds(struct lws_context *context,
struct lws *wsi);
LWS_EXTERN int
int
lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi,
struct lws_pollfd *pfd);

View file

@ -158,6 +158,8 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs)
if (secs == LWS_TO_KILL_ASYNC)
secs = 0;
assert(!wsi->h2_stream_immortal);
lws_pt_lock(pt, __func__);
__lws_set_timeout(wsi, reason, secs);
lws_pt_unlock(pt);

View file

@ -830,20 +830,69 @@ lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p,
return 0;
}
void
lws_http_close_immortal(struct lws *wsi)
{
struct lws *nwsi;
if (!wsi->http2_substream)
return;
assert(wsi->h2_stream_immortal);
wsi->h2_stream_immortal = 0;
nwsi = lws_get_network_wsi(wsi);
lwsl_debug("%s: %p %p %d\n", __func__, wsi, nwsi,
nwsi->immortal_substream_count);
assert(nwsi->immortal_substream_count);
nwsi->immortal_substream_count--;
if (!nwsi->immortal_substream_count)
/*
* since we closed the only immortal stream on this nwsi, we
* need to reapply a normal timeout regime to the nwsi
*/
lws_set_timeout(nwsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
wsi->vhost->keepalive_timeout ?
wsi->vhost->keepalive_timeout : 31);
}
void
lws_http_mark_immortal(struct lws *wsi)
{
struct lws *nwsi;
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
if (!wsi->http2_substream
#if defined(LWS_WITH_CLIENT)
&& !wsi->client_h2_substream
#endif
) {
lwsl_err("%s: not h2 substream\n", __func__);
return;
}
nwsi = lws_get_network_wsi(wsi);
lwsl_debug("%s: %p %p %d\n", __func__, wsi, nwsi,
nwsi->immortal_substream_count);
wsi->h2_stream_immortal = 1;
assert(nwsi->immortal_substream_count < 255); /* largest count */
nwsi->immortal_substream_count++;
if (nwsi->immortal_substream_count == 1)
lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0);
}
int
lws_http_mark_sse(struct lws *wsi)
{
lws_http_headers_detach(wsi);
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
if (wsi->http2_substream) {
struct lws *nwsi = lws_get_network_wsi(wsi);
lws_http_mark_immortal(wsi);
if (wsi->http2_substream)
wsi->h2_stream_carries_sse = 1;
nwsi->immortal_substream_count++;
if (nwsi->immortal_substream_count == 1)
lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0);
}
return 0;
}

View file

@ -845,11 +845,9 @@ lws_h2_parse_frame_header(struct lws *wsi)
/* let the network wsi live a bit longer if subs are active */
if (!wsi->immortal_substream_count)
#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, wsi->vhost->keepalive_timeout);
#else
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
#endif
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
wsi->vhost->keepalive_timeout ?
wsi->vhost->keepalive_timeout : 31);
if (h2n->sid)
h2n->swsi = lws_h2_wsi_from_id(wsi, h2n->sid);
@ -1164,12 +1162,24 @@ lws_h2_parse_frame_header(struct lws *wsi)
assert(w->h2.sibling_list != w);
} lws_end_foreach_ll(w, h2.sibling_list);
if (lws_check_opt(h2n->swsi->vhost->options,
LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL)) {
/* END_STREAM means after servicing this, close the stream */
h2n->swsi->h2.END_STREAM =
/*
* We don't directly timeout streams that enter the
* half-closed remote state, allowing immortal long
* poll
*/
lws_http_mark_immortal(h2n->swsi);
lwsl_info("%s: %p: h2 stream entering long poll\n",
__func__, h2n->swsi);
} else {
h2n->swsi->h2.END_STREAM =
!!(h2n->flags & LWS_H2_FLAG_END_STREAM);
lwsl_info("%s: hdr END_STREAM = %d\n",__func__,
lwsl_debug("%s: hdr END_STREAM = %d\n",__func__,
h2n->swsi->h2.END_STREAM);
}
h2n->cont_exp = !(h2n->flags & LWS_H2_FLAG_END_HEADERS);
h2n->cont_exp_sid = h2n->sid;
@ -1863,12 +1873,9 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
*/
if (!wsi->immortal_substream_count)
lws_set_timeout(wsi,
PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
#if defined(LWS_AMAZON_RTOS) || defined(LWS_AMAZON_LINUX)
wsi->vhost->keepalive_timeout);
#else
31);
#endif
PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE,
wsi->vhost->keepalive_timeout ?
wsi->vhost->keepalive_timeout : 31);
if (!h2n->swsi)
break;
@ -2411,3 +2418,21 @@ lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
return lws_ptr_diff(buf, oldbuf);
}
int
lws_h2_client_stream_long_poll_rxonly(struct lws *wsi)
{
if (!wsi->http2_substream)
return 1;
/*
* Elect to send an empty DATA with END_STREAM, to force the stream
* into HALF_CLOSED LOCAL
*/
wsi->h2.long_poll = 1;
wsi->h2.send_END_STREAM = 1;
lws_callback_on_writable(wsi);
return 0;
}

View file

@ -386,20 +386,22 @@ rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len,
/* if not in a state to send stuff, then just send nothing */
if (!lwsi_role_ws(wsi) &&
if (!lwsi_role_ws(wsi) && !wsi->h2_stream_immortal &&
base != LWS_WRITE_HTTP &&
base != LWS_WRITE_HTTP_FINAL &&
base != LWS_WRITE_HTTP_HEADERS_CONTINUATION &&
base != LWS_WRITE_HTTP_HEADERS &&
((lwsi_state(wsi) != LRS_RETURNED_CLOSE &&
lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE &&
lwsi_state(wsi) != LRS_ESTABLISHED &&
lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK)
#if defined(LWS_ROLE_WS)
|| base != LWS_WRITE_CLOSE
#endif
)) {
//assert(0);
lwsl_notice("binning wsistate 0x%x %d\n", wsi->wsistate, *wp);
lwsl_notice("%s: binning wsistate 0x%x %d\n", __func__,
wsi->wsistate, *wp);
return 0;
}
@ -486,7 +488,6 @@ static int
rops_check_upgrades_h2(struct lws *wsi)
{
#if defined(LWS_ROLE_WS)
struct lws *nwsi;
char *p;
/*
@ -504,21 +505,16 @@ rops_check_upgrades_h2(struct lws *wsi)
if (!p || strcmp(p, "websocket"))
return LWS_UPG_RET_CONTINUE;
nwsi = lws_get_network_wsi(wsi);
#if defined(LWS_WITH_SERVER_STATUS)
wsi->vhost->conn_stats.ws_upg++;
#endif
lwsl_info("Upgrade h2 to ws\n");
lws_http_mark_immortal(wsi);
wsi->h2_stream_carries_ws = 1;
nwsi->immortal_substream_count++;
if (lws_process_ws_upgrade(wsi))
return LWS_UPG_RET_BAIL;
if (nwsi->immortal_substream_count == 1)
lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0);
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
lwsl_info("Upgraded h2 to ws OK\n");
return LWS_UPG_RET_DONE;
@ -717,16 +713,6 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason)
lws_free_set_NULL(wsi->h2.pending_status_body);
}
if (wsi->h2_stream_carries_ws || wsi->h2_stream_carries_sse) {
struct lws *nwsi = lws_get_network_wsi(wsi);
nwsi->immortal_substream_count--;
/* if no ws, then put a timeout on the parent wsi */
if (!nwsi->immortal_substream_count)
__lws_set_timeout(nwsi,
PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
}
return 0;
}
@ -1148,6 +1134,30 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
goto next_child;
}
#endif
/*
* set client wsi to immortal long-poll mode; send END_STREAM
* flag on headers to indicate to a server, that allows
* it, that you want them to leave the stream in a long poll
* ro immortal state. We have to send headers so the client
* understands the http connection is ongoing.
*/
if (w->h2.send_END_STREAM && w->h2.long_poll) {
uint8_t buf[LWS_PRE + 1];
enum lws_write_protocol wp = 0;
if (!rops_write_role_protocol_h2(w, buf + LWS_PRE, 0,
&wp)) {
lwsl_info("%s: wsi %p: entering ro long poll\n",
__func__, w);
lws_http_mark_immortal(w);
} else
lwsl_err("%s: wsi %p: failed to set long poll\n",
__func__, w);
goto next_child;
}
if (lws_callback_as_writeable(w)) {
lwsl_info("Closing POLLOUT child (end stream %d)\n",
w->h2.send_END_STREAM);

View file

@ -328,12 +328,13 @@ struct _lws_h2_related {
int my_priority;
uint32_t dependent_on;
unsigned int END_STREAM:1;
unsigned int END_HEADERS:1;
unsigned int send_END_STREAM:1;
unsigned int GOING_AWAY;
unsigned int requested_POLLOUT:1;
unsigned int skint:1;
uint16_t END_STREAM:1;
uint16_t END_HEADERS:1;
uint16_t send_END_STREAM:1;
uint16_t long_poll:1;
uint16_t GOING_AWAY;
uint16_t requested_POLLOUT:1;
uint16_t skint:1;
uint16_t round_robin_POLLOUT;
uint16_t count_POLLOUT_children;

View file

@ -132,6 +132,7 @@ static const char * const paths_vhosts[] = {
"vhosts[].allow-http-on-https",
"vhosts[].disable-no-protocol-ws-upgrades",
"vhosts[].h2-half-closed-long-poll",
};
enum lejp_vhost_paths {
@ -199,6 +200,7 @@ enum lejp_vhost_paths {
LEJPVP_FLAG_ALLOW_HTTP_ON_HTTPS,
LEJPVP_FLAG_DISABLE_NO_PROTOCOL_WS_UPGRADES,
LEJPVP_FLAG_H2_HALF_CLOSED_LONG_POLL,
};
#define MAX_PLUGIN_DIRS 10
@ -864,6 +866,11 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
a->reject_ws_with_no_protocol = 1;
return 0;
case LEJPVP_FLAG_H2_HALF_CLOSED_LONG_POLL:
set_reset_flag(&a->info->options, ctx->buf,
LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL);
return 0;
default:
return 0;
}

View file

@ -1340,8 +1340,9 @@ lws_http_action(struct lws *wsi)
* if there is content supposed to be coming,
* put a timeout on it having arrived
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
wsi->context->timeout_secs);
if (!wsi->h2_stream_immortal)
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
wsi->context->timeout_secs);
#ifdef LWS_WITH_TLS
if (wsi->tls.redirect_to_https) {
/*

View file

@ -17,6 +17,9 @@
#include <signal.h>
static int interrupted, bad = 1, status;
#if defined(LWS_WITH_HTTP2)
static int long_poll;
#endif
static struct lws *client_wsi;
static int
@ -35,11 +38,22 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason,
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
status = lws_http_client_http_response(wsi);
lwsl_user("Connected with server response: %d\n", status);
#if defined(LWS_WITH_HTTP2)
if (long_poll) {
lwsl_user("%s: Client entering long poll mode\n", __func__);
lws_h2_client_stream_long_poll_rxonly(wsi);
}
#endif
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 defined(LWS_WITH_HTTP2)
if (long_poll)
lwsl_notice("long poll rx: '%.*s'\n",
(int)len, (const char *)in);
#endif
#if 0 /* enable to dump the html */
{
const char *p = in;
@ -155,8 +169,16 @@ int main(int argc, const char **argv)
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
if (!lws_cmdline_option(argc, argv, "-n"))
if (!lws_cmdline_option(argc, argv, "-n")) {
i.ssl_connection = LCCSCF_USE_SSL;
#if defined(LWS_WITH_HTTP2)
/* requires h2 */
if (lws_cmdline_option(argc, argv, "--long-poll")) {
lwsl_user("%s: long poll mode\n", __func__);
long_poll = 1;
}
#endif
}
if (lws_cmdline_option(argc, argv, "-l")) {
i.port = 7681;

View file

@ -0,0 +1,80 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-http-server-h2-long-poll)
set(SRCS minimal-http-server.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_H1 1 requirements)
require_lws_config(LWS_WITH_SERVER 1 requirements)
require_lws_config(LWS_WITH_HTTP2 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,18 @@
# lws minimal http server
## build
```
$ cmake . && make
```
## usage
```
$ ./lws-minimal-http-server
[2018/03/04 09:30:02:7986] USER: LWS minimal http server | visit http://localhost:7681
[2018/03/04 09:30:02:7986] NOTICE: Creating Vhost 'default' port 7681, 1 protocols, IPv6 on
```
Visit http://localhost:7681

View file

@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
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
e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE=
-----END CERTIFICATE-----

View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
fBrpEY1IATtPq1taBZZogRqI3rOkkPk=
-----END PRIVATE KEY-----

View file

@ -0,0 +1,147 @@
/*
* lws-minimal-http-server-h2-long-poll
*
* 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 an h2 server that supports "long poll"
* immortal client connections. For simplicity it doesn't serve
* any regular files, you can add a mount to do it if you want.
*
* The protocol keeps the long poll h2 stream open, and sends
* the time on the stream once per minute. Normally idle h2
* connections are closed by default within 30s, so this demonstrates
* the stream and network connection are operating as "immortal"
* on both sides.
*
* See http-client/minimal-http-client-h2-long-poll for the
* client example that connects and transitions the stream to the
* immortal long poll mode.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted;
struct pss {
struct lws *wsi;
lws_sorted_usec_list_t sul;
char pending;
};
static void
sul_cb(lws_sorted_usec_list_t *sul)
{
struct pss *pss = (struct pss *)lws_container_of(sul, struct pss, sul);
pss->pending = 1;
lws_callback_on_writable(pss->wsi);
/* interval 1min... longer than any normal timeout */
lws_sul_schedule(lws_get_context(pss->wsi), 0, &pss->sul, sul_cb,
60 * LWS_US_PER_SEC);
}
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;
uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
*start = &buf[LWS_PRE], *p = start,
*end = p + sizeof(buf) - LWS_PRE;
int m, n;
switch (reason) {
case LWS_CALLBACK_HTTP:
lwsl_user("%s: connect\n", __func__);
pss->wsi = wsi;
if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
"text/html",
LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */
&p, end))
return 1;
if (lws_finalize_write_http_header(wsi, start, &p, end))
return 1;
sul_cb(&pss->sul);
return 0;
case LWS_CALLBACK_CLOSED_HTTP:
if (!pss)
break;
lws_sul_schedule(lws_get_context(wsi), 0, &pss->sul, sul_cb,
LWS_SET_TIMER_USEC_CANCEL);
break;
case LWS_CALLBACK_HTTP_WRITEABLE:
if (!pss->pending)
break;
n = lws_snprintf((char *)p, sizeof(buf) - LWS_PRE, "%llu",
(unsigned long long)lws_now_usecs());
m = lws_write(wsi, p, n, LWS_WRITE_HTTP);
if (m < n) {
lwsl_err("ERROR %d writing to socket\n", n);
return -1;
}
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static struct lws_protocols protocols[] = {
{ "http", callback_http, sizeof(struct pss), 0 },
{ NULL, NULL, 0, 0 } /* terminator */
};
void sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
const char *p;
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal http server h2 long poll\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.port = 7681;
info.ssl_cert_filepath = "localhost-100y.cert";
info.ssl_private_key_filepath = "localhost-100y.key";
info.protocols = protocols;
info.options =
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
LWS_SERVER_OPTION_VH_H2_HALF_CLOSED_LONG_POLL |
LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
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);
return 0;
}