1/* 2 * lws-minimal-http-server-eventlib-custom 3 * 4 * Written in 2010-2021 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 using lws, on top of a custom "event 10 * library" that uses an existing application POLL loop. 11 * 12 * To keep it simple, it serves stuff from the subdirectory "./mount-origin" of 13 * the dir it was started in. Change mount.origin to serve from elsewhere. 14 */ 15 16#include <libwebsockets.h> 17#include <string.h> 18#include <signal.h> 19 20static int interrupted; 21static struct lws_context *context; 22 23#define MAX_CUSTOM_POLLFDS 64 24 25/* this represents the existing application poll loop context we want lws 26 * to cooperate with */ 27 28typedef struct custom_poll_ctx { 29 struct lws_pollfd pollfds[MAX_CUSTOM_POLLFDS]; 30 int count_pollfds; 31} custom_poll_ctx_t; 32 33/* for this example we just have the one, but it is passed into lws as a 34 * foreign loop pointer, and all callbacks have access to it via that, so it 35 * is not needed to be defined at file scope. */ 36static custom_poll_ctx_t a_cpcx; 37 38/* 39 * These are the custom event loop operators that just make the custom event 40 * loop able to work by itself. These would already exist in some form in an 41 * existing application. 42 */ 43 44static struct lws_pollfd * 45custom_poll_find_fd(custom_poll_ctx_t *cpcx, lws_sockfd_type fd) 46{ 47 int n; 48 49 for (n = 0; n < cpcx->count_pollfds; n++) 50 if (cpcx->pollfds[n].fd == fd) 51 return &cpcx->pollfds[n]; 52 53 return NULL; 54} 55 56static int 57custom_poll_add_fd(custom_poll_ctx_t *cpcx, lws_sockfd_type fd, int events) 58{ 59 struct lws_pollfd *pfd; 60 61 lwsl_info("%s: ADD fd %d, ev %d\n", __func__, fd, events); 62 63 pfd = custom_poll_find_fd(cpcx, fd); 64 if (pfd) { 65 lwsl_err("%s: ADD fd %d already in ext table\n", __func__, fd); 66 return 1; 67 } 68 69 if (cpcx->count_pollfds == LWS_ARRAY_SIZE(cpcx->pollfds)) { 70 lwsl_err("%s: no room left\n", __func__); 71 return 1; 72 } 73 74 pfd = &cpcx->pollfds[cpcx->count_pollfds++]; 75 pfd->fd = fd; 76 pfd->events = (short)events; 77 pfd->revents = 0; 78 79 return 0; 80} 81 82static int 83custom_poll_del_fd(custom_poll_ctx_t *cpcx, lws_sockfd_type fd) 84{ 85 struct lws_pollfd *pfd; 86 87 lwsl_info("%s: DEL fd %d\n", __func__, fd); 88 89 pfd = custom_poll_find_fd(cpcx, fd); 90 if (!pfd) { 91 lwsl_err("%s: DEL fd %d missing in ext table\n", __func__, fd); 92 return 1; 93 } 94 95 if (cpcx->count_pollfds > 1) 96 *pfd = cpcx->pollfds[cpcx->count_pollfds - 1]; 97 98 cpcx->count_pollfds--; 99 100 return 0; 101} 102 103static int 104custom_poll_change_fd(custom_poll_ctx_t *cpcx, lws_sockfd_type fd, 105 int events_add, int events_remove) 106{ 107 struct lws_pollfd *pfd; 108 109 lwsl_info("%s: CHG fd %d, ev_add %d, ev_rem %d\n", __func__, fd, 110 events_add, events_remove); 111 112 pfd = custom_poll_find_fd(cpcx, fd); 113 if (!pfd) 114 return 1; 115 116 pfd->events = (short)((pfd->events & (~events_remove)) | events_add); 117 118 return 0; 119} 120 121int 122custom_poll_run(custom_poll_ctx_t *cpcx) 123{ 124 int n; 125 126 while (!interrupted) { 127 128 /* 129 * Notice that the existing loop must consult with lws about 130 * the maximum wait timeout to use. Lws will reduce the 131 * timeout to the earliest scheduled event time if any earlier 132 * than the provided timeout. 133 */ 134 135 n = lws_service_adjust_timeout(context, 5000, 0); 136 137 lwsl_debug("%s: entering poll wait %dms\n", __func__, n); 138 139 n = poll(cpcx->pollfds, (nfds_t)cpcx->count_pollfds, n); 140 141 lwsl_debug("%s: exiting poll ret %d\n", __func__, n); 142 143 if (n <= 0) 144 continue; 145 146 for (n = 0; n < cpcx->count_pollfds; n++) { 147 lws_sockfd_type fd = cpcx->pollfds[n].fd; 148 int m; 149 150 if (!cpcx->pollfds[n].revents) 151 continue; 152 153 m = lws_service_fd(context, &cpcx->pollfds[n]); 154 155 /* if something closed, retry this slot since may have been 156 * swapped with end fd */ 157 if (m && cpcx->pollfds[n].fd != fd) 158 n--; 159 160 if (m < 0) 161 /* lws feels something bad happened, but 162 * the outer application may not care */ 163 continue; 164 if (!m) { 165 /* check if it is an fd owned by the 166 * application */ 167 } 168 } 169 } 170 171 return 0; 172} 173 174 175/* 176 * These is the custom "event library" interface layer between lws event lib 177 * support and the custom loop implementation above. We only need to support 178 * a few key apis. 179 * 180 * We are user code, so all the internal lws objects are opaque. But there are 181 * enough public helpers to get everything done. 182 */ 183 184/* one of these is appended to each pt for our use */ 185struct pt_eventlibs_custom { 186 custom_poll_ctx_t *io_loop; 187}; 188 189/* 190 * During lws context creation, we get called with the foreign loop pointer 191 * that was passed in the creation info struct. Stash it in our private part 192 * of the pt, so we can reference it in the other callbacks subsequently. 193 */ 194 195static int 196init_pt_custom(struct lws_context *cx, void *_loop, int tsi) 197{ 198 struct pt_eventlibs_custom *priv = (struct pt_eventlibs_custom *) 199 lws_evlib_tsi_to_evlib_pt(cx, tsi); 200 201 /* store the loop we are bound to in our private part of the pt */ 202 203 priv->io_loop = (custom_poll_ctx_t *)_loop; 204 205 return 0; 206} 207 208static int 209sock_accept_custom(struct lws *wsi) 210{ 211 struct pt_eventlibs_custom *priv = (struct pt_eventlibs_custom *) 212 lws_evlib_wsi_to_evlib_pt(wsi); 213 214 return custom_poll_add_fd(priv->io_loop, lws_get_socket_fd(wsi), POLLIN); 215} 216 217static void 218io_custom(struct lws *wsi, unsigned int flags) 219{ 220 struct pt_eventlibs_custom *priv = (struct pt_eventlibs_custom *) 221 lws_evlib_wsi_to_evlib_pt(wsi); 222 int e_add = 0, e_remove = 0; 223 224 if (flags & LWS_EV_START) { 225 if (flags & LWS_EV_WRITE) 226 e_add |= POLLOUT; 227 228 if (flags & LWS_EV_READ) 229 e_add |= POLLIN; 230 } else { 231 if (flags & LWS_EV_WRITE) 232 e_remove |= POLLOUT; 233 234 if (flags & LWS_EV_READ) 235 e_remove |= POLLIN; 236 } 237 238 custom_poll_change_fd(priv->io_loop, lws_get_socket_fd(wsi), 239 e_add, e_remove); 240} 241 242static int 243wsi_logical_close_custom(struct lws *wsi) 244{ 245 struct pt_eventlibs_custom *priv = (struct pt_eventlibs_custom *) 246 lws_evlib_wsi_to_evlib_pt(wsi); 247 return custom_poll_del_fd(priv->io_loop, lws_get_socket_fd(wsi)); 248} 249 250static const struct lws_event_loop_ops event_loop_ops_custom = { 251 .name = "custom", 252 253 .init_pt = init_pt_custom, 254 .init_vhost_listen_wsi = sock_accept_custom, 255 .sock_accept = sock_accept_custom, 256 .io = io_custom, 257 .wsi_logical_close = wsi_logical_close_custom, 258 259 .evlib_size_pt = sizeof(struct pt_eventlibs_custom) 260}; 261 262static const lws_plugin_evlib_t evlib_custom = { 263 .hdr = { 264 "custom event loop", 265 "lws_evlib_plugin", 266 LWS_BUILD_HASH, 267 LWS_PLUGIN_API_MAGIC 268 }, 269 270 .ops = &event_loop_ops_custom 271}; 272 273/* 274 * The rest is just the normal minimal example for lws, with a couple of extra 275 * lines wiring up the custom event library handlers above. 276 */ 277 278static const struct lws_http_mount mount = { 279 /* .mount_next */ NULL, /* linked-list "next" */ 280 /* .mountpoint */ "/", /* mountpoint URL */ 281 /* .origin */ "./mount-origin", /* serve from dir */ 282 /* .def */ "index.html", /* default filename */ 283 /* .protocol */ NULL, 284 /* .cgienv */ NULL, 285 /* .extra_mimetypes */ NULL, 286 /* .interpret */ NULL, 287 /* .cgi_timeout */ 0, 288 /* .cache_max_age */ 0, 289 /* .auth_mask */ 0, 290 /* .cache_reusable */ 0, 291 /* .cache_revalidate */ 0, 292 /* .cache_intermediaries */ 0, 293 /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ 294 /* .mountpoint_len */ 1, /* char count */ 295 /* .basic_auth_login_file */ NULL, 296}; 297 298/* 299 * This demonstrates a client connection operating on the same loop 300 * It's optional... 301 */ 302 303static int 304callback_http(struct lws *wsi, enum lws_callback_reasons reason, 305 void *user, void *in, size_t len) 306{ 307 switch (reason) { 308 309 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 310 lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: resp %u\n", 311 lws_http_client_http_response(wsi)); 312 break; 313 314 /* because we are protocols[0] ... */ 315 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 316 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", 317 in ? (char *)in : "(null)"); 318 break; 319 320 /* chunks of chunked content, with header removed */ 321 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: 322 lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); 323 lwsl_hexdump_info(in, len); 324 return 0; /* don't passthru */ 325 326 /* uninterpreted http content */ 327 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: 328 { 329 char buffer[1024 + LWS_PRE]; 330 char *px = buffer + LWS_PRE; 331 int lenx = sizeof(buffer) - LWS_PRE; 332 333 if (lws_http_client_read(wsi, &px, &lenx) < 0) 334 return -1; 335 } 336 return 0; /* don't passthru */ 337 338 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: 339 lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %s\n", 340 lws_wsi_tag(wsi)); 341 break; 342 343 case LWS_CALLBACK_CLOSED_CLIENT_HTTP: 344 lwsl_info("%s: closed: %s\n", __func__, lws_wsi_tag(wsi)); 345 break; 346 347 default: 348 break; 349 } 350 351 return lws_callback_http_dummy(wsi, reason, user, in, len); 352} 353 354static const struct lws_protocols protocols[] = { 355 { "httptest", callback_http, 0, 0, 0, NULL, 0}, 356 LWS_PROTOCOL_LIST_TERM 357}; 358 359static int 360do_client_conn(void) 361{ 362 struct lws_client_connect_info i; 363 364 memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ 365 366 i.context = context; 367 368 i.ssl_connection = LCCSCF_USE_SSL; 369 i.port = 443; 370 i.address = "warmcat.com"; 371 372 i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | 373 LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; 374 i.path = "/"; 375 i.host = i.address; 376 i.origin = i.address; 377 i.method = "GET"; 378 i.protocol = protocols[0].name; 379#if defined(LWS_WITH_SYS_FAULT_INJECTION) 380 i.fi_wsi_name = "user"; 381#endif 382 383 if (!lws_client_connect_via_info(&i)) { 384 lwsl_err("Client creation failed\n"); 385 386 return 1; 387 } 388 389 lwsl_notice("Client creation OK\n"); 390 391 return 0; 392} 393 394/* 395 * End of client part 396 * 397 * Initialization part --> 398 */ 399 400void sigint_handler(int sig) 401{ 402 interrupted = 1; 403} 404 405int main(int argc, const char **argv) 406{ 407 struct lws_context_creation_info info; 408 const char *p; 409 int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; 410 void *foreign_loops[1]; 411 412 signal(SIGINT, sigint_handler); 413 414 if ((p = lws_cmdline_option(argc, argv, "-d"))) 415 logs = atoi(p); 416 417 /* 418 * init the existing custom event loop here if anything to do, don't 419 * run it yet. In our example, no init required. 420 */ 421 422 lws_set_log_level(logs, NULL); 423 lwsl_user("LWS minimal http server | visit http://localhost:7681\n"); 424 425 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ 426 info.port = 7681; 427 info.mounts = &mount; 428 info.error_document_404 = "/404.html"; 429 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | 430 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; 431 432 info.event_lib_custom = &evlib_custom; /* bind lws to our custom event 433 * lib implementation above */ 434 foreign_loops[0] = &a_cpcx; /* pass in the custom poll object as the 435 * foreign loop object we will bind to */ 436 info.foreign_loops = foreign_loops; 437 438 /* optional to demonstrate client connection */ 439 info.protocols = protocols; 440 441 context = lws_create_context(&info); 442 if (!context) { 443 lwsl_err("lws init failed\n"); 444 return 1; 445 } 446 447 /* optional to demonstrate client connection */ 448 do_client_conn(); 449 450 /* 451 * We're going to run the custom loop now, instead of the lws loop. 452 * We have told lws to cooperate with this loop to get stuff done. 453 * 454 * We only come back from this when interrupted gets set by SIGINT 455 */ 456 457 custom_poll_run(&a_cpcx); 458 459 /* clean up lws part */ 460 461 lws_context_destroy(context); 462 463 return 0; 464} 465