1/*
2 * lws-minimal-http-client
3 *
4 * Written in 2010-2019 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
19static int interrupted, bad = 1, status;
20static struct lws *client_wsi;
21
22static int
23callback_http(struct lws *wsi, enum lws_callback_reasons reason,
24	      void *user, void *in, size_t len)
25{
26	char val[32];
27	int n;
28
29	switch (reason) {
30
31	/* because we are protocols[0] ... */
32	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
33		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
34			 in ? (char *)in : "(null)");
35		client_wsi = NULL;
36		break;
37
38	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
39	{
40		 unsigned char **p = (unsigned char **)in, *end = (*p) + len;
41
42		 /*
43		  * How to send a custom header in the request to the server
44		  */
45
46		 if (lws_add_http_header_by_name(wsi,
47				 (const unsigned char *)"dnt",
48				 (const unsigned char *)"1", 1, p, end))
49			 return -1;
50		break;
51	}
52
53	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
54		status = (int)lws_http_client_http_response(wsi);
55		lwsl_user("Connected with server response: %d\n", status);
56
57		/*
58		 * How to query custom headers (http 1.x only at the momemnt)
59		 *
60		 * warmcat.com sends a custom header "test-custom-header" for
61		 * testing, it has the fixed value "hello".
62		 */
63
64		n = lws_hdr_custom_length(wsi, "test-custom-header:", 19);
65		if (n < 0)
66			lwsl_notice("%s: Can't find test-custom-header\n",
67				    __func__);
68		else {
69			if (lws_hdr_custom_copy(wsi, val, sizeof(val),
70						"test-custom-header:", 19) < 0)
71				lwsl_notice("%s: custom header too long\n",
72					    __func__);
73			else
74				lwsl_notice("%s: custom header: '%s'\n",
75						__func__, val);
76		}
77		break;
78
79	/* chunks of chunked content, with header removed */
80	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
81		lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
82#if 0  /* enable to dump the html */
83		{
84			const char *p = in;
85
86			while (len--)
87				if (*p < 0x7f)
88					putchar(*p++);
89				else
90					putchar('.');
91		}
92#endif
93		return 0; /* don't passthru */
94
95	/* uninterpreted http content */
96	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
97		{
98			char buffer[1024 + LWS_PRE];
99			char *px = buffer + LWS_PRE;
100			int lenx = sizeof(buffer) - LWS_PRE;
101
102			if (lws_http_client_read(wsi, &px, &lenx) < 0)
103				return -1;
104		}
105		return 0; /* don't passthru */
106
107	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
108		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
109		client_wsi = NULL;
110		bad = status != 200;
111		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
112		break;
113
114	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
115		client_wsi = NULL;
116		bad = status != 200;
117		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
118		break;
119
120	default:
121		break;
122	}
123
124	return lws_callback_http_dummy(wsi, reason, user, in, len);
125}
126
127static const struct lws_protocols protocols[] = {
128	{
129		"http",
130		callback_http,
131		0, 0, 0, NULL, 0
132	},
133	LWS_PROTOCOL_LIST_TERM
134};
135
136static void
137sigint_handler(int sig)
138{
139	interrupted = 1;
140}
141
142int main(int argc, const char **argv)
143{
144	struct lws_context_creation_info info;
145	struct lws_client_connect_info i;
146	struct lws_context *context;
147	const char *p;
148	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
149		   /*
150		    * For LLL_ verbosity above NOTICE to be built into lws,
151		    * lws must have been configured and built with
152		    * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
153		    *
154		    * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
155		    *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
156		    */ ;
157
158	signal(SIGINT, sigint_handler);
159
160	if ((p = lws_cmdline_option(argc, argv, "-d")))
161		logs = atoi(p);
162
163	lws_set_log_level(logs, NULL);
164	lwsl_user("LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1]\n");
165
166	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
167	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
168	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
169	info.protocols = protocols;
170	/*
171	 * since we know this lws context is only ever going to be used with
172	 * one client wsis / fds / sockets at a time, let lws know it doesn't
173	 * have to use the default allocations for fd tables up to ulimit -n.
174	 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
175	 * will use.
176	 */
177	info.fd_limit_per_thread = 1 + 1 + 1;
178
179#if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
180	/*
181	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
182	 * CA to trust explicitly.
183	 */
184	info.client_ssl_ca_filepath = "./warmcat.com.cer";
185#endif
186
187	context = lws_create_context(&info);
188	if (!context) {
189		lwsl_err("lws init failed\n");
190		return 1;
191	}
192
193	memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
194	i.context = context;
195
196	if (!lws_cmdline_option(argc, argv, "-n"))
197		i.ssl_connection = LCCSCF_USE_SSL;
198
199	if (lws_cmdline_option(argc, argv, "-l")) {
200		i.port = 7681;
201		i.address = "localhost";
202		i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
203	} else {
204		i.port = 443;
205		i.address = "warmcat.com";
206	}
207
208	/* currently custom headers receive only works with h1 */
209	i.alpn = "http/1.1";
210
211	i.path = "/";
212	i.host = i.address;
213	i.origin = i.address;
214	i.method = "GET";
215
216	i.protocol = protocols[0].name;
217	i.pwsi = &client_wsi;
218	lws_client_connect_via_info(&i);
219
220	while (n >= 0 && client_wsi && !interrupted)
221		n = lws_service(context, 0);
222
223	lws_context_destroy(context);
224	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
225
226	return bad;
227}
228