mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
minimal examples for Server Side Events
This commit is contained in:
parent
658c752998
commit
97e36d8901
15 changed files with 938 additions and 0 deletions
|
@ -9,6 +9,8 @@ minimal-http-server-libuv-foreign|Same as minimal-http-server but lws uses a for
|
|||
minimal-http-server-libuv|Same as minimal-http-server but lws uses its own libuv event loop
|
||||
minimal-http-server-multivhost|Same as minimal-http-server but three different vhosts
|
||||
minimal-http-server-smp|Multiple service threads
|
||||
minimal-http-server-sse-ring|Server Side Events with ringbuffer and threaded event sources
|
||||
minimal-http-server-sse|Simple Server Side Events
|
||||
minimal-http-server-tls|Serves a directory over http/1 or http/2 with TLS (SSL), custom 404 handler
|
||||
minimal-http-server|Serves a directory over http/1, custom 404 handler
|
||||
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
include(CheckIncludeFile)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(SAMP lws-minimal-http-server-sse-ring)
|
||||
set(SRCS minimal-http-server-sse-ring.c)
|
||||
|
||||
MACRO(require_pthreads result)
|
||||
CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
|
||||
if (NOT LWS_HAVE_PTHREAD_H)
|
||||
if (LWS_WITH_MINIMAL_EXAMPLES)
|
||||
set(result 0)
|
||||
else()
|
||||
message(FATAL_ERROR "threading support requires pthreads")
|
||||
endif()
|
||||
endif()
|
||||
ENDMACRO()
|
||||
|
||||
# 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_pthreads(requirements)
|
||||
require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
|
||||
|
||||
if (requirements)
|
||||
add_executable(${SAMP} ${SRCS})
|
||||
|
||||
if (websockets_shared)
|
||||
target_link_libraries(${SAMP} websockets_shared pthread)
|
||||
add_dependencies(${SAMP} websockets_shared)
|
||||
else()
|
||||
target_link_libraries(${SAMP} websockets pthread)
|
||||
endif()
|
||||
endif()
|
|
@ -0,0 +1,27 @@
|
|||
# lws minimal http Server Side Events + ringbuffer
|
||||
|
||||
This demonstates serving both normal content and
|
||||
content over Server Side Events, where all clients
|
||||
see the same data via a ringbuffer.
|
||||
|
||||
Two separate threads generate content into the
|
||||
ringbuffer at random intervals.
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
$ cmake . && make
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
```
|
||||
$ ./lws-minimal-http-server-sse
|
||||
[2018/04/20 06:09:56:9974] USER: LWS minimal http Server-Side Events + ring | visit http://localhost:7681
|
||||
[2018/04/20 06:09:57:0148] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
|
||||
```
|
||||
|
||||
Visit http://localhost:7681, which connects back to the server using SSE
|
||||
and displays the incoming data. Connecting from multiple browsers shows
|
||||
the same content from the server ringbuffer.
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* lws-minimal-http-server-sse
|
||||
*
|
||||
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* This demonstrates a minimal http server that can serve both normal static
|
||||
* content and server-side event connections.
|
||||
*
|
||||
* To keep it simple, it serves the static stuff from the subdirectory
|
||||
* "./mount-origin" of the directory it was started in.
|
||||
*
|
||||
* You can change that by changing mount.origin below.
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* one of these created for each message in the ringbuffer */
|
||||
|
||||
struct msg {
|
||||
void *payload; /* is malloc'd */
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/*
|
||||
* Unlike ws, http is a stateless protocol. This pss only exists for the
|
||||
* duration of a single http transaction. With http/1.1 keep-alive and http/2,
|
||||
* that is unrelated to (shorter than) the lifetime of the network connection.
|
||||
*/
|
||||
struct pss {
|
||||
struct pss *pss_list;
|
||||
struct lws *wsi;
|
||||
uint32_t tail;
|
||||
};
|
||||
|
||||
/* one of these is created for each vhost our protocol is used with */
|
||||
|
||||
struct vhd {
|
||||
struct lws_context *context;
|
||||
struct lws_vhost *vhost;
|
||||
const struct lws_protocols *protocol;
|
||||
|
||||
struct pss *pss_list; /* linked-list of live pss*/
|
||||
pthread_t pthread_spam[2];
|
||||
|
||||
pthread_mutex_t lock_ring; /* serialize access to the ring buffer */
|
||||
struct lws_ring *ring; /* ringbuffer holding unsent messages */
|
||||
char finished;
|
||||
};
|
||||
|
||||
static int interrupted;
|
||||
|
||||
/* destroys the message when everyone has had a copy of it */
|
||||
|
||||
static void
|
||||
__minimal_destroy_message(void *_msg)
|
||||
{
|
||||
struct msg *msg = _msg;
|
||||
|
||||
free(msg->payload);
|
||||
msg->payload = NULL;
|
||||
msg->len = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This runs under the "spam thread" thread context only.
|
||||
*
|
||||
* We spawn two threads that generate messages with this.
|
||||
*
|
||||
*/
|
||||
|
||||
static void *
|
||||
thread_spam(void *d)
|
||||
{
|
||||
struct vhd *vhd = (struct vhd *)d;
|
||||
struct msg amsg;
|
||||
int len = 128, index = 1, n;
|
||||
|
||||
do {
|
||||
/* don't generate output if nobody connected */
|
||||
if (!vhd->pss_list)
|
||||
goto wait;
|
||||
|
||||
pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */
|
||||
|
||||
/* only create if space in ringbuffer */
|
||||
n = (int)lws_ring_get_count_free_elements(vhd->ring);
|
||||
if (!n) {
|
||||
lwsl_user("dropping!\n");
|
||||
goto wait_unlock;
|
||||
}
|
||||
|
||||
amsg.payload = malloc(len);
|
||||
if (!amsg.payload) {
|
||||
lwsl_user("OOM: dropping\n");
|
||||
goto wait_unlock;
|
||||
}
|
||||
n = lws_snprintf((char *)amsg.payload, len,
|
||||
"%s: tid: %p, msg: %d", __func__,
|
||||
(void *)pthread_self(), index++);
|
||||
amsg.len = n;
|
||||
n = lws_ring_insert(vhd->ring, &amsg, 1);
|
||||
if (n != 1) {
|
||||
__minimal_destroy_message(&amsg);
|
||||
lwsl_user("dropping!\n");
|
||||
} else
|
||||
/*
|
||||
* This will cause a LWS_CALLBACK_EVENT_WAIT_CANCELLED
|
||||
* in the lws service thread context.
|
||||
*/
|
||||
lws_cancel_service(vhd->context);
|
||||
|
||||
wait_unlock:
|
||||
pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */
|
||||
|
||||
wait:
|
||||
usleep(100000 + (rand() & 0xfffff));
|
||||
|
||||
} while (!vhd->finished);
|
||||
|
||||
lwsl_notice("thread_spam %p exiting\n", (void *)pthread_self());
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
callback_sse(struct lws *wsi, enum lws_callback_reasons reason, void *user,
|
||||
void *in, size_t len)
|
||||
{
|
||||
struct pss *pss = (struct pss *)user;
|
||||
struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get(
|
||||
lws_get_vhost(wsi), lws_get_protocol(wsi));
|
||||
uint8_t buf[LWS_PRE + 256], *start = &buf[LWS_PRE], *p = start,
|
||||
*end = &buf[sizeof(buf) - 1];
|
||||
const struct msg *pmsg;
|
||||
void *retval;
|
||||
int n;
|
||||
|
||||
switch (reason) {
|
||||
|
||||
/* --- vhost protocol lifecycle --- */
|
||||
|
||||
case LWS_CALLBACK_PROTOCOL_INIT:
|
||||
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
||||
lws_get_protocol(wsi), sizeof(struct vhd));
|
||||
vhd->context = lws_get_context(wsi);
|
||||
vhd->protocol = lws_get_protocol(wsi);
|
||||
vhd->vhost = lws_get_vhost(wsi);
|
||||
|
||||
vhd->ring = lws_ring_create(sizeof(struct msg), 8,
|
||||
__minimal_destroy_message);
|
||||
if (!vhd->ring)
|
||||
return 1;
|
||||
|
||||
pthread_mutex_init(&vhd->lock_ring, NULL);
|
||||
|
||||
/* start the content-creating threads */
|
||||
|
||||
for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
|
||||
if (pthread_create(&vhd->pthread_spam[n], NULL,
|
||||
thread_spam, vhd)) {
|
||||
lwsl_err("thread creation failed\n");
|
||||
goto init_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
||||
init_fail:
|
||||
vhd->finished = 1;
|
||||
for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++)
|
||||
if (vhd->pthread_spam[n])
|
||||
pthread_join(vhd->pthread_spam[n], &retval);
|
||||
|
||||
if (vhd->ring)
|
||||
lws_ring_destroy(vhd->ring);
|
||||
|
||||
pthread_mutex_destroy(&vhd->lock_ring);
|
||||
return 0;
|
||||
|
||||
/* --- http connection lifecycle --- */
|
||||
|
||||
case LWS_CALLBACK_HTTP:
|
||||
/*
|
||||
* `in` contains the url part after our mountpoint /sse, if any
|
||||
* you can use this to determine what data to return and store
|
||||
* that in the pss
|
||||
*/
|
||||
lwsl_info("%s: LWS_CALLBACK_HTTP: '%s'\n", __func__,
|
||||
(const char *)in);
|
||||
|
||||
/* SSE requires a http OK response with this content-type */
|
||||
|
||||
if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
|
||||
"text/event-stream",
|
||||
LWS_ILLEGAL_HTTP_CONTENT_LEN,
|
||||
&p, end))
|
||||
return 1;
|
||||
|
||||
if (lws_finalize_write_http_header(wsi, start, &p, end))
|
||||
return 1;
|
||||
|
||||
/* add ourselves to the list of live pss held in the vhd */
|
||||
|
||||
lws_ll_fwd_insert(pss, pss_list, vhd->pss_list);
|
||||
pss->tail = lws_ring_get_oldest_tail(vhd->ring);
|
||||
pss->wsi = wsi;
|
||||
|
||||
/* Unlike a normal http connection, we don't want any specific
|
||||
* timeout. We want to stay up until the client drops us */
|
||||
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
|
||||
/* write the body separately */
|
||||
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_CLOSED_HTTP:
|
||||
/* remove our closing pss from the list of live pss */
|
||||
|
||||
lws_ll_fwd_remove(struct pss, pss_list, pss, vhd->pss_list);
|
||||
return 0;
|
||||
|
||||
/* --- data transfer --- */
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
|
||||
lwsl_info("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
|
||||
|
||||
pmsg = lws_ring_get_element(vhd->ring, &pss->tail);
|
||||
if (!pmsg)
|
||||
break;
|
||||
|
||||
p += lws_snprintf((char *)p, end - p,
|
||||
"data: %s\x0d\x0a\x0d\x0a",
|
||||
(const char *)pmsg->payload);
|
||||
|
||||
if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start),
|
||||
LWS_WRITE_HTTP) != lws_ptr_diff(p, start))
|
||||
return 1;
|
||||
|
||||
lws_ring_consume_and_update_oldest_tail(
|
||||
vhd->ring, /* lws_ring object */
|
||||
struct pss, /* type of objects with tails */
|
||||
&pss->tail, /* tail of guy doing the consuming */
|
||||
1, /* number of payload objects being consumed */
|
||||
vhd->pss_list, /* head of list of objects with tails */
|
||||
tail, /* member name of tail in objects with tails */
|
||||
pss_list /* member name of next object in objects with tails */
|
||||
);
|
||||
|
||||
if (lws_ring_get_element(vhd->ring, &pss->tail))
|
||||
/* come back as soon as we can write more */
|
||||
lws_callback_on_writable(pss->wsi);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
|
||||
/*
|
||||
* let everybody know we want to write something on them
|
||||
* as soon as they are ready
|
||||
*/
|
||||
lws_start_foreach_llp(struct pss **, ppss, vhd->pss_list) {
|
||||
lws_callback_on_writable((*ppss)->wsi);
|
||||
} lws_end_foreach_llp(ppss, pss_list);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return lws_callback_http_dummy(wsi, reason, user, in, len);
|
||||
}
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{ "http", lws_callback_http_dummy, 0, 0 },
|
||||
{ "sse", callback_sse, sizeof(struct pss), 0 },
|
||||
{ NULL, NULL, 0, 0 } /* terminator */
|
||||
};
|
||||
|
||||
/* override the default mount for /sse in the URL space */
|
||||
|
||||
static const struct lws_http_mount mount_sse = {
|
||||
/* .mount_next */ NULL, /* linked-list "next" */
|
||||
/* .mountpoint */ "/sse", /* mountpoint URL */
|
||||
/* .origin */ NULL, /* protocol */
|
||||
/* .def */ NULL,
|
||||
/* .protocol */ "sse",
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */
|
||||
/* .mountpoint_len */ 4, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
/* default mount serves the URL space from ./mount-origin */
|
||||
|
||||
static const struct lws_http_mount mount = {
|
||||
/* .mount_next */ &mount_sse, /* linked-list "next" */
|
||||
/* .mountpoint */ "/", /* mountpoint URL */
|
||||
/* .origin */ "./mount-origin", /* serve from dir */
|
||||
/* .def */ "index.html", /* default filename */
|
||||
/* .protocol */ NULL,
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
|
||||
/* .mountpoint_len */ 1, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
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
|
||||
/* 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_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
|
||||
/* | LLL_DEBUG */;
|
||||
|
||||
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-Side Events + ring | visit http://localhost:7681\n");
|
||||
|
||||
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
|
||||
info.port = 7681;
|
||||
info.protocols = protocols;
|
||||
info.mounts = &mount;
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (!context) {
|
||||
lwsl_err("lws init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (n >= 0 && !interrupted)
|
||||
n = lws_service(context, 1000);
|
||||
|
||||
lws_context_destroy(context);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<body>
|
||||
<img src="libwebsockets.org-logo.png"><br>
|
||||
<h1>404</h1>
|
||||
Sorry, that file doesn't exist.
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,58 @@
|
|||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<body>
|
||||
<img src="libwebsockets.org-logo.png"><br>
|
||||
|
||||
Hello from the <b>minimal http Server Side Events + Ring example</b>.
|
||||
<p>
|
||||
This is a static page served from ./mount-origin/index.html.
|
||||
<p>
|
||||
It connects back to the server at <i>/sse/sourcename</i> using EventSource()<br>
|
||||
and displays the perioding incoming event data below.
|
||||
<p>
|
||||
The data is being produced by two asynchronous threads at the server,
|
||||
which each sleep for a random period inbetween samples.
|
||||
<p>
|
||||
<textarea id=r readonly cols=60 rows=20></textarea><br>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
var head = 0, tail = 0, ring = new Array();
|
||||
|
||||
es = new EventSource("/sse/sourcename");
|
||||
try {
|
||||
es.onopen = function() {
|
||||
// console.log("EventSource opened");
|
||||
document.getElementById("r").disabled = 0;
|
||||
}
|
||||
|
||||
es.onmessage = function got_packet(msg) {
|
||||
var n, s = "";
|
||||
|
||||
// console.log(msg.data);
|
||||
ring[head] = msg.data + "\n";
|
||||
head = (head + 1) % 50;
|
||||
if (tail == head)
|
||||
tail = (tail + 1) % 50;
|
||||
|
||||
n = tail;
|
||||
do {
|
||||
s = s + ring[n];
|
||||
n = (n + 1) % 50;
|
||||
} while (n != head);
|
||||
|
||||
document.getElementById("r").value = s;
|
||||
document.getElementById("r").scrollTop =
|
||||
document.getElementById("r").scrollHeight;
|
||||
}
|
||||
|
||||
/* there is no onclose() for EventSource */
|
||||
|
||||
} catch(exception) {
|
||||
alert('<p>Error' + exception);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
|
@ -0,0 +1,77 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
include(CheckCSourceCompiles)
|
||||
|
||||
set(SAMP lws-minimal-http-server-sse)
|
||||
set(SRCS minimal-http-server-sse.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_WITHOUT_SERVER 0 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()
|
|
@ -0,0 +1,23 @@
|
|||
# lws minimal http Server Side Events
|
||||
|
||||
This demonstates serving both normal content and
|
||||
content over Server Side Events.
|
||||
|
||||
## build
|
||||
|
||||
```
|
||||
$ cmake . && make
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
```
|
||||
$ ./lws-minimal-http-server-sse
|
||||
[2018/04/20 06:09:56:9974] USER: LWS minimal http Server-Side Events | visit http://localhost:7681
|
||||
[2018/04/20 06:09:57:0148] NOTICE: Creating Vhost 'default' port 7681, 2 protocols, IPv6 off
|
||||
```
|
||||
|
||||
Visit http://localhost:7681, which connects back to the server using SSE
|
||||
and displays the incoming data. Connecting from multiple browsers shows
|
||||
content individual to the connection.
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* lws-minimal-http-server-sse
|
||||
*
|
||||
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
|
||||
*
|
||||
* This file is made available under the Creative Commons CC0 1.0
|
||||
* Universal Public Domain Dedication.
|
||||
*
|
||||
* This demonstrates a minimal http server that can serve both normal static
|
||||
* content and server-side event connections.
|
||||
*
|
||||
* To keep it simple, it serves the static stuff from the subdirectory
|
||||
* "./mount-origin" of the directory it was started in.
|
||||
*
|
||||
* You can change that by changing mount.origin below.
|
||||
*/
|
||||
|
||||
#include <libwebsockets.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/*
|
||||
* Unlike ws, http is a stateless protocol. This pss only exists for the
|
||||
* duration of a single http transaction. With http/1.1 keep-alive and http/2,
|
||||
* that is unrelated to (shorter than) the lifetime of the network connection.
|
||||
*/
|
||||
struct pss {
|
||||
time_t established;
|
||||
};
|
||||
|
||||
static int interrupted;
|
||||
|
||||
#define SECS_REPORT 3
|
||||
|
||||
static int
|
||||
callback_sse(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 + 256], *start = &buf[LWS_PRE], *p = start,
|
||||
*end = &buf[sizeof(buf) - 1];
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_HTTP:
|
||||
/*
|
||||
* `in` contains the url part after our mountpoint /sse, if any
|
||||
* you can use this to determine what data to return and store
|
||||
* that in the pss
|
||||
*/
|
||||
lwsl_notice("%s: LWS_CALLBACK_HTTP: '%s'\n", __func__,
|
||||
(const char *)in);
|
||||
|
||||
pss->established = time(NULL);
|
||||
|
||||
/* SSE requires a response with this content-type */
|
||||
|
||||
if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
|
||||
"text/event-stream",
|
||||
LWS_ILLEGAL_HTTP_CONTENT_LEN,
|
||||
&p, end))
|
||||
return 1;
|
||||
|
||||
if (lws_finalize_write_http_header(wsi, start, &p, end))
|
||||
return 1;
|
||||
|
||||
/* Unlike a normal http connection, we don't want any specific
|
||||
* timeout. We want to stay up until the client drops us */
|
||||
|
||||
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
||||
|
||||
/* write the body separately */
|
||||
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_HTTP_WRITEABLE:
|
||||
|
||||
lwsl_notice("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
|
||||
|
||||
if (!pss)
|
||||
break;
|
||||
|
||||
/*
|
||||
* to keep this demo as simple as possible, each client has his
|
||||
* own private data and timer.
|
||||
*/
|
||||
|
||||
p += lws_snprintf((char *)p, end - p,
|
||||
"data: %llu\x0d\x0a\x0d\x0a",
|
||||
(unsigned long long)time(NULL) -
|
||||
pss->established);
|
||||
|
||||
if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start),
|
||||
LWS_WRITE_HTTP) != lws_ptr_diff(p, start))
|
||||
return 1;
|
||||
|
||||
lws_set_timer_usecs(wsi, SECS_REPORT * LWS_USEC_PER_SEC);
|
||||
|
||||
return 0;
|
||||
|
||||
case LWS_CALLBACK_TIMER:
|
||||
|
||||
lwsl_notice("%s: LWS_CALLBACK_TIMER\n", __func__);
|
||||
lws_callback_on_writable(wsi);
|
||||
|
||||
return 0;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return lws_callback_http_dummy(wsi, reason, user, in, len);
|
||||
}
|
||||
|
||||
static struct lws_protocols protocols[] = {
|
||||
{ "http", lws_callback_http_dummy, 0, 0 },
|
||||
{ "sse", callback_sse, sizeof(struct pss), 0 },
|
||||
{ NULL, NULL, 0, 0 } /* terminator */
|
||||
};
|
||||
|
||||
/* override the default mount for /sse in the URL space */
|
||||
|
||||
static const struct lws_http_mount mount_sse = {
|
||||
/* .mount_next */ NULL, /* linked-list "next" */
|
||||
/* .mountpoint */ "/sse", /* mountpoint URL */
|
||||
/* .origin */ NULL, /* protocol */
|
||||
/* .def */ NULL,
|
||||
/* .protocol */ "sse",
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */
|
||||
/* .mountpoint_len */ 4, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
/* default mount serves the URL space from ./mount-origin */
|
||||
|
||||
static const struct lws_http_mount mount = {
|
||||
/* .mount_next */ &mount_sse, /* linked-list "next" */
|
||||
/* .mountpoint */ "/", /* mountpoint URL */
|
||||
/* .origin */ "./mount-origin", /* serve from dir */
|
||||
/* .def */ "index.html", /* default filename */
|
||||
/* .protocol */ NULL,
|
||||
/* .cgienv */ NULL,
|
||||
/* .extra_mimetypes */ NULL,
|
||||
/* .interpret */ NULL,
|
||||
/* .cgi_timeout */ 0,
|
||||
/* .cache_max_age */ 0,
|
||||
/* .auth_mask */ 0,
|
||||
/* .cache_reusable */ 0,
|
||||
/* .cache_revalidate */ 0,
|
||||
/* .cache_intermediaries */ 0,
|
||||
/* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
|
||||
/* .mountpoint_len */ 1, /* char count */
|
||||
/* .basic_auth_login_file */ NULL,
|
||||
};
|
||||
|
||||
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
|
||||
/* 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_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
|
||||
/* | LLL_DEBUG */;
|
||||
|
||||
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-Side Events | visit http://localhost:7681\n");
|
||||
|
||||
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
|
||||
info.port = 7681;
|
||||
info.protocols = protocols;
|
||||
info.mounts = &mount;
|
||||
|
||||
context = lws_create_context(&info);
|
||||
if (!context) {
|
||||
lwsl_err("lws init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (n >= 0 && !interrupted)
|
||||
n = lws_service(context, 1000);
|
||||
|
||||
lws_context_destroy(context);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<body>
|
||||
<img src="libwebsockets.org-logo.png"><br>
|
||||
<h1>404</h1>
|
||||
Sorry, that file doesn't exist.
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,55 @@
|
|||
<meta charset="UTF-8">
|
||||
<html>
|
||||
<body>
|
||||
<img src="libwebsockets.org-logo.png"><br>
|
||||
|
||||
Hello from the <b>minimal http Server Side Events example</b>.
|
||||
<p>
|
||||
This is a static page served from ./mount-origin/index.html.
|
||||
<p>
|
||||
It connects back to the server at <i>/sse/sourcename</i> using EventSource()<br>
|
||||
and displays the perioding incoming event data below.
|
||||
<p>
|
||||
<textarea id=r readonly cols=40 rows=20></textarea><br>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
var head = 0, tail = 0, ring = new Array();
|
||||
|
||||
es = new EventSource("/sse/sourcename");
|
||||
try {
|
||||
es.onopen = function() {
|
||||
// console.log("EventSource opened");
|
||||
document.getElementById("r").disabled = 0;
|
||||
}
|
||||
|
||||
es.onmessage = function got_packet(msg) {
|
||||
var n, s = "";
|
||||
|
||||
// console.log(msg.data);
|
||||
ring[head] = msg.data + "\n";
|
||||
head = (head + 1) % 50;
|
||||
if (tail == head)
|
||||
tail = (tail + 1) % 50;
|
||||
|
||||
n = tail;
|
||||
do {
|
||||
s = s + ring[n];
|
||||
n = (n + 1) % 50;
|
||||
} while (n != head);
|
||||
|
||||
document.getElementById("r").value = s;
|
||||
document.getElementById("r").scrollTop =
|
||||
document.getElementById("r").scrollHeight;
|
||||
}
|
||||
|
||||
/* there is no onclose() for EventSource */
|
||||
|
||||
} catch(exception) {
|
||||
alert('<p>Error' + exception);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 6.9 KiB |
Loading…
Add table
Reference in a new issue