/* * lws-minimal-http-client-attach * * Written in 2010-2019 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. * * This demonstrates how to use the lws_system (*attach) api to allow a * different thread to arrange to join an existing lws event loop safely. The * attached stuff does an http client GET from the lws event loop, even though * it was originally requested from a different thread than the lws event loop. */ #include #include #include #include static struct lws_context *context; static pthread_t lws_thread; static pthread_mutex_t lock; static int interrupted, bad = 1, status; static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { switch (reason) { /* because we are protocols[0] ... */ case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); interrupted = 1; break; case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char buf[128]; lws_get_peer_simple(wsi, buf, sizeof(buf)); status = lws_http_client_http_response(wsi); lwsl_user("Connected to %s, http response: %d\n", buf, status); } break; /* chunks of chunked content, with header removed */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); #if 0 /* enable to dump the html */ { const char *p = in; while (len--) if (*p < 0x7f) putchar(*p++); else putchar('.'); } #endif return 0; /* don't passthru */ /* uninterpreted http content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { char buffer[1024 + LWS_PRE]; char *px = buffer + LWS_PRE; int lenx = sizeof(buffer) - LWS_PRE; if (lws_http_client_read(wsi, &px, &lenx) < 0) return -1; } return 0; /* don't passthru */ case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); interrupted = 1; bad = status != 200; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: interrupted = 1; bad = status != 200; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ break; default: break; } return lws_callback_http_dummy(wsi, reason, user, in, len); } static const struct lws_protocols protocols[] = { { "http", callback_http, 0, 0, }, { NULL, NULL, 0, 0 } }; void sigint_handler(int sig) { interrupted = 1; } static void attach_callback(struct lws_context *context, int tsi, void *opaque) { struct lws_client_connect_info i; /* * Even though it was asked for from a different thread, we are called * back by lws from the lws event loop thread context */ lwsl_user("%s: called from tid %p\n", __func__, (void *)pthread_self()); /* * We can set up our operations on the lws event loop and return so * they can happen asynchronously */ memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ i.context = context; i.ssl_connection = LCCSCF_USE_SSL; i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; i.port = 443; i.address = "warmcat.com"; i.path = "/"; i.host = i.address; i.origin = i.address; i.method = "GET"; i.protocol = protocols[0].name; lws_client_connect_via_info(&i); } static int lws_attach_with_pthreads_locking(struct lws_context *context, int tsi, lws_attach_cb_t cb, lws_system_states_t state, void *opaque, struct lws_attach_item **get) { int n; pthread_mutex_lock(&lock); /* * We just provide system-specific locking around the lws non-threadsafe * helper that adds and removes things from the pt list */ n = __lws_system_attach(context, tsi, cb, state, opaque, get); pthread_mutex_unlock(&lock); return n; } lws_system_ops_t ops = { .attach = lws_attach_with_pthreads_locking }; /* * We made this into a different thread to model it being run from completely * different codebase that's all linked together */ static void * lws_create(void *d) { struct lws_context_creation_info info; lwsl_user("%s: tid %p\n", __func__, (void *)pthread_self()); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.port = CONTEXT_PORT_NO_LISTEN; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.system_ops = &ops; info.protocols = protocols; context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); goto bail; } /* start the event loop */ while (!interrupted) if (lws_service(context, 0)) interrupted = 1; lws_context_destroy(context); bail: pthread_exit(NULL); return NULL; } int main(int argc, const char **argv) { int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; const char *p; void *retval; signal(SIGINT, sigint_handler); if ((p = lws_cmdline_option(argc, argv, "-d"))) logs = atoi(p); lws_set_log_level(logs, NULL); lwsl_user("%s: main thread tid %p\n", __func__, (void *)pthread_self()); lwsl_user("LWS minimal http client attach\n"); pthread_mutex_init(&lock, NULL); /* * The idea of the example is we're going to split the lws context and * event loop off to be created from its own thread... this is like it * was actually started by some completely different code... */ if (pthread_create(&lws_thread, NULL, lws_create, NULL)) { lwsl_err("thread creation failed\n"); goto bail1; } /* * Now on the original / different thread representing a different * codebase that wants to join this existing event loop, we'll ask to * get a callback from the event loop context when the event loop * thread is operational. We have to wait around a bit because we * may run before the lws context was created. */ while (!context && n++ < 30) usleep(10000); if (!context) { lwsl_err("%s: context didn't start\n", __func__); goto bail; } /* * From our different, non event loop thread, ask for our attach * callback to get called when lws system state is OPERATIONAL */ lws_system_get_ops(context)->attach(context, 0, attach_callback, LWS_SYSTATE_OPERATIONAL, NULL, NULL); /* * That's all we wanted to do with our thread. Just wait for the lws * thread to exit as well. */ bail: pthread_join(lws_thread, &retval); bail1: pthread_mutex_destroy(&lock); lwsl_user("%s: finished\n", __func__); return 0; }