1 /*
2 * lws-minimal-http-server-sse
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * This demonstrates a minimal http server that can serve both normal static
10 * content and server-side event connections.
11 *
12 * To keep it simple, it serves the static stuff from the subdirectory
13 * "./mount-origin" of the directory it was started in.
14 *
15 * You can change that by changing mount.origin below.
16 */
17
18 #include <libwebsockets.h>
19 #include <string.h>
20 #include <signal.h>
21 #include <time.h>
22 #if defined(WIN32)
23 #define HAVE_STRUCT_TIMESPEC
24 #if defined(pid_t)
25 #undef pid_t
26 #endif
27 #endif
28 #include <pthread.h>
29
30 /*
31 * Unlike ws, http is a stateless protocol. This pss only exists for the
32 * duration of a single http transaction. With http/1.1 keep-alive and http/2,
33 * that is unrelated to (shorter than) the lifetime of the network connection.
34 */
35 struct pss {
36 time_t established;
37 };
38
39 static int interrupted;
40
41 #define SECS_REPORT 3
42
43 static int
callback_sse(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)44 callback_sse(struct lws *wsi, enum lws_callback_reasons reason, void *user,
45 void *in, size_t len)
46 {
47 struct pss *pss = (struct pss *)user;
48 uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE], *start = &buf[LWS_PRE],
49 *p = start, *end = &buf[sizeof(buf) - 1];
50
51 switch (reason) {
52 case LWS_CALLBACK_HTTP:
53 /*
54 * `in` contains the url part after our mountpoint /sse, if any
55 * you can use this to determine what data to return and store
56 * that in the pss
57 */
58 lwsl_notice("%s: LWS_CALLBACK_HTTP: '%s'\n", __func__,
59 (const char *)in);
60
61 pss->established = time(NULL);
62
63 /* SSE requires a response with this content-type */
64
65 if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK,
66 "text/event-stream",
67 LWS_ILLEGAL_HTTP_CONTENT_LEN,
68 &p, end))
69 return 1;
70
71 if (lws_finalize_write_http_header(wsi, start, &p, end))
72 return 1;
73
74 /*
75 * This tells lws we are no longer a normal http stream,
76 * but are an "immortal" (plus or minus whatever timeout you
77 * set on it afterwards) SSE stream. In http/2 case that also
78 * stops idle timeouts being applied to the network connection
79 * while this wsi is still open.
80 */
81 lws_http_mark_sse(wsi);
82
83 /* write the body separately */
84
85 lws_callback_on_writable(wsi);
86
87 return 0;
88
89 case LWS_CALLBACK_HTTP_WRITEABLE:
90
91 lwsl_notice("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
92
93 if (!pss)
94 break;
95
96 /*
97 * to keep this demo as simple as possible, each client has his
98 * own private data and timer.
99 */
100
101 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
102 "data: %llu\x0d\x0a\x0d\x0a",
103 (unsigned long long)(time(NULL) -
104 pss->established));
105
106 if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff_size_t(p, start),
107 LWS_WRITE_HTTP) != lws_ptr_diff(p, start))
108 return 1;
109
110 lws_set_timer_usecs(wsi, SECS_REPORT * LWS_USEC_PER_SEC);
111
112 return 0;
113
114 case LWS_CALLBACK_TIMER:
115
116 lwsl_notice("%s: LWS_CALLBACK_TIMER\n", __func__);
117 lws_callback_on_writable(wsi);
118
119 return 0;
120
121 default:
122 break;
123 }
124
125 return lws_callback_http_dummy(wsi, reason, user, in, len);
126 }
127
128 static struct lws_protocols protocols[] = {
129 { "http", lws_callback_http_dummy, 0, 0, 0, NULL, 0 },
130 { "sse", callback_sse, sizeof(struct pss), 0, 0, NULL, 0 },
131 LWS_PROTOCOL_LIST_TERM
132 };
133
134 /* override the default mount for /sse in the URL space */
135
136 static const struct lws_http_mount mount_sse = {
137 /* .mount_next */ NULL, /* linked-list "next" */
138 /* .mountpoint */ "/sse", /* mountpoint URL */
139 /* .origin */ NULL, /* protocol */
140 /* .def */ NULL,
141 /* .protocol */ "sse",
142 /* .cgienv */ NULL,
143 /* .extra_mimetypes */ NULL,
144 /* .interpret */ NULL,
145 /* .cgi_timeout */ 0,
146 /* .cache_max_age */ 0,
147 /* .auth_mask */ 0,
148 /* .cache_reusable */ 0,
149 /* .cache_revalidate */ 0,
150 /* .cache_intermediaries */ 0,
151 /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */
152 /* .mountpoint_len */ 4, /* char count */
153 /* .basic_auth_login_file */ NULL,
154 };
155
156 /* default mount serves the URL space from ./mount-origin */
157
158 static const struct lws_http_mount mount = {
159 /* .mount_next */ &mount_sse, /* linked-list "next" */
160 /* .mountpoint */ "/", /* mountpoint URL */
161 /* .origin */ "./mount-origin", /* serve from dir */
162 /* .def */ "index.html", /* default filename */
163 /* .protocol */ NULL,
164 /* .cgienv */ NULL,
165 /* .extra_mimetypes */ NULL,
166 /* .interpret */ NULL,
167 /* .cgi_timeout */ 0,
168 /* .cache_max_age */ 0,
169 /* .auth_mask */ 0,
170 /* .cache_reusable */ 0,
171 /* .cache_revalidate */ 0,
172 /* .cache_intermediaries */ 0,
173 /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
174 /* .mountpoint_len */ 1, /* char count */
175 /* .basic_auth_login_file */ NULL,
176 };
177
sigint_handler(int sig)178 void sigint_handler(int sig)
179 {
180 interrupted = 1;
181 }
182
main(int argc,const char ** argv)183 int main(int argc, const char **argv)
184 {
185 struct lws_context_creation_info info;
186 struct lws_context *context;
187 const char *p;
188 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
189 /* for LLL_ verbosity above NOTICE to be built into lws,
190 * lws must have been configured and built with
191 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
192 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
193 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
194 /* | LLL_DEBUG */;
195
196 signal(SIGINT, sigint_handler);
197
198 if ((p = lws_cmdline_option(argc, argv, "-d")))
199 logs = atoi(p);
200
201 lws_set_log_level(logs, NULL);
202 lwsl_user("LWS minimal http Server-Side Events | visit http://localhost:7681\n");
203
204 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
205
206 info.protocols = protocols;
207 info.mounts = &mount;
208 info.options =
209 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
210 info.port = 7681;
211
212 #if defined(LWS_WITH_TLS)
213 if (lws_cmdline_option(argc, argv, "-s")) {
214 info.port = 443;
215 info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
216 info.ssl_cert_filepath = "localhost-100y.cert";
217 info.ssl_private_key_filepath = "localhost-100y.key";
218 }
219 #endif
220
221 context = lws_create_context(&info);
222 if (!context) {
223 lwsl_err("lws init failed\n");
224 return 1;
225 }
226
227 while (n >= 0 && !interrupted)
228 n = lws_service(context, 0);
229
230 lws_context_destroy(context);
231
232 return 0;
233 }
234