1/* 2 * lws-minimal-http-server-dynamic 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 a minimal http server that can produce dynamic http 10 * content as well as static content. 11 * 12 * To keep it simple, it serves the static stuff from the subdirectory 13 * "./mount-origin" of the directory it was started in. 14 * 15 * You can change that by changing mount.origin below. 16 */ 17 18#include <libwebsockets.h> 19#include <string.h> 20#include <signal.h> 21#include <time.h> 22 23/* 24 * Unlike ws, http is a stateless protocol. This pss only exists for the 25 * duration of a single http transaction. With http/1.1 keep-alive and http/2, 26 * that is unrelated to (shorter than) the lifetime of the network connection. 27 */ 28struct pss { 29 char path[128]; 30 31 int times; 32 int budget; 33 34 int content_lines; 35}; 36 37static int interrupted; 38 39static int 40callback_dynamic_http(struct lws *wsi, enum lws_callback_reasons reason, 41 void *user, void *in, size_t len) 42{ 43 struct pss *pss = (struct pss *)user; 44 uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, 45 *end = &buf[sizeof(buf) - 1]; 46 time_t t; 47 int n; 48#if defined(LWS_HAVE_CTIME_R) 49 char date[32]; 50#endif 51 52 switch (reason) { 53 case LWS_CALLBACK_HTTP: 54 55 /* 56 * If you want to know the full url path used, you can get it 57 * like this 58 * 59 * n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI); 60 * 61 * The base path is the first (n - strlen((const char *)in)) 62 * chars in buf. 63 */ 64 65 /* 66 * In contains the url part after the place the mount was 67 * positioned at, eg, if positioned at "/dyn" and given 68 * "/dyn/mypath", in will contain /mypath 69 */ 70 lws_snprintf(pss->path, sizeof(pss->path), "%s", 71 (const char *)in); 72 73 lws_get_peer_simple(wsi, (char *)buf, sizeof(buf)); 74 lwsl_notice("%s: HTTP: connection %s, path %s\n", __func__, 75 (const char *)buf, pss->path); 76 77 /* 78 * Demonstrates how to retreive a urlarg x=value 79 */ 80 81 { 82 char value[100]; 83 int z = lws_get_urlarg_by_name_safe(wsi, "x", value, 84 sizeof(value) - 1); 85 86 if (z >= 0) 87 lwsl_hexdump_notice(value, (size_t)z); 88 } 89 90 /* 91 * prepare and write http headers... with regards to content- 92 * length, there are three approaches: 93 * 94 * - http/1.0 or connection:close: no need, but no pipelining 95 * - http/1.1 or connected:keep-alive 96 * (keep-alive is default for 1.1): content-length required 97 * - http/2: no need, LWS_WRITE_HTTP_FINAL closes the stream 98 * 99 * giving the api below LWS_ILLEGAL_HTTP_CONTENT_LEN instead of 100 * a content length forces the connection response headers to 101 * send back "connection: close", disabling keep-alive. 102 * 103 * If you know the final content-length, it's always OK to give 104 * it and keep-alive can work then if otherwise possible. But 105 * often you don't know it and avoiding having to compute it 106 * at header-time makes life easier at the server. 107 */ 108 if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK, 109 "text/html", 110 LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */ 111 &p, end)) 112 return 1; 113 if (lws_finalize_write_http_header(wsi, start, &p, end)) 114 return 1; 115 116 pss->times = 0; 117 pss->budget = atoi((char *)in + 1); 118 pss->content_lines = 0; 119 if (!pss->budget) 120 pss->budget = 10; 121 122 /* write the body separately */ 123 lws_callback_on_writable(wsi); 124 125 return 0; 126 127 case LWS_CALLBACK_HTTP_WRITEABLE: 128 129 if (!pss || pss->times > pss->budget) 130 break; 131 132 /* 133 * We send a large reply in pieces of around 2KB each. 134 * 135 * For http/1, it's possible to send a large buffer at once, 136 * but lws will malloc() up a temp buffer to hold any data 137 * that the kernel didn't accept in one go. This is expensive 138 * in memory and cpu, so it's better to stage the creation of 139 * the data to be sent each time. 140 * 141 * For http/2, large data frames would block the whole 142 * connection, not just the stream and are not allowed. Lws 143 * will call back on writable when the stream both has transmit 144 * credit and the round-robin fair access for sibling streams 145 * allows it. 146 * 147 * For http/2, we must send the last part with 148 * LWS_WRITE_HTTP_FINAL to close the stream representing 149 * this transaction. 150 */ 151 n = LWS_WRITE_HTTP; 152 if (pss->times == pss->budget) 153 n = LWS_WRITE_HTTP_FINAL; 154 155 if (!pss->times) { 156 /* 157 * the first time, we print some html title 158 */ 159 t = time(NULL); 160 /* 161 * to work with http/2, we must take care about LWS_PRE 162 * valid behind the buffer we will send. 163 */ 164 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "<html>" 165 "<head><meta charset=utf-8 " 166 "http-equiv=\"Content-Language\" " 167 "content=\"en\"/></head><body>" 168 "<img src=\"/libwebsockets.org-logo.svg\">" 169 "<br>Dynamic content for '%s' from mountpoint." 170 "<br>Time: %s<br><br>" 171 "</body></html>", pss->path, 172#if defined(LWS_HAVE_CTIME_R) 173 ctime_r(&t, date)); 174#else 175 ctime(&t)); 176#endif 177 } else { 178 /* 179 * after the first time, we create bulk content. 180 * 181 * Again we take care about LWS_PRE valid behind the 182 * buffer we will send. 183 */ 184 185 while (lws_ptr_diff(end, p) > 80) 186 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), 187 "%d.%d: this is some content... ", 188 pss->times, pss->content_lines++); 189 190 p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "<br><br>"); 191 } 192 193 pss->times++; 194 if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff_size_t(p, start), (enum lws_write_protocol)n) != 195 lws_ptr_diff(p, start)) 196 return 1; 197 198 /* 199 * HTTP/1.0 no keepalive: close network connection 200 * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction 201 * HTTP/2: stream ended, parent connection remains up 202 */ 203 if (n == LWS_WRITE_HTTP_FINAL) { 204 if (lws_http_transaction_completed(wsi)) 205 return -1; 206 } else 207 lws_callback_on_writable(wsi); 208 209 return 0; 210 211 default: 212 break; 213 } 214 215 return lws_callback_http_dummy(wsi, reason, user, in, len); 216} 217 218static const struct lws_protocols defprot = 219 { "defprot", lws_callback_http_dummy, 0, 0, 0, NULL, 0 }, protocol = 220 { "http", callback_dynamic_http, sizeof(struct pss), 0, 0, NULL, 0 }; 221 222static const struct lws_protocols *pprotocols[] = { &defprot, &protocol, NULL }; 223 224/* override the default mount for /dyn in the URL space */ 225 226static const struct lws_http_mount mount_dyn = { 227 /* .mount_next */ NULL, /* linked-list "next" */ 228 /* .mountpoint */ "/dyn", /* mountpoint URL */ 229 /* .origin */ NULL, /* protocol */ 230 /* .def */ NULL, 231 /* .protocol */ "http", 232 /* .cgienv */ NULL, 233 /* .extra_mimetypes */ NULL, 234 /* .interpret */ NULL, 235 /* .cgi_timeout */ 0, 236 /* .cache_max_age */ 0, 237 /* .auth_mask */ 0, 238 /* .cache_reusable */ 0, 239 /* .cache_revalidate */ 0, 240 /* .cache_intermediaries */ 0, 241 /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ 242 /* .mountpoint_len */ 4, /* char count */ 243 /* .basic_auth_login_file */ NULL, 244}; 245 246/* default mount serves the URL space from ./mount-origin */ 247 248static const struct lws_http_mount mount = { 249 /* .mount_next */ &mount_dyn, /* linked-list "next" */ 250 /* .mountpoint */ "/", /* mountpoint URL */ 251 /* .origin */ "./mount-origin", /* serve from dir */ 252 /* .def */ "index.html", /* default filename */ 253 /* .protocol */ NULL, 254 /* .cgienv */ NULL, 255 /* .extra_mimetypes */ NULL, 256 /* .interpret */ NULL, 257 /* .cgi_timeout */ 0, 258 /* .cache_max_age */ 0, 259 /* .auth_mask */ 0, 260 /* .cache_reusable */ 0, 261 /* .cache_revalidate */ 0, 262 /* .cache_intermediaries */ 0, 263 /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ 264 /* .mountpoint_len */ 1, /* char count */ 265 /* .basic_auth_login_file */ NULL, 266}; 267 268void sigint_handler(int sig) 269{ 270 interrupted = 1; 271} 272 273int main(int argc, const char **argv) 274{ 275 struct lws_context_creation_info info; 276 struct lws_context *context; 277 const char *p; 278 int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE 279 /* for LLL_ verbosity above NOTICE to be built into lws, 280 * lws must have been configured and built with 281 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ 282 /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ 283 /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ 284 /* | LLL_DEBUG */; 285 286 signal(SIGINT, sigint_handler); 287 288 if ((p = lws_cmdline_option(argc, argv, "-d"))) 289 logs = atoi(p); 290 291 lws_set_log_level(logs, NULL); 292 lwsl_user("LWS minimal http server dynamic | visit http://localhost:7681\n"); 293 294 memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ 295 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | 296 LWS_SERVER_OPTION_EXPLICIT_VHOSTS | 297 LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; 298 299 /* for testing ah queue, not useful in real world */ 300 if (lws_cmdline_option(argc, argv, "--ah1")) 301 info.max_http_header_pool = 1; 302 303 context = lws_create_context(&info); 304 if (!context) { 305 lwsl_err("lws init failed\n"); 306 return 1; 307 } 308 309 /* http on 7681 */ 310 311 info.port = 7681; 312 info.pprotocols = pprotocols; 313 info.mounts = &mount; 314 info.vhost_name = "http"; 315 316 if (!lws_create_vhost(context, &info)) { 317 lwsl_err("Failed to create tls vhost\n"); 318 goto bail; 319 } 320 321 /* https on 7682 */ 322 323 info.port = 7682; 324 info.error_document_404 = "/404.html"; 325#if defined(LWS_WITH_TLS) 326 info.ssl_cert_filepath = "localhost-100y.cert"; 327 info.ssl_private_key_filepath = "localhost-100y.key"; 328#endif 329 info.vhost_name = "localhost"; 330 331 if (!lws_create_vhost(context, &info)) { 332 lwsl_err("Failed to create tls vhost\n"); 333 goto bail; 334 } 335 336 while (n >= 0 && !interrupted) 337 n = lws_service(context, 0); 338 339bail: 340 lws_context_destroy(context); 341 342 return 0; 343} 344