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
28static struct lws_context_creation_info info;
29static const struct ops *ops = NULL;
30struct lws_context *context;
31int lifetime = 5, reported;
32
33enum {
34	TEST_STATE_CREATE_LWS_CONTEXT,
35	TEST_STATE_DESTROY_LWS_CONTEXT,
36	TEST_STATE_EXIT
37};
38
39static int sequence = TEST_STATE_CREATE_LWS_CONTEXT;
40
41static 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
61void
62signal_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
77static int
78callback_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
128static const struct lws_protocols protocols[] = {
129	{ "httptest", callback_http, 0, 0, 0, NULL, 0},
130	LWS_PROTOCOL_LIST_TERM
131};
132
133static int
134do_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
169void
170foreign_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
222int 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