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[] = { ¬ifier, 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