• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * lws-minimal-http-client-multi
3  *
4  * Written in 2010-2020 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 the a minimal http client using lws, which makes
10  * 8 downloads simultaneously from warmcat.com.
11  *
12  * Currently that takes the form of 8 individual simultaneous tcp and
13  * tls connections, which happen concurrently.  Notice that the ordering
14  * of the returned payload may be intermingled for the various connections.
15  *
16  * By default the connections happen all together at the beginning and operate
17  * concurrently, which is fast.  However this is resource-intenstive, there are
18  * 8 tcp connections, 8 tls tunnels on both the client and server.  You can
19  * instead opt to have the connections happen one after the other inside a
20  * single tcp connection and tls tunnel, using HTTP/1.1 pipelining.  To be
21  * eligible to be pipelined on another existing connection to the same server,
22  * the client connection must have the LCCSCF_PIPELINE flag on its
23  * info.ssl_connection member (this is independent of whether the connection
24  * is in ssl mode or not).
25  *
26  * HTTP/1.0: Pipelining only possible if Keep-Alive: yes sent by server
27  * HTTP/1.1: always possible... serializes requests
28  * HTTP/2:   always possible... all requests sent as individual streams in parallel
29  */
30 
31 #include <libwebsockets.h>
32 #include <string.h>
33 #include <signal.h>
34 #include <assert.h>
35 #include <time.h>
36 
37 #define COUNT 8
38 
39 struct cliuser {
40 	int index;
41 };
42 
43 static int completed, failed, numbered, stagger_idx, posting, count = COUNT;
44 static lws_sorted_usec_list_t sul_stagger;
45 static struct lws_client_connect_info i;
46 static struct lws *client_wsi[COUNT];
47 static char urlpath[64];
48 static struct lws_context *context;
49 
50 /* we only need this for tracking POST emit state */
51 
52 struct pss {
53 	char body_part;
54 };
55 
56 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)57 callback_http(struct lws *wsi, enum lws_callback_reasons reason,
58 	      void *user, void *in, size_t len)
59 {
60 	char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
61 	     *end = &buf[sizeof(buf) - LWS_PRE - 1];
62 	int n, idx = (int)(intptr_t)lws_get_opaque_user_data(wsi);
63 	struct pss *pss = (struct pss *)user;
64 
65 	switch (reason) {
66 
67 	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
68 		lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: idx: %d, resp %u\n",
69 				idx, lws_http_client_http_response(wsi));
70 		break;
71 
72 	/* because we are protocols[0] ... */
73 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
74 		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
75 			 in ? (char *)in : "(null)");
76 		client_wsi[idx] = NULL;
77 		failed++;
78 		goto finished;
79 
80 	/* chunks of chunked content, with header removed */
81 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
82 		lwsl_user("RECEIVE_CLIENT_HTTP_READ: conn %d: read %d\n", idx, (int)len);
83 		lwsl_hexdump_info(in, len);
84 		return 0; /* don't passthru */
85 
86 	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
87 		/*
88 		 * Tell lws we are going to send the body next...
89 		 */
90 		if (posting && !lws_http_is_redirected_to_get(wsi)) {
91 			lwsl_user("%s: doing POST flow\n", __func__);
92 			lws_client_http_body_pending(wsi, 1);
93 			lws_callback_on_writable(wsi);
94 		} else
95 			lwsl_user("%s: doing GET flow\n", __func__);
96 		break;
97 
98 	/* uninterpreted http content */
99 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
100 		{
101 			char buffer[1024 + LWS_PRE];
102 			char *px = buffer + LWS_PRE;
103 			int lenx = sizeof(buffer) - LWS_PRE;
104 
105 			if (lws_http_client_read(wsi, &px, &lenx) < 0)
106 				return -1;
107 		}
108 		return 0; /* don't passthru */
109 
110 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
111 		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP %p: idx %d\n",
112 			  wsi, idx);
113 		client_wsi[idx] = NULL;
114 		goto finished;
115 
116 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
117 		lwsl_info("%s: closed: %p\n", __func__, client_wsi[idx]);
118 		if (client_wsi[idx]) {
119 			/*
120 			 * If it completed normally, it will have been set to
121 			 * NULL then already.  So we are dealing with an
122 			 * abnormal, failing, close
123 			 */
124 			client_wsi[idx] = NULL;
125 			failed++;
126 			goto finished;
127 		}
128 		break;
129 
130 	case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
131 		if (!posting)
132 			break;
133 		if (lws_http_is_redirected_to_get(wsi))
134 			break;
135 		lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: %p, part %d\n", wsi, pss->body_part);
136 		n = LWS_WRITE_HTTP;
137 
138 		/*
139 		 * For a small body like this, we could prepare it in memory and
140 		 * send it all at once.  But to show how to handle, eg,
141 		 * arbitrary-sized file payloads, or huge form-data fields, the
142 		 * sending is done in multiple passes through the event loop.
143 		 */
144 
145 		switch (pss->body_part++) {
146 		case 0:
147 			if (lws_client_http_multipart(wsi, "text", NULL, NULL,
148 						      &p, end))
149 				return -1;
150 			/* notice every usage of the boundary starts with -- */
151 			p += lws_snprintf(p, end - p, "my text field\xd\xa");
152 			break;
153 		case 1:
154 			if (lws_client_http_multipart(wsi, "file", "myfile.txt",
155 						      "text/plain", &p, end))
156 				return -1;
157 			p += lws_snprintf(p, end - p,
158 					"This is the contents of the "
159 					"uploaded file.\xd\xa"
160 					"\xd\xa");
161 			break;
162 		case 2:
163 			if (lws_client_http_multipart(wsi, NULL, NULL, NULL,
164 						      &p, end))
165 				return -1;
166 			lws_client_http_body_pending(wsi, 0);
167 			 /* necessary to support H2, it means we will write no
168 			  * more on this stream */
169 			n = LWS_WRITE_HTTP_FINAL;
170 			break;
171 
172 		default:
173 			/*
174 			 * We can get extra callbacks here, if nothing to do,
175 			 * then do nothing.
176 			 */
177 			return 0;
178 		}
179 
180 		if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n)
181 				!= lws_ptr_diff(p, start))
182 			return 1;
183 
184 		if (n != LWS_WRITE_HTTP_FINAL)
185 			lws_callback_on_writable(wsi);
186 
187 		break;
188 
189 	default:
190 		break;
191 	}
192 
193 	return lws_callback_http_dummy(wsi, reason, user, in, len);
194 
195 finished:
196 	if (++completed == count) {
197 		if (!failed)
198 			lwsl_user("Done: all OK\n");
199 		else
200 			lwsl_err("Done: failed: %d\n", failed);
201 		//interrupted = 1;
202 		/*
203 		 * This is how we can exit the event loop even when it's an
204 		 * event library backing it... it will start and stage the
205 		 * destroy to happen after we exited this service for each pt
206 		 */
207 		lws_context_destroy(lws_get_context(wsi));
208 	}
209 
210 	return 0;
211 }
212 
213 static const struct lws_protocols protocols[] = {
214 	{ "http", callback_http, sizeof(struct pss), 0, },
215 	{ NULL, NULL, 0, 0 }
216 };
217 
218 static void
signal_cb(void * handle,int signum)219 signal_cb(void *handle, int signum)
220 {
221 	switch (signum) {
222 	case SIGTERM:
223 	case SIGINT:
224 		break;
225 	default:
226 		lwsl_err("%s: signal %d\n", __func__, signum);
227 		break;
228 	}
229 	lws_context_destroy(context);
230 }
231 
232 static void
sigint_handler(int sig)233 sigint_handler(int sig)
234 {
235 	signal_cb(NULL, sig);
236 }
237 
238 #if defined(WIN32)
gettimeofday(struct timeval * tp,struct timezone * tzp)239 int gettimeofday(struct timeval * tp, struct timezone * tzp)
240 {
241     // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's
242     // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC)
243     // until 00:00:00 January 1, 1970
244     static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL);
245 
246     SYSTEMTIME  system_time;
247     FILETIME    file_time;
248     uint64_t    time;
249 
250     GetSystemTime( &system_time );
251     SystemTimeToFileTime( &system_time, &file_time );
252     time =  ((uint64_t)file_time.dwLowDateTime )      ;
253     time += ((uint64_t)file_time.dwHighDateTime) << 32;
254 
255     tp->tv_sec  = (long) ((time - EPOCH) / 10000000L);
256     tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
257     return 0;
258 }
259 #endif
260 
us(void)261 unsigned long long us(void)
262 {
263 	struct timeval t;
264 
265 	gettimeofday(&t, NULL);
266 
267 	return (t.tv_sec * 1000000ull) + t.tv_usec;
268 }
269 
270 static void
lws_try_client_connection(struct lws_client_connect_info * i,int m)271 lws_try_client_connection(struct lws_client_connect_info *i, int m)
272 {
273 	char path[128];
274 
275 	if (numbered) {
276 		lws_snprintf(path, sizeof(path), "/%d.png", m + 1);
277 		i->path = path;
278 	} else
279 		i->path = urlpath;
280 
281 	i->pwsi = &client_wsi[m];
282 	i->opaque_user_data = (void *)(intptr_t)m;
283 
284 	if (!lws_client_connect_via_info(i)) {
285 		failed++;
286 		if (++completed == count) {
287 			lwsl_user("Done: failed: %d\n", failed);
288 			lws_context_destroy(context);
289 		}
290 	} else
291 		lwsl_user("started connection %p: idx %d (%s)\n",
292 			  client_wsi[m], m, i->path);
293 }
294 
295 static void
stagger_cb(lws_sorted_usec_list_t * sul)296 stagger_cb(lws_sorted_usec_list_t *sul)
297 {
298 	lws_usec_t next;
299 
300 	/*
301 	 * open the connections at 100ms intervals, with the
302 	 * last one being after 1s, testing both queuing, and
303 	 * direct H2 stream addition stability
304 	 */
305 	lws_try_client_connection(&i, stagger_idx++);
306 
307 	if (stagger_idx == count)
308 		return;
309 
310 	next = 300 * LWS_US_PER_MS;
311 	if (stagger_idx == count - 1)
312 		next += 700 * LWS_US_PER_MS;
313 
314 	lws_sul_schedule(context, 0, &sul_stagger, stagger_cb, next);
315 }
316 
main(int argc,const char ** argv)317 int main(int argc, const char **argv)
318 {
319 	struct lws_context_creation_info info;
320 	unsigned long long start;
321 	const char *p;
322 	int m, staggered = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
323 		/* for LLL_ verbosity above NOTICE to be built into lws,
324 		 * lws must have been configured and built with
325 		 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
326 		/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
327 		/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
328 		/* | LLL_DEBUG */;
329 
330 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
331 	memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
332 
333 	info.signal_cb = signal_cb;
334 	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
335 
336 	if (lws_cmdline_option(argc, argv, "--uv"))
337 		info.options |= LWS_SERVER_OPTION_LIBUV;
338 	else
339 		if (lws_cmdline_option(argc, argv, "--event"))
340 			info.options |= LWS_SERVER_OPTION_LIBEVENT;
341 		else
342 			if (lws_cmdline_option(argc, argv, "--ev"))
343 				info.options |= LWS_SERVER_OPTION_LIBEV;
344 			else
345 				if (lws_cmdline_option(argc, argv, "--glib"))
346 					info.options |= LWS_SERVER_OPTION_GLIB;
347 				else
348 					signal(SIGINT, sigint_handler);
349 
350 	staggered = !!lws_cmdline_option(argc, argv, "-s");
351 	if ((p = lws_cmdline_option(argc, argv, "-d")))
352 		logs = atoi(p);
353 
354 	lws_set_log_level(logs, NULL);
355 	lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n");
356 	lwsl_user("   [--h1 (http/1 only)] [-l (localhost)] [-d <logs>]\n");
357 	lwsl_user("   [-n (numbered)] [--post]\n");
358 
359 	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
360 	info.protocols = protocols;
361 	/*
362 	 * since we know this lws context is only ever going to be used with
363 	 * COUNT client wsis / fds / sockets at a time, let lws know it doesn't
364 	 * have to use the default allocations for fd tables up to ulimit -n.
365 	 * It will just allocate for 1 internal and COUNT + 1 (allowing for h2
366 	 * network wsi) that we will use.
367 	 */
368 	info.fd_limit_per_thread = 1 + COUNT + 1;
369 
370 #if defined(LWS_WITH_MBEDTLS)
371 	/*
372 	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
373 	 * CA to trust explicitly.
374 	 */
375 	info.client_ssl_ca_filepath = "./warmcat.com.cer";
376 #endif
377 
378 	if ((p = lws_cmdline_option(argc, argv, "--limit")))
379 		info.simultaneous_ssl_restriction = atoi(p);
380 
381 #if defined(LWS_WITH_DETAILED_LATENCY)
382 	info.detailed_latency_cb = lws_det_lat_plot_cb;
383 	info.detailed_latency_filepath = "/tmp/lws-latency-results";
384 #endif
385 
386 	context = lws_create_context(&info);
387 	if (!context) {
388 		lwsl_err("lws init failed\n");
389 		return 1;
390 	}
391 
392 	i.context = context;
393 	i.ssl_connection = LCCSCF_USE_SSL |
394 			   LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
395 			   LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
396 
397 	if (lws_cmdline_option(argc, argv, "--post")) {
398 		posting = 1;
399 		i.method = "POST";
400 		i.ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME;
401 	} else
402 		i.method = "GET";
403 
404 	/* enables h1 or h2 connection sharing */
405 	if (lws_cmdline_option(argc, argv, "-p"))
406 		i.ssl_connection |= LCCSCF_PIPELINE;
407 
408 	/* force h1 even if h2 available */
409 	if (lws_cmdline_option(argc, argv, "--h1"))
410 		i.alpn = "http/1.1";
411 
412 	strcpy(urlpath, "/");
413 
414 	if (lws_cmdline_option(argc, argv, "-l")) {
415 		i.port = 7681;
416 		i.address = "localhost";
417 		i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
418 		if (posting)
419 			strcpy(urlpath, "/formtest");
420 	} else {
421 		i.port = 443;
422 		i.address = "libwebsockets.org";
423 		if (posting)
424 			strcpy(urlpath, "/testserver/formtest");
425 	}
426 
427 	if (lws_cmdline_option(argc, argv, "-n"))
428 		numbered = 1;
429 
430 	if ((p = lws_cmdline_option(argc, argv, "--server")))
431 		i.address = p;
432 
433 	if ((p = lws_cmdline_option(argc, argv, "--port")))
434 		i.port = atoi(p);
435 
436 	if ((p = lws_cmdline_option(argc, argv, "--path")))
437 		lws_strncpy(urlpath, p, sizeof(urlpath));
438 
439 	if ((p = lws_cmdline_option(argc, argv, "-c")))
440 		if (atoi(p) <= COUNT && atoi(p))
441 			count = atoi(p);
442 
443 	i.host = i.address;
444 	i.origin = i.address;
445 	i.protocol = protocols[0].name;
446 
447 	if (!staggered)
448 		/*
449 		 * just pile on all the connections at once, testing the
450 		 * pipeline queuing before the first is connected
451 		 */
452 		for (m = 0; m < count; m++)
453 			lws_try_client_connection(&i, m);
454 	else
455 		/*
456 		 * delay the connections slightly
457 		 */
458 		lws_sul_schedule(context, 0, &sul_stagger, stagger_cb,
459 				 100 * LWS_US_PER_MS);
460 
461 	start = us();
462 	while (!lws_service(context, 0))
463 		;
464 
465 	lwsl_user("Duration: %lldms\n", (us() - start) / 1000);
466 	lws_context_destroy(context);
467 
468 	lwsl_user("Exiting with %d\n", failed || completed != count);
469 
470 	return failed || completed != count;
471 }
472