From 581b86efd04002d1ae95e3e29e3bbaf5e28f7dfd Mon Sep 17 00:00:00 2001 From: Andy Green Date: Mon, 12 Jun 2017 13:36:24 +0800 Subject: [PATCH] HTTP_PROXY: make usable --- CMakeLists.txt | 8 ++-- README.lwsws.md | 14 ++++++ lib/client-handshake.c | 61 ++++++++++++++++++--------- lib/context.c | 88 +++++++++++++++++++++++++++++++++++++- lib/libwebsockets.h | 4 +- lib/parsers.c | 7 ++- lib/server.c | 96 ++++++++++++++++++++++++++++++++++++++++++ lib/service.c | 3 ++ lib/ssl-client.c | 4 +- test-server/test-server-http.c | 43 +++++++++++-------- test-server/test-server.c | 13 +++++- 11 files changed, 291 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d3bdee..a79fdf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ option(LWS_UNIX_SOCK "Compile with support for UNIX domain socket" OFF) option(LWS_WITH_HTTP2 "Compile with support for http2" OFF) option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF) option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF) -option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF) +option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying (requires libhubbub)" OFF) option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF) option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF) option(LWS_WITH_ACCESS_LOG "Support generating Apache-compatible access logs" OFF) @@ -392,7 +392,7 @@ if (LWS_WITH_HTTP2) endif() if ("${LWS_MAX_SMP}" STREQUAL "") - set(LWS_MAX_SMP 32) + set(LWS_MAX_SMP 1) endif() @@ -735,7 +735,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID endif () if ((CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) AND NOT LWS_WITHOUT_TESTAPPS) - if (UNIX) + if (UNIX AND LWS_MAX_SMP GREATER 1) # jeez clang understands -pthread but dies if he sees it at link time! # http://stackoverflow.com/questions/2391194/what-is-gs-pthread-equiv-in-clang set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread" ) @@ -1251,7 +1251,7 @@ if (NOT LWS_WITHOUT_TESTAPPS) "" "") endif() - if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))) + if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) AND LWS_MAX_SMP GREATER 1) create_test_app(test-server-pthreads "test-server/test-server-pthreads.c" "test-server/test-server-http.c" diff --git a/README.lwsws.md b/README.lwsws.md index 12ad81e..04ba81a 100644 --- a/README.lwsws.md +++ b/README.lwsws.md @@ -292,6 +292,20 @@ Mount protocols are used to control what kind of translation happens ``` would cause the url /git/myrepo to pass "myrepo" to the cgi /var/www/cgi-bin/cgit and send the results to the client. + - http:// or https:// these perform reverse proxying, serving the remote origin content from the mountpoint. Eg + +``` + { + "mountpoint": "/proxytest", + "origin": "https://libwebsockets.org" + } +``` + +This will cause your local url `/proxytest` to serve content fetched from libwebsockets.org over ssl; whether it's served from your server using ssl is unrelated and depends how you configured your local server. Notice if you will use the proxying feature, `LWS_WITH_HTTP_PROXY` is required to be enabled at cmake, and for `https` proxy origins, your lwsws configuration must include `"init-ssl": "1"` and the vhost with the proxy mount must have `"enable-client-ssl": "1"`, even if you are not using ssl to serve. + +`/proxytest/abc`, or `/proxytest/abc?def=ghi` etc map to the origin + the part past `/proxytest`, so links and img src urls etc work as do all urls under the origin path. + +In addition link and src urls in the document are rewritten so / or the origin url part are rewritten to the mountpoint part. @section lwswsomo Lwsws Other mount options diff --git a/lib/client-handshake.c b/lib/client-handshake.c index dc6de91..26543b9 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -262,7 +262,8 @@ lws_client_connect_2(struct lws *wsi) n = sizeof(struct sockaddr); } - if (connect(wsi->desc.sockfd, v, n) == -1 || LWS_ERRNO == LWS_EISCONN) { + if (connect(wsi->desc.sockfd, v, n) == -1 || + LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || LWS_ERRNO == LWS_EWOULDBLOCK @@ -527,14 +528,14 @@ html_parser_cb(const hubbub_token *token, void *pw) "(force-quirks) " : ""); if (token->data.doctype.public_missing) - printf("\tpublic: missing\n"); + lwsl_debug("\tpublic: missing\n"); else p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n", (int) token->data.doctype.public_id.len, token->data.doctype.public_id.ptr); if (token->data.doctype.system_missing) - printf("\tsystem: missing\n"); + lwsl_debug("\tsystem: missing\n"); else p += lws_snprintf(p, end - p, " \"%.*s\">\n", (int) token->data.doctype.system_id.len, @@ -557,25 +558,28 @@ html_parser_cb(const hubbub_token *token, void *pw) const char *pp = (const char *)token->data.tag.attributes[i].value.ptr; int plen = (int) token->data.tag.attributes[i].value.len; - if (!hstrcmp(&token->data.tag.attributes[i].value, - r->from, r->from_len)) { - pp += r->from_len; - plen -= r->from_len; + if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) { + + if (!hstrcmp(&token->data.tag.attributes[i].value, + r->from, r->from_len)) { + pp += r->from_len; + plen -= r->from_len; + } + p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + r->to, plen, pp); + continue; } - p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"", - (int) token->data.tag.attributes[i].name.len, - token->data.tag.attributes[i].name.ptr, - r->to, plen, pp); - - } else - - p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"", - (int) token->data.tag.attributes[i].name.len, - token->data.tag.attributes[i].name.ptr, - (int) token->data.tag.attributes[i].value.len, - token->data.tag.attributes[i].value.ptr); + } + + p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); } - p += lws_snprintf(p, end - p, ">\n"); + p += lws_snprintf(p, end - p, ">"); break; case HUBBUB_TOKEN_END_TAG: p += lws_snprintf(p, end - p, "data.tag.name.len, @@ -593,7 +597,7 @@ html_parser_cb(const hubbub_token *token, void *pw) (int) token->data.tag.attributes[i].value.len, token->data.tag.attributes[i].value.ptr); } - p += lws_snprintf(p, end - p, ">\n"); + p += lws_snprintf(p, end - p, ">"); break; case HUBBUB_TOKEN_COMMENT: p += lws_snprintf(p, end - p, "\n", @@ -601,6 +605,21 @@ html_parser_cb(const hubbub_token *token, void *pw) token->data.comment.ptr); break; case HUBBUB_TOKEN_CHARACTER: + if (token->data.character.len == 1) { + if (*token->data.character.ptr == '<') { + p += lws_snprintf(p, end - p, "<"); + break; + } + if (*token->data.character.ptr == '>') { + p += lws_snprintf(p, end - p, ">"); + break; + } + if (*token->data.character.ptr == '&') { + p += lws_snprintf(p, end - p, "&"); + break; + } + } + p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len, token->data.character.ptr); break; diff --git a/lib/context.c b/lib/context.c index f71f01d..fab3dc2 100644 --- a/lib/context.c +++ b/lib/context.c @@ -222,10 +222,13 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, { #ifdef LWS_WITH_CGI struct lws_cgi_args *args; - char buf[128]; +#endif +#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) + char buf[512]; int n; #endif + switch (reason) { case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER @@ -250,9 +253,92 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, break; } #endif +#if defined(LWS_WITH_HTTP_PROXY) + if (wsi->reason_bf & 2) { + char *px = buf + LWS_PRE; + int lenx = sizeof(buf) - LWS_PRE; + /* + * our sink is writeable and our source has something + * to read. So read a lump of source material of + * suitable size to send or what's available, whichever + * is the smaller. + */ + + wsi->reason_bf &= ~2; + if (!lws_get_child(wsi)) + break; + if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) + return -1; + break; + } +#endif + break; + +#if defined(LWS_WITH_HTTP_PROXY) + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + lws_get_parent(wsi)->reason_bf |= 2; + lws_callback_on_writable(lws_get_parent(wsi)); break; + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", (int)len); + assert(lws_get_parent(wsi)); + n = lws_write(lws_get_parent(wsi), (unsigned char *)in, + len, LWS_WRITE_HTTP); + if (n < 0) + return -1; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + unsigned char *p, *end; + char ctype[64], ctlen = 0; + + //lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); + + p = (unsigned char *)buf + LWS_PRE; + end = p + sizeof(buf) - LWS_PRE; + + if (lws_add_http_header_status(lws_get_parent(wsi), HTTP_STATUS_OK, &p, end)) + return 1; + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_SERVER, + (unsigned char *)"libwebsockets", + 13, &p, end)) + return 1; + + ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); + if (ctlen > 0) { + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)ctype, ctlen, &p, end)) + return 1; + } +#if 0 + if (lws_add_http_header_content_length(lws_get_parent(wsi), + file_len, &p, end)) + return 1; +#endif + if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) + return 1; + + *p = '\0'; +// lwsl_info("%s\n", buf + LWS_PRE); + + n = lws_write(lws_get_parent(wsi), (unsigned char *)buf + LWS_PRE, + p - ((unsigned char *)buf + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + if (n < 0) + return -1; + + break; } + +#endif + #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here, our default policy is: * diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 2db2fc6..23d6588 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -2289,8 +2289,8 @@ struct lws_protocol_vhost_options { * served from a filesystem, or it is a cgi etc. */ enum lws_mount_protocols { - LWSMPRO_HTTP = 0, /**< not supported yet */ - LWSMPRO_HTTPS = 1, /**< not supported yet */ + LWSMPRO_HTTP = 0, /**< http reverse proxy */ + LWSMPRO_HTTPS = 1, /**< https reverse proxy */ LWSMPRO_FILE = 2, /**< serve from filesystem directory */ LWSMPRO_CGI = 3, /**< pass to CGI to handle */ LWSMPRO_REDIR_HTTP = 4, /**< redirect to http:// url */ diff --git a/lib/parsers.c b/lib/parsers.c index 1140b4d..9cd70e0 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -321,15 +321,18 @@ int lws_header_table_detach(struct lws *wsi, int autoservice) pt->ah_wait_list_length--; #ifndef LWS_NO_CLIENT - if (wsi->state == LWSS_CLIENT_UNCONNECTED) + if (wsi->state == LWSS_CLIENT_UNCONNECTED) { + lws_pt_unlock(pt); + if (!lws_client_connect_via_info2(wsi)) { /* our client connect has failed, the wsi * has been closed */ - lws_pt_unlock(pt); return -1; } + return 0; + } #endif assert(!!pt->ah_wait_list_length == !!(int)(long)pt->ah_wait_list); diff --git a/lib/server.c b/lib/server.c index 54e974a..2693cc0 100644 --- a/lib/server.c +++ b/lib/server.c @@ -662,6 +662,23 @@ lws_unauthorised_basic_auth(struct lws *wsi) #endif +int lws_clean_url(char *p) +{ + while (*p) { + if (p[0] == '/' && p[1] == '/') { + char *p1 = p; + while (*p1) { + *p1 = p1[1]; + p1++; + } + continue; + } + p++; + } + + return 0; +} + int lws_http_action(struct lws *wsi) { @@ -958,6 +975,7 @@ lws_http_action(struct lws *wsi) "%s%s%s/", oprot[lws_is_ssl(wsi)], lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), uri_ptr); + lws_clean_url((char *)end); n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, end, n, &p, end); @@ -1013,6 +1031,84 @@ lws_http_action(struct lws *wsi) } #endif +#if defined(LWS_WITH_HTTP_PROXY) + /* + * The mount is a reverse proxy? + */ + + if (hit->origin_protocol == LWSMPRO_HTTPS || + hit->origin_protocol == LWSMPRO_HTTP) { + struct lws_client_connect_info i; + char ads[96], rpath[256], *pcolon, *pslash, *p; + int n, na; + + memset(&i, 0, sizeof(i)); + i.context = lws_get_context(wsi); + + pcolon = strchr(hit->origin, ':'); + pslash = strchr(hit->origin, '/'); + if (!pslash) { + lwsl_err("Proxy mount origin '%s' must have /\n", hit->origin); + return -1; + } + if (pcolon > pslash) + pcolon = NULL; + + if (pcolon) + n = pcolon - hit->origin; + else + n = pslash - hit->origin; + + if (n >= sizeof(ads) - 2) + n = sizeof(ads) - 2; + + memcpy(ads, hit->origin, n); + ads[n] = '\0'; + + i.address = ads; + i.port = 80; + if (hit->origin_protocol == LWSMPRO_HTTPS) { + i.port = 443; + i.ssl_connection = 1; + } + if (pcolon) + i.port = atoi(pcolon + 1); + + lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", pslash + 1, uri_ptr + hit->mountpoint_len); + lws_clean_url(rpath); + na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS); + if (na) { + p = rpath + strlen(rpath); + *p++ = '?'; + lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p, WSI_TOKEN_HTTP_URI_ARGS); + while (--na) { + if (*p == '\0') + *p = '&'; + p++; + } + } + + + i.path = rpath; + i.host = i.address; + i.origin = NULL; + i.method = "GET"; + i.parent_wsi = wsi; + i.uri_replace_from = hit->origin; + i.uri_replace_to = hit->mountpoint; + + lwsl_notice("proxying to %s port %d url %s, ssl %d, from %s, to %s\n", + i.address, i.port, i.path, i.ssl_connection, i.uri_replace_from, i.uri_replace_to); + + if (!lws_client_connect_via_info(&i)) { + lwsl_err("proxy connect fail\n"); + return 1; + } + + return 0; + } +#endif + /* * A particular protocol callback is mounted here? * diff --git a/lib/service.c b/lib/service.c index 6f08dee..6d7e538 100644 --- a/lib/service.c +++ b/lib/service.c @@ -1203,6 +1203,9 @@ drain: lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP closed it\n"); goto close_and_handled; } + + n = 0; + goto handled; } #endif /* diff --git a/lib/ssl-client.c b/lib/ssl-client.c index 45c9fb7..aba337b 100644 --- a/lib/ssl-client.c +++ b/lib/ssl-client.c @@ -318,7 +318,7 @@ lws_ssl_client_connect2(struct lws *wsi) if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); - lwsl_notice("%s: SSL_connect says %d\n", __func__, n); + lwsl_debug("%s: SSL_connect says %d\n", __func__, n); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); @@ -395,7 +395,7 @@ lws_ssl_client_connect2(struct lws *wsi) lws_latency(context, wsi, "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); - lwsl_notice("get_verify says %d\n", n); + lwsl_debug("get_verify says %d\n", n); if (n != X509_V_OK) { if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c index eeda0f0..ea852c2 100644 --- a/test-server/test-server-http.c +++ b/test-server/test-server-http.c @@ -230,38 +230,37 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, goto try_to_reuse; } -#ifndef LWS_NO_CLIENT +#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT) if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; - char *rootpath = "/"; + char *rootpath = "/git/"; const char *p = (const char *)in; if (lws_get_child(wsi)) break; pss->client_finished = 0; - memset(&i,0, sizeof(i)); + memset(&i, 0, sizeof(i)); i.context = lws_get_context(wsi); - i.address = "git.libwebsockets.org"; - i.port = 80; - i.ssl_connection = 0; + i.address = "libwebsockets.org"; + i.port = 443; + i.ssl_connection = 1; if (p[10]) i.path = (char *)in + 10; else i.path = rootpath; - i.host = "git.libwebsockets.org"; + i.host = i.address; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; - i.uri_replace_from = "git.libwebsockets.org/"; + i.uri_replace_from = "libwebsockets.org/git/"; i.uri_replace_to = "/proxytest/"; + if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } - - break; } #endif @@ -421,6 +420,11 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, */ break; + case LWS_CALLBACK_CLIENT_RECEIVE: + ((char *)in)[len] = '\0'; + lwsl_info("rx %d '%s'\n", (int)len, (char *)in); + break; + case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { @@ -511,8 +515,10 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, if (pss->client_finished) return -1; - if (!pss->fop_fd) + if (!lws_get_child(wsi) && !pss->fop_fd) { + lwsl_notice("fop_fd NULL\n"); goto try_to_reuse; + } #ifndef LWS_NO_CLIENT if (pss->reason_bf & 2) { @@ -524,17 +530,24 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, * suitable size to send or what's available, whichever * is the smaller. */ + + pss->reason_bf &= ~2; wsi1 = lws_get_child(wsi); if (!wsi1) break; if (lws_http_client_read(wsi1, &px, &lenx) < 0) - goto bail; + return -1; if (pss->client_finished) return -1; + break; } + + if (lws_get_child(wsi)) + break; + #endif /* * we can send more of whatever it is we were sending @@ -657,16 +670,12 @@ bail: assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; - // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", - // wsi, lws_get_socket_fd(wsi), - // lws_get_parent(wsi), - // lws_get_socket_fd(lws_get_parent(wsi)), len); pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->reason_bf |= 2; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: - //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); + //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", (int)len); assert(lws_get_parent(wsi)); m = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); diff --git a/test-server/test-server.c b/test-server/test-server.c index d6ddb9d..7c1f9ea 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -191,6 +191,7 @@ static struct option options[] = { int main(int argc, char **argv) { struct lws_context_creation_info info; + struct lws_vhost *vhost; char interface_name[128] = ""; unsigned int ms, oldms = 0; const char *iface = NULL; @@ -382,7 +383,7 @@ int main(int argc, char **argv) info.gid = gid; info.uid = uid; info.max_http_header_pool = 16; - info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8; + info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.extensions = exts; info.timeout_secs = 5; info.ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" @@ -409,6 +410,16 @@ int main(int argc, char **argv) return -1; } + vhost = lws_create_vhost(context, &info); + if (!vhost) { + lwsl_err("vhost creation failed\n"); + return -1; + } + +#if !defined(LWS_NO_CLIENT) && defined(LWS_OPENSSL_SUPPORT) + lws_init_vhost_client_ssl(&info, vhost); +#endif + /* this shows how to override the lws file operations. You don't need * to do any of this unless you have a reason (eg, want to serve * compressed files without decompressing the whole archive) -- 2.7.4