1/* 2 * lws-minimal-http-client-attach 3 * 4 * Written in 2010-2019 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 how to use the lws_system (*attach) api to allow a 10 * different thread to arrange to join an existing lws event loop safely. The 11 * attached stuff does an http client GET from the lws event loop, even though 12 * it was originally requested from a different thread than the lws event loop. 13 */ 14 15#include <libwebsockets.h> 16#include <string.h> 17#include <signal.h> 18#if defined(WIN32) 19#define HAVE_STRUCT_TIMESPEC 20#if defined(pid_t) 21#undef pid_t 22#endif 23#endif 24#include <pthread.h> 25 26static struct lws_context *context; 27static pthread_t lws_thread; 28static pthread_mutex_t lock; 29static int interrupted, bad = 1, status; 30 31static int 32callback_http(struct lws *wsi, enum lws_callback_reasons reason, 33 void *user, void *in, size_t len) 34{ 35 switch (reason) { 36 37 /* because we are protocols[0] ... */ 38 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 39 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", 40 in ? (char *)in : "(null)"); 41 interrupted = 1; 42 break; 43 44 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: 45 { 46 char buf[128]; 47 48 lws_get_peer_simple(wsi, buf, sizeof(buf)); 49 status = (int)lws_http_client_http_response(wsi); 50 51 lwsl_user("Connected to %s, http response: %d\n", 52 buf, status); 53 } 54 break; 55 56 /* chunks of chunked content, with header removed */ 57 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: 58 lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); 59 60#if 0 /* enable to dump the html */ 61 { 62 const char *p = in; 63 64 while (len--) 65 if (*p < 0x7f) 66 putchar(*p++); 67 else 68 putchar('.'); 69 } 70#endif 71 return 0; /* don't passthru */ 72 73 /* uninterpreted http content */ 74 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: 75 { 76 char buffer[1024 + LWS_PRE]; 77 char *px = buffer + LWS_PRE; 78 int lenx = sizeof(buffer) - LWS_PRE; 79 80 if (lws_http_client_read(wsi, &px, &lenx) < 0) 81 return -1; 82 } 83 return 0; /* don't passthru */ 84 85 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: 86 lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); 87 interrupted = 1; 88 bad = status != 200; 89 lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ 90 break; 91 92 case LWS_CALLBACK_CLOSED_CLIENT_HTTP: 93 interrupted = 1; 94 bad = status != 200; 95 lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ 96 break; 97 98 default: 99 break; 100 } 101 102 return lws_callback_http_dummy(wsi, reason, user, in, len); 103} 104 105static const struct lws_protocols protocols[] = { 106 { 107 "http", 108 callback_http, 109 0, 0, 0, NULL, 0 110 }, 111 LWS_PROTOCOL_LIST_TERM 112}; 113 114void sigint_handler(int sig) 115{ 116 interrupted = 1; 117} 118 119static void 120attach_callback(struct lws_context *context, int tsi, void *opaque) 121{ 122 struct lws_client_connect_info i; 123 124 /* 125 * Even though it was asked for from a different thread, we are called 126 * back by lws from the lws event loop thread context 127 * 128 * We can set up our operations on the lws event loop and return so 129 * they can happen asynchronously 130 */ 131 132 memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ 133 i.context = context; 134 i.ssl_connection = LCCSCF_USE_SSL; 135 i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR | 136 LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM; 137 i.port = 443; 138 i.address = "warmcat.com"; 139 i.path = "/"; 140 i.host = i.address; 141 i.origin = i.address; 142 i.method = "GET"; 143 144 i.protocol = protocols[0].name; 145 146 lws_client_connect_via_info(&i); 147} 148 149 150static int 151lws_attach_with_pthreads_locking(struct lws_context *context, int tsi, 152 lws_attach_cb_t cb, lws_system_states_t state, 153 void *opaque, struct lws_attach_item **get) 154{ 155 int n; 156 157 pthread_mutex_lock(&lock); 158 /* 159 * We just provide system-specific locking around the lws non-threadsafe 160 * helper that adds and removes things from the pt list 161 */ 162 n = __lws_system_attach(context, tsi, cb, state, opaque, get); 163 pthread_mutex_unlock(&lock); 164 165 return n; 166} 167 168 169lws_system_ops_t ops = { 170 .attach = lws_attach_with_pthreads_locking 171}; 172 173/* 174 * We made this into a different thread to model it being run from completely 175 * different codebase that's all linked together 176 */ 177 178static void * 179lws_create(void *d) 180{ 181 struct lws_context_creation_info info; 182 183 lwsl_user("%s: tid %p\n", __func__, (void *)(intptr_t)pthread_self()); 184 185 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ 186 info.port = CONTEXT_PORT_NO_LISTEN; 187 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; 188 info.system_ops = &ops; 189 info.protocols = protocols; 190 191 context = lws_create_context(&info); 192 if (!context) { 193 lwsl_err("lws init failed\n"); 194 goto bail; 195 } 196 197 /* start the event loop */ 198 199 while (!interrupted) 200 if (lws_service(context, 0)) 201 interrupted = 1; 202 203 lws_context_destroy(context); 204 205bail: 206 pthread_exit(NULL); 207 208 return NULL; 209} 210 211int main(int argc, const char **argv) 212{ 213 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; 214 const char *p; 215 void *retval; 216 217 signal(SIGINT, sigint_handler); 218 219 if ((p = lws_cmdline_option(argc, argv, "-d"))) 220 logs = atoi(p); 221 222 lws_set_log_level(logs, NULL); 223 lwsl_user("LWS minimal http client attach\n"); 224 225 pthread_mutex_init(&lock, NULL); 226 227 /* 228 * The idea of the example is we're going to split the lws context and 229 * event loop off to be created from its own thread... this is like it 230 * was actually started by some completely different code... 231 */ 232 233 if (pthread_create(&lws_thread, NULL, lws_create, NULL)) { 234 lwsl_err("thread creation failed\n"); 235 goto bail1; 236 } 237 238 /* 239 * Now on the original / different thread representing a different 240 * codebase that wants to join this existing event loop, we'll ask to 241 * get a callback from the event loop context when the event loop 242 * thread is operational. We have to wait around a bit because we 243 * may run before the lws context was created. 244 */ 245 246 while (!context && n++ < 30) 247 usleep(10000); 248 249 if (!context) { 250 lwsl_err("%s: context didn't start\n", __func__); 251 goto bail; 252 } 253 254 /* 255 * From our different, non event loop thread, ask for our attach 256 * callback to get called when lws system state is OPERATIONAL 257 */ 258 259 lws_system_get_ops(context)->attach(context, 0, attach_callback, 260 LWS_SYSTATE_OPERATIONAL, 261 NULL, NULL); 262 263 /* 264 * That's all we wanted to do with our thread. Just wait for the lws 265 * thread to exit as well. 266 */ 267 268bail: 269 pthread_join(lws_thread, &retval); 270bail1: 271 pthread_mutex_destroy(&lock); 272 273 lwsl_user("%s: finished\n", __func__); 274 275 return 0; 276} 277