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