• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * lws-minimal-http-client
3  *
4  * Written in 2010-2021 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.
10  *
11  * It visits https://warmcat.com/ and receives the html page there.  You
12  * can dump the page data by changing the #if 0 below.
13  */
14 
15 #include <libwebsockets.h>
16 #include <string.h>
17 #include <signal.h>
18 
19 static int interrupted, bad = 1, status, conmon;
20 #if defined(LWS_WITH_HTTP2)
21 static int long_poll;
22 #endif
23 static struct lws *client_wsi;
24 static const char *ba_user, *ba_password;
25 
26 static const lws_retry_bo_t retry = {
27 	.secs_since_valid_ping = 3,
28 	.secs_since_valid_hangup = 10,
29 };
30 
31 #if defined(LWS_WITH_CONMON)
32 void
dump_conmon_data(struct lws * wsi)33 dump_conmon_data(struct lws *wsi)
34 {
35 	const struct addrinfo *ai;
36 	struct lws_conmon cm;
37 	char ads[48];
38 
39 	lws_conmon_wsi_take(wsi, &cm);
40 
41 	lws_sa46_write_numeric_address(&cm.peer46, ads, sizeof(ads));
42 	lwsl_notice("%s: peer %s, dns: %uus, sockconn: %uus, tls: %uus, txn_resp: %uus\n",
43 		    __func__, ads,
44 		    (unsigned int)cm.ciu_dns,
45 		    (unsigned int)cm.ciu_sockconn,
46 		    (unsigned int)cm.ciu_tls,
47 		    (unsigned int)cm.ciu_txn_resp);
48 
49 	ai = cm.dns_results_copy;
50 	while (ai) {
51 		lws_sa46_write_numeric_address((lws_sockaddr46 *)ai->ai_addr, ads, sizeof(ads));
52 		lwsl_notice("%s: DNS %s\n", __func__, ads);
53 		ai = ai->ai_next;
54 	}
55 
56 	/*
57 	 * This destroys the DNS list in the lws_conmon that we took
58 	 * responsibility for when we used lws_conmon_wsi_take()
59 	 */
60 
61 	lws_conmon_release(&cm);
62 }
63 #endif
64 
65 static const char *ua = "Mozilla/5.0 (X11; Linux x86_64) "
66 			"AppleWebKit/537.36 (KHTML, like Gecko) "
67 			"Chrome/51.0.2704.103 Safari/537.36",
68 		  *acc = "*/*";
69 
70 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)71 callback_http(struct lws *wsi, enum lws_callback_reasons reason,
72 	      void *user, void *in, size_t len)
73 {
74 	switch (reason) {
75 
76 	/* because we are protocols[0] ... */
77 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
78 		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
79 			 in ? (char *)in : "(null)");
80 		interrupted = 1;
81 		bad = 3; /* connection failed before we could make connection */
82 		lws_cancel_service(lws_get_context(wsi));
83 
84 #if defined(LWS_WITH_CONMON)
85 	if (conmon)
86 		dump_conmon_data(wsi);
87 #endif
88 		break;
89 
90 	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
91 		{
92 			char buf[128];
93 
94 			lws_get_peer_simple(wsi, buf, sizeof(buf));
95 			status = (int)lws_http_client_http_response(wsi);
96 
97 			lwsl_user("Connected to %s, http response: %d\n",
98 					buf, status);
99 		}
100 #if defined(LWS_WITH_HTTP2)
101 		if (long_poll) {
102 			lwsl_user("%s: Client entering long poll mode\n", __func__);
103 			lws_h2_client_stream_long_poll_rxonly(wsi);
104 		}
105 #endif
106 
107 		if (lws_fi_user_wsi_fi(wsi, "user_reject_at_est"))
108 			return -1;
109 
110 		break;
111 
112 	/* you only need this if you need to do Basic Auth */
113 	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
114 	{
115 		unsigned char **p = (unsigned char **)in, *end = (*p) + len;
116 
117 		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_USER_AGENT,
118 				(unsigned char *)ua, (int)strlen(ua), p, end))
119 			return -1;
120 
121 		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT,
122 				(unsigned char *)acc, (int)strlen(acc), p, end))
123 			return -1;
124 #if defined(LWS_WITH_HTTP_BASIC_AUTH)
125 		{
126 		char b[128];
127 
128 		if (!ba_user || !ba_password)
129 			break;
130 
131 		if (lws_http_basic_auth_gen(ba_user, ba_password, b, sizeof(b)))
132 			break;
133 		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_AUTHORIZATION,
134 				(unsigned char *)b, (int)strlen(b), p, end))
135 			return -1;
136 		}
137 #endif
138 		break;
139 	}
140 
141 	/* chunks of chunked content, with header removed */
142 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
143 		lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
144 #if defined(LWS_WITH_HTTP2)
145 		if (long_poll) {
146 			char dotstar[128];
147 			lws_strnncpy(dotstar, (const char *)in, len,
148 				     sizeof(dotstar));
149 			lwsl_notice("long poll rx: %d '%s'\n", (int)len,
150 					dotstar);
151 		}
152 #endif
153 #if 0
154 		lwsl_hexdump_notice(in, len);
155 #endif
156 
157 		return 0; /* don't passthru */
158 
159 	/* uninterpreted http content */
160 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
161 		{
162 			char buffer[1024 + LWS_PRE];
163 			char *px = buffer + LWS_PRE;
164 			int lenx = sizeof(buffer) - LWS_PRE;
165 
166 			if (lws_fi_user_wsi_fi(wsi, "user_reject_at_rx"))
167 				return -1;
168 
169 			if (lws_http_client_read(wsi, &px, &lenx) < 0)
170 				return -1;
171 		}
172 		return 0; /* don't passthru */
173 
174 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
175 		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
176 		interrupted = 1;
177 		bad = status != 200;
178 		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
179 		break;
180 
181 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
182 		interrupted = 1;
183 		bad = status != 200;
184 		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
185 #if defined(LWS_WITH_CONMON)
186 		if (conmon)
187 			dump_conmon_data(wsi);
188 #endif
189 		break;
190 
191 	default:
192 		break;
193 	}
194 
195 	return lws_callback_http_dummy(wsi, reason, user, in, len);
196 }
197 
198 static const struct lws_protocols protocols[] = {
199 	{
200 		"http",
201 		callback_http,
202 		0, 0, 0, NULL, 0
203 	},
204 	LWS_PROTOCOL_LIST_TERM
205 };
206 
207 static void
sigint_handler(int sig)208 sigint_handler(int sig)
209 {
210 	interrupted = 1;
211 }
212 
213 struct args {
214 	int argc;
215 	const char **argv;
216 };
217 
218 static int
system_notify_cb(lws_state_manager_t * mgr,lws_state_notify_link_t * link,int current,int target)219 system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
220 		   int current, int target)
221 {
222 	struct lws_context *context = mgr->parent;
223 	struct lws_client_connect_info i;
224 	struct args *a = lws_context_user(context);
225 	const char *p;
226 
227 	if (current != LWS_SYSTATE_OPERATIONAL || target != LWS_SYSTATE_OPERATIONAL)
228 		return 0;
229 
230 	lwsl_info("%s: operational\n", __func__);
231 
232 	memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
233 	i.context = context;
234 	if (!lws_cmdline_option(a->argc, a->argv, "-n")) {
235 		i.ssl_connection = LCCSCF_USE_SSL;
236 #if defined(LWS_WITH_HTTP2)
237 		/* requires h2 */
238 		if (lws_cmdline_option(a->argc, a->argv, "--long-poll")) {
239 			lwsl_user("%s: long poll mode\n", __func__);
240 			long_poll = 1;
241 		}
242 #endif
243 	}
244 
245 	if (lws_cmdline_option(a->argc, a->argv, "-l")) {
246 		i.port = 7681;
247 		i.address = "localhost";
248 		i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
249 	} else {
250 		i.port = 443;
251 		i.address = "warmcat.com";
252 	}
253 
254 	if (lws_cmdline_option(a->argc, a->argv, "--nossl"))
255 		i.ssl_connection = 0;
256 
257 	i.ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR |
258 			    LCCSCF_ACCEPT_TLS_DOWNGRADE_REDIRECTS |
259 			    LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
260 
261 	i.alpn = "h2,http/1.1";
262 	if (lws_cmdline_option(a->argc, a->argv, "--h1"))
263 		i.alpn = "http/1.1";
264 
265 	if (lws_cmdline_option(a->argc, a->argv, "--h2-prior-knowledge"))
266 		i.ssl_connection |= LCCSCF_H2_PRIOR_KNOWLEDGE;
267 
268 	if ((p = lws_cmdline_option(a->argc, a->argv, "-p")))
269 		i.port = atoi(p);
270 
271 	if ((p = lws_cmdline_option(a->argc, a->argv, "--user")))
272 		ba_user = p;
273 	if ((p = lws_cmdline_option(a->argc, a->argv, "--password")))
274 		ba_password = p;
275 
276 	if (lws_cmdline_option(a->argc, a->argv, "-j"))
277 		i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
278 
279 	if (lws_cmdline_option(a->argc, a->argv, "-k"))
280 		i.ssl_connection |= LCCSCF_ALLOW_INSECURE;
281 
282 	if (lws_cmdline_option(a->argc, a->argv, "-b"))
283 		i.ssl_connection |= LCCSCF_CACHE_COOKIES;
284 
285 	if (lws_cmdline_option(a->argc, a->argv, "-m"))
286 		i.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
287 
288 	if (lws_cmdline_option(a->argc, a->argv, "-e"))
289 		i.ssl_connection |= LCCSCF_ALLOW_EXPIRED;
290 
291 	if ((p = lws_cmdline_option(a->argc, a->argv, "-f"))) {
292 		i.ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW;
293 		i.manual_initial_tx_credit = atoi(p);
294 		lwsl_notice("%s: manual peer tx credit %d\n", __func__,
295 				i.manual_initial_tx_credit);
296 	}
297 
298 #if defined(LWS_WITH_CONMON)
299 	if (lws_cmdline_option(a->argc, a->argv, "--conmon")) {
300 		i.ssl_connection |= LCCSCF_CONMON;
301 		conmon = 1;
302 	}
303 #endif
304 
305 	/* the default validity check is 5m / 5m10s... -v = 3s / 10s */
306 
307 	if (lws_cmdline_option(a->argc, a->argv, "-v"))
308 		i.retry_and_idle_policy = &retry;
309 
310 	if ((p = lws_cmdline_option(a->argc, a->argv, "--server")))
311 		i.address = p;
312 
313 	if ((p = lws_cmdline_option(a->argc, a->argv, "--path")))
314 		i.path = p;
315 	else
316 		i.path = "/";
317 
318 	i.host = i.address;
319 	i.origin = i.address;
320 	i.method = "GET";
321 
322 	i.protocol = protocols[0].name;
323 	i.pwsi = &client_wsi;
324 	i.fi_wsi_name = "user";
325 
326 	if (!lws_client_connect_via_info(&i)) {
327 		lwsl_err("Client creation failed\n");
328 		interrupted = 1;
329 		bad = 2; /* could not even start client connection */
330 		lws_cancel_service(context);
331 
332 		return 1;
333 	}
334 
335 	return 0;
336 }
337 
main(int argc,const char ** argv)338 int main(int argc, const char **argv)
339 {
340 	lws_state_notify_link_t notifier = { { NULL, NULL, NULL },
341 					     system_notify_cb, "app" };
342 	lws_state_notify_link_t *na[] = { &notifier, NULL };
343 	struct lws_context_creation_info info;
344 	struct lws_context *context;
345 	int n = 0, expected = 0;
346 	struct args args;
347 	const char *p;
348 	// uint8_t memcert[4096];
349 
350 	args.argc = argc;
351 	args.argv = argv;
352 
353 	signal(SIGINT, sigint_handler);
354 
355 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
356 	lws_cmdline_option_handle_builtin(argc, argv, &info);
357 
358 	lwsl_user("LWS minimal http client [-d<verbosity>] [-l] [--h1]\n");
359 
360 	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
361 		       LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW;
362 	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
363 	info.protocols = protocols;
364 	info.user = &args;
365 	info.register_notifier_list = na;
366 	info.connect_timeout_secs = 30;
367 
368 #if defined(LWS_WITH_CACHE_NSCOOKIEJAR)
369 	info.http_nsc_filepath = "./cookies.txt";
370 	if ((p = lws_cmdline_option(argc, argv, "-c")))
371 		info.http_nsc_filepath = p;
372 #endif
373 
374 	/*
375 	 * since we know this lws context is only ever going to be used with
376 	 * one client wsis / fds / sockets at a time, let lws know it doesn't
377 	 * have to use the default allocations for fd tables up to ulimit -n.
378 	 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
379 	 * will use.
380 	 */
381 	info.fd_limit_per_thread = 1 + 1 + 1;
382 
383 #if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
384 	/*
385 	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
386 	 * CA to trust explicitly.
387 	 */
388 	if (lws_cmdline_option(argc, argv, "-w"))
389 		/* option to confirm we are validating against the right cert */
390 		info.client_ssl_ca_filepath = "./wrong.cer";
391 	else
392 		info.client_ssl_ca_filepath = "./warmcat.com.cer";
393 #endif
394 #if 0
395 	n = open("./warmcat.com.cer", O_RDONLY);
396 	if (n >= 0) {
397 		info.client_ssl_ca_mem_len = read(n, memcert, sizeof(memcert));
398 		info.client_ssl_ca_mem = memcert;
399 		close(n);
400 		n = 0;
401 		memcert[info.client_ssl_ca_mem_len++] = '\0';
402 	}
403 #endif
404 	context = lws_create_context(&info);
405 	if (!context) {
406 		lwsl_err("lws init failed\n");
407 		bad = 5;
408 		goto bail;
409 	}
410 
411 	while (n >= 0 && !interrupted)
412 		n = lws_service(context, 0);
413 
414 	lws_context_destroy(context);
415 
416 bail:
417 	if ((p = lws_cmdline_option(argc, argv, "--expected-exit")))
418 		expected = atoi(p);
419 
420 	if (bad == expected) {
421 		lwsl_user("Completed: OK (seen expected %d)\n", expected);
422 		return 0;
423 	} else
424 		lwsl_err("Completed: failed: exit %d, expected %d\n", bad, expected);
425 
426 	return 1;
427 }
428