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