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