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