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
19 static int interrupted, bad = 1, status;
20 static struct lws *client_wsi;
21
22 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)23 callback_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 = 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
127 static const struct lws_protocols protocols[] = {
128 {
129 "http",
130 callback_http,
131 0,
132 0,
133 },
134 { NULL, NULL, 0, 0 }
135 };
136
137 static void
sigint_handler(int sig)138 sigint_handler(int sig)
139 {
140 interrupted = 1;
141 }
142
main(int argc,const char ** argv)143 int main(int argc, const char **argv)
144 {
145 struct lws_context_creation_info info;
146 struct lws_client_connect_info i;
147 struct lws_context *context;
148 const char *p;
149 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
150 /*
151 * For LLL_ verbosity above NOTICE to be built into lws,
152 * lws must have been configured and built with
153 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
154 *
155 * | LLL_INFO | LLL_PARSER | LLL_HEADER | LLL_EXT |
156 * LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
157 */ ;
158
159 signal(SIGINT, sigint_handler);
160
161 if ((p = lws_cmdline_option(argc, argv, "-d")))
162 logs = atoi(p);
163
164 lws_set_log_level(logs, NULL);
165 lwsl_user("LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1]\n");
166
167 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
168 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
169 info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
170 info.protocols = protocols;
171 /*
172 * since we know this lws context is only ever going to be used with
173 * one client wsis / fds / sockets at a time, let lws know it doesn't
174 * have to use the default allocations for fd tables up to ulimit -n.
175 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
176 * will use.
177 */
178 info.fd_limit_per_thread = 1 + 1 + 1;
179
180 #if defined(LWS_WITH_MBEDTLS)
181 /*
182 * OpenSSL uses the system trust store. mbedTLS has to be told which
183 * CA to trust explicitly.
184 */
185 info.client_ssl_ca_filepath = "./warmcat.com.cer";
186 #endif
187
188 context = lws_create_context(&info);
189 if (!context) {
190 lwsl_err("lws init failed\n");
191 return 1;
192 }
193
194 memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
195 i.context = context;
196
197 if (!lws_cmdline_option(argc, argv, "-n"))
198 i.ssl_connection = LCCSCF_USE_SSL;
199
200 if (lws_cmdline_option(argc, argv, "-l")) {
201 i.port = 7681;
202 i.address = "localhost";
203 i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
204 } else {
205 i.port = 443;
206 i.address = "warmcat.com";
207 }
208
209 /* currently custom headers receive only works with h1 */
210 i.alpn = "http/1.1";
211
212 i.path = "/";
213 i.host = i.address;
214 i.origin = i.address;
215 i.method = "GET";
216
217 i.protocol = protocols[0].name;
218 i.pwsi = &client_wsi;
219 lws_client_connect_via_info(&i);
220
221 while (n >= 0 && client_wsi && !interrupted)
222 n = lws_service(context, 0);
223
224 lws_context_destroy(context);
225 lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
226
227 return bad;
228 }
229