1 /*
2 * lws-minimal-http-server-eventlib-foreign
3 *
4 * Written in 2010-2020 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 the most minimal http server you can make with lws that
10 * uses a libuv event loop created outside lws. It shows how lws can
11 * participate in someone else's event loop and clean up after itself.
12 *
13 * You choose the event loop to work with at runtime, by giving the
14 * --uv, --event or --ev switch. Lws has to have been configured to build the
15 * selected event lib support.
16 *
17 * To keep it simple, it serves stuff from the subdirectory
18 * "./mount-origin" of the directory it was started in.
19 * You can change that by changing mount.origin below.
20 */
21
22 #include <libwebsockets.h>
23 #include <string.h>
24 #include <signal.h>
25
26 #include "private.h"
27
28 static struct lws_context_creation_info info;
29 static const struct ops *ops = NULL;
30 struct lws_context *context;
31 int lifetime = 5, reported;
32
33 enum {
34 TEST_STATE_CREATE_LWS_CONTEXT,
35 TEST_STATE_DESTROY_LWS_CONTEXT,
36 TEST_STATE_EXIT
37 };
38
39 static int sequence = TEST_STATE_CREATE_LWS_CONTEXT;
40
41 static const struct lws_http_mount mount = {
42 /* .mount_next */ NULL, /* linked-list "next" */
43 /* .mountpoint */ "/", /* mountpoint URL */
44 /* .origin */ "./mount-origin", /* serve from dir */
45 /* .def */ "index.html", /* default filename */
46 /* .protocol */ NULL,
47 /* .cgienv */ NULL,
48 /* .extra_mimetypes */ NULL,
49 /* .interpret */ NULL,
50 /* .cgi_timeout */ 0,
51 /* .cache_max_age */ 0,
52 /* .auth_mask */ 0,
53 /* .cache_reusable */ 0,
54 /* .cache_revalidate */ 0,
55 /* .cache_intermediaries */ 0,
56 /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */
57 /* .mountpoint_len */ 1, /* char count */
58 /* .basic_auth_login_file */ NULL,
59 };
60
61 void
signal_cb(int signum)62 signal_cb(int signum)
63 {
64 lwsl_notice("Signal %d caught, exiting...\n", signum);
65
66 switch (signum) {
67 case SIGTERM:
68 case SIGINT:
69 break;
70 default:
71 break;
72 }
73
74 lws_context_destroy(context);
75 }
76
77 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)78 callback_http(struct lws *wsi, enum lws_callback_reasons reason,
79 void *user, void *in, size_t len)
80 {
81 switch (reason) {
82
83 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
84 lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: resp %u\n",
85 lws_http_client_http_response(wsi));
86 break;
87
88 /* because we are protocols[0] ... */
89 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
90 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
91 in ? (char *)in : "(null)");
92 break;
93
94 /* chunks of chunked content, with header removed */
95 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
96 lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
97 lwsl_hexdump_info(in, len);
98 return 0; /* don't passthru */
99
100 /* uninterpreted http content */
101 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
102 {
103 char buffer[1024 + LWS_PRE];
104 char *px = buffer + LWS_PRE;
105 int lenx = sizeof(buffer) - LWS_PRE;
106
107 if (lws_http_client_read(wsi, &px, &lenx) < 0)
108 return -1;
109 }
110 return 0; /* don't passthru */
111
112 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
113 lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %s\n",
114 lws_wsi_tag(wsi));
115 break;
116
117 case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
118 lwsl_info("%s: closed: %s\n", __func__, lws_wsi_tag(wsi));
119 break;
120
121 default:
122 break;
123 }
124
125 return lws_callback_http_dummy(wsi, reason, user, in, len);
126 }
127
128 static const struct lws_protocols protocols[] = {
129 { "httptest", callback_http, 0, 0, 0, NULL, 0},
130 LWS_PROTOCOL_LIST_TERM
131 };
132
133 static int
do_client_conn(void)134 do_client_conn(void)
135 {
136 struct lws_client_connect_info i;
137
138 memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
139
140 i.context = context;
141
142 i.ssl_connection = LCCSCF_USE_SSL;
143 i.port = 443;
144 i.address = "warmcat.com";
145
146 i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
147 LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
148 i.path = "/";
149 i.host = i.address;
150 i.origin = i.address;
151 i.method = "GET";
152 i.local_protocol_name = protocols[0].name;
153 #if defined(LWS_WITH_SYS_FAULT_INJECTION)
154 i.fi_wsi_name = "user";
155 #endif
156
157 if (!lws_client_connect_via_info(&i)) {
158 lwsl_err("Client creation failed\n");
159
160 return 1;
161 }
162
163 return 0;
164 }
165
166
167 /* this is called at 1Hz using a foreign loop timer */
168
169 void
foreign_timer_service(void * foreign_loop)170 foreign_timer_service(void *foreign_loop)
171 {
172 void *foreign_loops[1];
173
174 lwsl_user("Foreign 1Hz timer\n");
175
176 if (sequence == TEST_STATE_EXIT && !context && !reported) {
177 /*
178 * at this point the lws_context_destroy() we did earlier
179 * has completed and the entire context is wholly destroyed
180 */
181 lwsl_user("lws_destroy_context() done, continuing for 5s\n");
182 reported = 1;
183 }
184
185 if (--lifetime)
186 return;
187
188 switch (sequence++) {
189 case TEST_STATE_CREATE_LWS_CONTEXT:
190 /* this only has to exist for the duration of create context */
191 foreign_loops[0] = foreign_loop;
192 info.foreign_loops = foreign_loops;
193
194 context = lws_create_context(&info);
195 if (!context) {
196 lwsl_err("lws init failed\n");
197 return;
198 }
199 lwsl_user("LWS Context created and will be active for 10s\n");
200
201 do_client_conn();
202
203 lifetime = 11;
204 break;
205
206 case TEST_STATE_DESTROY_LWS_CONTEXT:
207 /* cleanup the lws part */
208 lwsl_user("Destroying lws context and continuing loop for 5s\n");
209 lws_context_destroy(context);
210 lifetime = 6;
211 break;
212
213 case TEST_STATE_EXIT:
214 lwsl_user("Deciding to exit foreign loop too\n");
215 ops->stop();
216 break;
217 default:
218 break;
219 }
220 }
221
main(int argc,const char ** argv)222 int main(int argc, const char **argv)
223 {
224 const char *p;
225 int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
226 /* for LLL_ verbosity above NOTICE to be built into lws,
227 * lws must have been configured and built with
228 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
229 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
230 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
231 /* | LLL_DEBUG */;
232
233 if ((p = lws_cmdline_option(argc, argv, "-d")))
234 logs = atoi(p);
235
236 lws_set_log_level(logs, NULL);
237 lwsl_user("LWS minimal http server eventlib + foreign loop |"
238 " visit http://localhost:7681\n");
239
240 /*
241 * We prepare the info here, but don't use it until later in the
242 * timer callback, to demonstrate the independence of the foreign loop
243 * and lws.
244 */
245
246 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
247 info.port = 7681;
248 if ((p = lws_cmdline_option(argc, argv, "-p")))
249 info.port = atoi(p);
250 info.mounts = &mount;
251 info.error_document_404 = "/404.html";
252 info.pcontext = &context;
253 info.protocols = protocols;
254 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
255 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
256
257 if (lws_cmdline_option(argc, argv, "-s")) {
258 info.ssl_cert_filepath = "localhost-100y.cert";
259 info.ssl_private_key_filepath = "localhost-100y.key";
260 }
261
262 /*
263 * We configure lws to use the chosen event loop, and select the
264 * matching event-lib specific code for our demo operations
265 */
266
267 #if defined(LWS_WITH_LIBUV)
268 if (lws_cmdline_option(argc, argv, "--uv")) {
269 info.options |= LWS_SERVER_OPTION_LIBUV;
270 ops = &ops_libuv;
271 lwsl_notice("%s: using libuv event loop\n", __func__);
272 } else
273 #endif
274 #if defined(LWS_WITH_LIBEVENT)
275 if (lws_cmdline_option(argc, argv, "--event")) {
276 info.options |= LWS_SERVER_OPTION_LIBEVENT;
277 ops = &ops_libevent;
278 lwsl_notice("%s: using libevent loop\n", __func__);
279 } else
280 #endif
281 #if defined(LWS_WITH_LIBEV)
282 if (lws_cmdline_option(argc, argv, "--ev")) {
283 info.options |= LWS_SERVER_OPTION_LIBEV;
284 ops = &ops_libev;
285 lwsl_notice("%s: using libev loop\n", __func__);
286 } else
287 #endif
288 #if defined(LWS_WITH_GLIB)
289 if (lws_cmdline_option(argc, argv, "--glib")) {
290 info.options |= LWS_SERVER_OPTION_GLIB;
291 ops = &ops_glib;
292 lwsl_notice("%s: using glib loop\n", __func__);
293 } else
294 #endif
295 #if defined(LWS_WITH_SDEVENT)
296 if (lws_cmdline_option(argc, argv, "--sd")) {
297 info.options |= LWS_SERVER_OPTION_SDEVENT;
298 ops = &ops_sdevent;
299 lwsl_notice("%s: using sd-event loop\n", __func__);
300 } else
301 #endif
302 #if defined(LWS_WITH_ULOOP)
303 if (lws_cmdline_option(argc, argv, "--uloop")) {
304 info.options |= LWS_SERVER_OPTION_ULOOP;
305 ops = &ops_uloop;
306 lwsl_notice("%s: using uloop loop\n", __func__);
307 } else
308 #endif
309 {
310 lwsl_err("This app only makes sense when used\n");
311 lwsl_err(" with a foreign loop, --uv, --event, --glib, --ev or --sd\n");
312
313 return 1;
314 }
315
316 lwsl_user(" This app creates a foreign event loop with a timer +\n");
317 lwsl_user(" signalhandler, and performs a test in three phases:\n");
318 lwsl_user("\n");
319 lwsl_user(" 1) 5s: Runs the loop with just the timer\n");
320 lwsl_user(" 2) 10s: create an lws context serving on localhost:7681\n");
321 lwsl_user(" using the same foreign loop. Destroy it after 10s.\n");
322 lwsl_user(" 3) 5s: Run the loop again with just the timer\n");
323 lwsl_user("\n");
324 lwsl_user(" Finally close only the timer and signalhandler and\n");
325 lwsl_user(" exit the loop cleanly\n");
326 lwsl_user("\n");
327
328 /* foreign loop specific startup and run */
329
330 ops->init_and_run();
331
332 lws_context_destroy(context);
333
334 /* foreign loop specific cleanup and exit */
335
336 ops->cleanup();
337
338 lwsl_user("%s: exiting...\n", __func__);
339
340 return 0;
341 }
342