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