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