diff --git a/minimal-examples/http-server/README.md b/minimal-examples/http-server/README.md index 84ef807d..212d5ee4 100644 --- a/minimal-examples/http-server/README.md +++ b/minimal-examples/http-server/README.md @@ -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 diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt new file mode 100644 index 00000000..a9723915 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse-ring/CMakeLists.txt @@ -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 \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() diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/README.md b/minimal-examples/http-server/minimal-http-server-sse-ring/README.md new file mode 100644 index 00000000..08c21bb0 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse-ring/README.md @@ -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. + diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c b/minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c new file mode 100644 index 00000000..80279827 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse-ring/minimal-http-server-sse-ring.c @@ -0,0 +1,377 @@ +/* + * lws-minimal-http-server-sse + * + * Copyright (C) 2018 Andy Green + * + * 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 +#include +#include +#include +#include + +/* 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; +} diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html new file mode 100644 index 00000000..1f7ae66e --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/404.html @@ -0,0 +1,9 @@ + + + +
+

404

+ Sorry, that file doesn't exist. + + + diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico new file mode 100644 index 00000000..c0cc2e3d Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/favicon.ico differ diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html new file mode 100644 index 00000000..3a7318a5 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/index.html @@ -0,0 +1,58 @@ + + + +
+ + Hello from the minimal http Server Side Events + Ring example. +

+ This is a static page served from ./mount-origin/index.html. +

+ It connects back to the server at /sse/sourcename using EventSource()
+ and displays the perioding incoming event data below. +

+ The data is being produced by two asynchronous threads at the server, + which each sleep for a random period inbetween samples. +

+
+ + + + + diff --git a/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.png b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.png new file mode 100644 index 00000000..2060a10c Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse-ring/mount-origin/libwebsockets.org-logo.png differ diff --git a/minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt b/minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt new file mode 100644 index 00000000..b96d6d8a --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse/CMakeLists.txt @@ -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 \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() diff --git a/minimal-examples/http-server/minimal-http-server-sse/README.md b/minimal-examples/http-server/minimal-http-server-sse/README.md new file mode 100644 index 00000000..7a423f48 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse/README.md @@ -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. + diff --git a/minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c b/minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c new file mode 100644 index 00000000..626fe57e --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse/minimal-http-server-sse.c @@ -0,0 +1,211 @@ +/* + * lws-minimal-http-server-sse + * + * Copyright (C) 2018 Andy Green + * + * 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 +#include +#include +#include +#include + +/* + * 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; +} diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html new file mode 100644 index 00000000..1f7ae66e --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/404.html @@ -0,0 +1,9 @@ + + + +
+

404

+ Sorry, that file doesn't exist. + + + diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico new file mode 100644 index 00000000..c0cc2e3d Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/favicon.ico differ diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html new file mode 100644 index 00000000..5efff523 --- /dev/null +++ b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/index.html @@ -0,0 +1,55 @@ + + + +
+ + Hello from the minimal http Server Side Events example. +

+ This is a static page served from ./mount-origin/index.html. +

+ It connects back to the server at /sse/sourcename using EventSource()
+ and displays the perioding incoming event data below. +

+
+ + + + + diff --git a/minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.png b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.png new file mode 100644 index 00000000..2060a10c Binary files /dev/null and b/minimal-examples/http-server/minimal-http-server-sse/mount-origin/libwebsockets.org-logo.png differ