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