1 /*
2 * lws-minimal-http-client-attach
3 *
4 * Written in 2010-2019 by Andy Green <[email protected]>
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
26 static struct lws_context *context;
27 static pthread_t lws_thread;
28 static pthread_mutex_t lock;
29 static int interrupted, bad = 1, status;
30
31 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)32 callback_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
105 static 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
sigint_handler(int sig)114 void sigint_handler(int sig)
115 {
116 interrupted = 1;
117 }
118
119 static void
attach_callback(struct lws_context * context,int tsi,void * opaque)120 attach_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
150 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)151 lws_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
169 lws_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
178 static void *
lws_create(void * d)179 lws_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
205 bail:
206 pthread_exit(NULL);
207
208 return NULL;
209 }
210
main(int argc,const char ** argv)211 int 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
268 bail:
269 pthread_join(lws_thread, &retval);
270 bail1:
271 pthread_mutex_destroy(&lock);
272
273 lwsl_user("%s: finished\n", __func__);
274
275 return 0;
276 }
277