Imported Upstream version 3.2.0
[platform/upstream/libwebsockets.git] / minimal-examples / http-client / minimal-http-client-post / minimal-http-client-post.c
1 /*
2  * lws-minimal-http-client-post
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 and POST.
10  *
11  * It POSTs both form data and a file to the form at
12  * https://libwebsockets.org/testserver/formtest and dumps
13  * the html page received generated by the POST handler.
14  */
15
16 #include <libwebsockets.h>
17 #include <string.h>
18 #include <signal.h>
19
20 static int interrupted, bad = 0, status, count_clients = 1, completed;
21 static struct lws *client_wsi[4];
22
23 struct pss {
24         char boundary[32];
25         char body_part;
26 };
27
28 static int
29 callback_http(struct lws *wsi, enum lws_callback_reasons reason,
30               void *user, void *in, size_t len)
31 {
32         struct pss *pss = (struct pss *)user;
33         char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
34                 *end = &buf[sizeof(buf) - 1];
35         uint8_t **up, *uend;
36         uint32_t r;
37         int n;
38
39         switch (reason) {
40
41         /* because we are protocols[0] ... */
42         case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
43                 lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
44                          in ? (char *)in : "(null)");
45                 bad = 1;
46                 if (++completed == count_clients)
47                         lws_cancel_service(lws_get_context(wsi));
48                 break;
49
50         case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
51                 for (n = 0; n < count_clients; n++)
52                         if (client_wsi[n] == wsi) {
53                                 client_wsi[n] = NULL;
54                                 bad |= status != 200;
55                                 if (++completed == count_clients)
56                                         /* abort poll wait */
57                                         lws_cancel_service(lws_get_context(wsi));
58                         }
59                 break;
60
61         /* ...callbacks related to receiving the result... */
62
63         case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
64                 status = lws_http_client_http_response(wsi);
65                 lwsl_user("Connected with server response: %d\n", status);
66                 break;
67
68         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
69                 lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
70                 lwsl_hexdump_notice(in, len);
71                 return 0; /* don't passthru */
72
73         case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
74                 n = sizeof(buf) - LWS_PRE;
75                 if (lws_http_client_read(wsi, &p, &n) < 0)
76                         return -1;
77
78                 return 0; /* don't passthru */
79
80         case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
81                 lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
82                 bad |= status != 200;
83                 /*
84                  * Do this to mark us as having processed the completion
85                  * so close doesn't duplicate (with pipelining, completion !=
86                  * connection close
87                  */
88                 for (n = 0; n < count_clients; n++)
89                         if (client_wsi[n] == wsi)
90                                 client_wsi[n] = NULL;
91                 if (++completed == count_clients)
92                         /* abort poll wait */
93                         lws_cancel_service(lws_get_context(wsi));
94                 break;
95
96         /* ...callbacks related to generating the POST... */
97
98         case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
99                 lwsl_user("LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER\n");
100                 up = (uint8_t **)in;
101                 uend = *up + len - 1;
102
103                 /* generate a random boundary string */
104
105                 lws_get_random(lws_get_context(wsi), &r, sizeof(r));
106                 lws_snprintf(pss->boundary, sizeof(pss->boundary) - 1,
107                                 "---boundary-%08x", r);
108
109                 n = lws_snprintf(buf, sizeof(buf) - 1,
110                         "multipart/form-data; boundary=%s", pss->boundary);
111                 if (lws_add_http_header_by_token(wsi,
112                                 WSI_TOKEN_HTTP_CONTENT_TYPE,
113                                 (uint8_t *)buf, n, up, uend))
114                         return 1;
115                 /*
116                  * Notice because we are sending multipart/form-data we can
117                  * usually rely on the server to understand where the form
118                  * payload ends without having to give it an overall
119                  * content-length (which can be troublesome to compute ahead
120                  * of generating the data to send).
121                  *
122                  * Tell lws we are going to send the body next...
123                  */
124                 lws_client_http_body_pending(wsi, 1);
125                 lws_callback_on_writable(wsi);
126                 break;
127
128         case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
129                 lwsl_user("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n");
130                 n = LWS_WRITE_HTTP;
131
132                 /*
133                  * For a small body like this, we could prepare it in memory and
134                  * send it all at once.  But to show how to handle, eg,
135                  * arbitrary-sized file payloads, or huge form-data fields, the
136                  * sending is done in multiple passes through the event loop.
137                  */
138
139                 switch (pss->body_part++) {
140                 case 0:
141                         /* notice every usage of the boundary starts with -- */
142                         p += lws_snprintf(p, end - p, "--%s\xd\xa"
143                                 "content-disposition: "
144                                         "form-data; name=\"text\"\xd\xa"
145                                 "\xd\xa"
146                                 "my text field"
147                                 "\xd\xa", pss->boundary);
148                         break;
149                 case 1:
150                         p += lws_snprintf(p, end - p,
151                                 "--%s\xd\xa"
152                                 "content-disposition: form-data; name=\"file\";"
153                                 "filename=\"myfile.txt\"\xd\xa"
154                                 "content-type: text/plain\xd\xa"
155                                 "\xd\xa"
156                                         "This is the contents of the "
157                                         "uploaded file.\xd\xa"
158                                 "\xd\xa", pss->boundary);
159                         break;
160                 case 2:
161                         p += lws_snprintf(p, end - p, "--%s--\xd\xa",
162                                           pss->boundary);
163                         lws_client_http_body_pending(wsi, 0);
164                          /* necessary to support H2, it means we will write no
165                           * more on this stream */
166                         n = LWS_WRITE_HTTP_FINAL;
167                         break;
168
169                 default:
170                         /*
171                          * We can get extra callbacks here, if nothing to do,
172                          * then do nothing.
173                          */
174                         return 0;
175                 }
176
177                 if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n)
178                                 != lws_ptr_diff(p, start))
179                         return 1;
180
181                 if (n != LWS_WRITE_HTTP_FINAL)
182                         lws_callback_on_writable(wsi);
183
184                 return 0;
185
186         default:
187                 break;
188         }
189
190         return lws_callback_http_dummy(wsi, reason, user, in, len);
191 }
192
193 static const struct lws_protocols protocols[] = {
194         {
195                 "http",
196                 callback_http,
197                 sizeof(struct pss),
198                 0,
199         },
200         { NULL, NULL, 0, 0 }
201 };
202
203 static void
204 sigint_handler(int sig)
205 {
206         interrupted = 1;
207 }
208
209 int main(int argc, const char **argv)
210 {
211         struct lws_context_creation_info info;
212         struct lws_client_connect_info i;
213         struct lws_context *context;
214         const char *p;
215         int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
216                    /*
217                     * For LLL_ verbosity above NOTICE to be built into lws,
218                     * lws must have been configured and built with
219                     * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
220                     *
221                     * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
222                     *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
223                     */ ;
224
225         signal(SIGINT, sigint_handler);
226
227         if ((p = lws_cmdline_option(argc, argv, "-d")))
228                 logs = atoi(p);
229
230         lws_set_log_level(logs, NULL);
231         lwsl_user("LWS minimal http client - POST [-d<verbosity>] [-l] [--h1]\n");
232
233         if (lws_cmdline_option(argc, argv, "-m"))
234                 count_clients = LWS_ARRAY_SIZE(client_wsi);
235
236         memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
237         info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
238         info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
239         info.protocols = protocols;
240         /*
241          * since we know this lws context is only ever going to be used with
242          * one client wsis / fds / sockets at a time, let lws know it doesn't
243          * have to use the default allocations for fd tables up to ulimit -n.
244          * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
245          * will use.
246          */
247         info.fd_limit_per_thread = 1 + count_clients + 1;
248
249 #if defined(LWS_WITH_MBEDTLS)
250         /*
251          * OpenSSL uses the system trust store.  mbedTLS has to be told which
252          * CA to trust explicitly.
253          */
254         if (!lws_cmdline_option(argc, argv, "-l"))
255                 info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
256 #endif
257
258         context = lws_create_context(&info);
259         if (!context) {
260                 lwsl_err("lws init failed\n");
261                 return 1;
262         }
263
264         memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
265         i.context = context;
266         i.ssl_connection = LCCSCF_USE_SSL;
267
268         if (lws_cmdline_option(argc, argv, "-l")) {
269                 i.port = 7681;
270                 i.address = "localhost";
271                 i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
272                 i.path = "/formtest";
273         } else {
274                 i.port = 443;
275                 i.address = "libwebsockets.org";
276                 i.path = "/testserver/formtest";
277         }
278
279         i.host = i.address;
280         i.origin = i.address;
281         i.method = "POST";
282
283         /* force h1 even if h2 available */
284         if (lws_cmdline_option(argc, argv, "--h1"))
285                 i.alpn = "http/1.1";
286
287         i.protocol = protocols[0].name;
288
289         for (n = 0; n < count_clients; n++) {
290                 i.pwsi = &client_wsi[n];
291                 if (!lws_client_connect_via_info(&i))
292                         completed++;
293         }
294
295         while (n >= 0 && completed != count_clients && !interrupted)
296                 n = lws_service(context, 0);
297
298         lws_context_destroy(context);
299         lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
300
301         return bad;
302 }