From 3671b2632db76cc1733c8a6bc3ade8965bd0a956 Mon Sep 17 00:00:00 2001 From: Daniel O'Connell <doconn1701@gmail.com> Date: Thu, 29 May 2014 17:04:28 +0100 Subject: [PATCH] proxy unit test reference implementation --- examples/proxy.c | 170 +++++++++++++++--------- examples/proxy_web_root/app1/index.html | 18 ++- examples/proxy_web_root/app2/index.html | 37 ++++++ examples/proxy_web_root/index.html | 6 +- 4 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 examples/proxy_web_root/app2/index.html diff --git a/examples/proxy.c b/examples/proxy.c index 79ff53b09..c0b65c2ee 100644 --- a/examples/proxy.c +++ b/examples/proxy.c @@ -6,18 +6,18 @@ // git clone https://github.com/cesanta/mongoose.git // cd mongoose/examples // make proxy -// ./proxy # (then point your browser at http://localhost:2014) +// ./proxy +// +// Configure your browser to use localhost:2014 as a proxy for all protocols +// Then, navigate to https://cesanta.com #include "net_skeleton.h" #include "mongoose.h" static int s_received_signal = 0; -static const char *s_ca_cert = NULL; -static const char *s_cert = NULL; -static const char *s_sse_port = "2014"; -static const char *s_proxy_port = "2015"; -static struct mg_server *s_sse_server = NULL; -static struct mg_server *s_proxy_server = NULL; +static struct mg_server *s_server = NULL; + +#define SSE_CONNECTION ((void *) 1) static void elog(int do_exit, const char *fmt, ...) { va_list ap; @@ -34,8 +34,7 @@ static void signal_handler(int sig_num) { } static int sse_push(struct mg_connection *conn, enum mg_event ev) { - const char *str = (const char *) conn->connection_param; - if (ev == MG_POLL && str != NULL && strcmp(str, "sse") == 0) { + if (ev == MG_POLL && conn->connection_param == SSE_CONNECTION) { mg_printf(conn, "data: %s\r\n\r\n", (const char *) conn->callback_param); } return MG_TRUE; @@ -43,57 +42,99 @@ static int sse_push(struct mg_connection *conn, enum mg_event ev) { static void *sse_pusher_thread_func(void *param) { while (s_received_signal == 0) { - mg_wakeup_server_ex(s_sse_server, sse_push, "%lu %s", + mg_wakeup_server_ex(s_server, sse_push, "%lu %s", (unsigned long) time(NULL), (const char *) param); sleep(1); } return NULL; } -static int sse_handler(struct mg_connection *conn, enum mg_event ev) { +// Return: 1 if regular file, 2 if directory, 0 if not found +static int exists(const char *path) { + struct stat st; + return stat(path, &st) != 0 ? 0 : S_ISDIR(st.st_mode) == 0 ? 1 : 2; +} + +// Return: 1 if regular file, 2 if directory, 0 if not found +static int is_local_file(const char *uri, char *path, size_t path_len) { + snprintf(path, path_len, "%s/%s", + mg_get_option(s_server, "document_root"), uri); + return exists(path); +} + +static int try_to_serve_locally(struct mg_connection *conn) { + char path[500], buf[2000]; + int n, res; + FILE *fp = NULL; + + if ((res = is_local_file(conn->uri, path, sizeof(path))) == 2) { + strncat(path, "/index.html", sizeof(path) - strlen(path) - 1); + res = exists(path); + printf("PATH: [%s]\n", path); +#if 0 + snprintf(path + strlen(path), sizeof(path) - strlen(path), + "/%s", "index.html"); +#endif + } + if (res == 0) return MG_FALSE; + + if ((fp = fopen(path, "rb")) != NULL) { + printf("Serving [%s] locally \n", path); + mg_send_header(conn, "Connection", "close"); + mg_send_header(conn, "Content-Type", mg_get_mime_type(path, "text/plain")); + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { + mg_send_data(conn, buf, n); + } + mg_send_data(conn, "", 0); + fclose(fp); + } + return fp == NULL ? MG_FALSE : MG_TRUE; +} + +static int is_resource_present_locally(const char *uri) { + char path[500]; + return is_local_file(uri, path, sizeof(path)) || strcmp(uri, "/api/sse") == 0; +} + +static int proxy_event_handler(struct mg_connection *conn, enum mg_event ev) { + static const char target_url[] = "http://cesanta.com"; + static int target_url_size = sizeof(target_url) - 1; + const char *host; + switch (ev) { case MG_REQUEST: - if (strcmp(conn->uri, "/") == 0) { - mg_printf(conn, "%s", "HTTP/1.1 302 Moved\r\n" - "Location: /sse.html\r\n\r\n"); - return MG_TRUE; + host = mg_get_header(conn, "Host"); + printf("[%s] [%s] [%s]\n", conn->request_method, conn->uri, + host == NULL ? "" : host); + if (strstr(conn->uri, "/qqq") != NULL) s_received_signal = SIGTERM; + + // Proxied HTTP requests have URI http://..... + // Serve requests for target_url from the local FS. + if (memcmp(conn->uri, target_url, target_url_size) == 0 && + is_resource_present_locally(conn->uri + target_url_size)) { + conn->uri += target_url_size; // Leave only path in the URI } + if (strcmp(conn->uri, "/api/sse") == 0) { - conn->connection_param = (void *) "sse"; - mg_printf(conn, "%s", - "HTTP/1.0 200 OK\r\n" + conn->connection_param = SSE_CONNECTION; + mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n" "Content-Type: text/event-stream\r\n" - "Cache-Control: no-cache\r\n" - "\r\n"); + "Cache-Control: no-cache\r\n\r\n"); return MG_MORE; } + + if (host != NULL && strstr(host, "cesanta") != NULL) { + return try_to_serve_locally(conn); + } + return MG_FALSE; case MG_AUTH: return MG_TRUE; - case MG_POLL: - return MG_FALSE; default: return MG_FALSE; } } -static void *serve_thread_func(void *param) { - struct mg_server *server = (struct mg_server *) param; - printf("Starting on port %s\n", mg_get_option(server, "listening_port")); - - while (s_received_signal == 0) { - mg_poll_server(server, 1000); - } - - return NULL; -} - -static void show_usage_and_exit(const char *prog) { - elog(1, "Usage: %s [-ca_cert FILE] [-sse_port PORT] [-proxy_port PORT]", - prog); - exit(EXIT_FAILURE); -} - static void setopt(struct mg_server *s, const char *opt, const char *val) { const char *err_msg = mg_set_option(s, opt, val); if (err_msg != NULL) { @@ -102,47 +143,50 @@ static void setopt(struct mg_server *s, const char *opt, const char *val) { } int main(int argc, char *argv[]) { + const char *cert = NULL, *ca_cert = NULL, *port = "2014"; + const char *mitm = NULL, *dump = NULL; int i; // Parse command line options for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-ca_cert") == 0 && i + 1 < argc) { - s_ca_cert = argv[++i]; + ca_cert = argv[++i]; } else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) { - s_cert = argv[++i]; - } else if (strcmp(argv[i], "-sse_port") == 0 && i + 1 < argc) { - s_sse_port = argv[++i]; - } else if (strcmp(argv[i], "-proxy_port") == 0 && i + 1 < argc) { - s_proxy_port = argv[++i]; + cert = argv[++i]; + } else if (strcmp(argv[i], "-port") == 0 && i + 1 < argc) { + port = argv[++i]; + } else if (strcmp(argv[i], "-mitm") == 0 && i + 1 < argc) { + mitm = argv[++i]; + } else if (strcmp(argv[i], "-dump") == 0 && i + 1 < argc) { + dump = argv[++i]; } else { - show_usage_and_exit(argv[0]); + elog(1, "Usage: %s [-cert FILE] [-ca_cert FILE] [-port PORT]", argv[0]); } } signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); - - // Create, configure and start proxy server in a separate thread - s_proxy_server = mg_create_server(NULL, &sse_handler); - setopt(s_proxy_server, "listening_port", s_proxy_port); - setopt(s_proxy_server, "ssl_certificate", s_cert); - setopt(s_proxy_server, "ssl_ca_certificate", s_ca_cert); - setopt(s_proxy_server, "hexdump_file", "/dev/stdout"); - mg_start_thread(serve_thread_func, s_proxy_server); - - // Create, configure and start SSE server and SSE pusher threads + + // Create and configure proxy server + s_server = mg_create_server(NULL, &proxy_event_handler); + setopt(s_server, "document_root", "proxy_web_root"); + setopt(s_server, "listening_port", port); + setopt(s_server, "ssl_certificate", cert); + setopt(s_server, "ssl_ca_certificate", ca_cert); + setopt(s_server, "hexdump_file", dump); + setopt(s_server, "ssl_mitm_certs", mitm); + // Start two SSE pushing threads - // Serve SSE server in the main thread - s_sse_server = mg_create_server(NULL, &sse_handler); - setopt(s_sse_server, "listening_port", s_sse_port); - setopt(s_sse_server, "document_root", "."); mg_start_thread(sse_pusher_thread_func, (void *) "sse_pusher_thread_1"); mg_start_thread(sse_pusher_thread_func, (void *) "sse_pusher_thread_2"); - serve_thread_func(s_sse_server); + // Start serving in the main thread + printf("Starting on port %s\n", mg_get_option(s_server, "listening_port")); + while (s_received_signal == 0) { + mg_poll_server(s_server, 1000); + } printf("Existing on signal %d\n", s_received_signal); - mg_destroy_server(&s_sse_server); - mg_destroy_server(&s_proxy_server); + mg_destroy_server(&s_server); return EXIT_SUCCESS; } diff --git a/examples/proxy_web_root/app1/index.html b/examples/proxy_web_root/app1/index.html index 503c1201e..5b5c81677 100644 --- a/examples/proxy_web_root/app1/index.html +++ b/examples/proxy_web_root/app1/index.html @@ -1,9 +1,23 @@ <html> <head> <title>App1 Index</title> + <style> + img { height: 40px; } + </style> </head> <body> - <h1>This is App1 index page</h1> - <p>Image below is </p> + + <h1>App1 index page. Served locally from the the proxy server filesystem</h1> + + <p>image that references non-existent local resource. Forwarded to + the 'real' proxy target:</p> + <img src="http://cesanta.com/images/logo.png" /> + + <p>Google logo via HTTPS (external resource, served by remote host):</p> + <img src="https://www.google.ie/images/srpr/logo11w.png" /> + + <p>Same image via HTTP:</p> + <img src="http://www.google.ie/images/srpr/logo11w.png" /> + </body> </html> diff --git a/examples/proxy_web_root/app2/index.html b/examples/proxy_web_root/app2/index.html new file mode 100644 index 000000000..28d4beadc --- /dev/null +++ b/examples/proxy_web_root/app2/index.html @@ -0,0 +1,37 @@ +<html> +<head> + <title>App2 Index</title> + <meta charset="utf-8"> + <script> + window.onload = function() { + // Using secure websocket connection, wss:// + var ws = new WebSocket('wss://echo.websocket.org'); + var div = document.getElementById('events'); + ws.onmessage = function(ev) { + var el = document.createElement('div'); + el.innerHTML = 'websocket message: ' + ev.data; + div.appendChild(el); + // Keep only last 5 messages in the list + while (div.childNodes.length > 5) div.removeChild(div.firstChild); + }; + + // Send random stuff to the websocket connection periodically. + // websocket server much echo that stuff back. + window.setInterval(function() { + var d = new Date(); + ws.send(d.toString()); + }, 1000); + }; + </script> +</head> +<body> + <h1>App2 index page. Served locally from the + the proxy's filesystem.</h1> + <p> + Following div shows proxy forwarding of websocket connection, served by + ws://echo.websocket.org: + </p> + + <div id="events"></div> +</body> +</html> diff --git a/examples/proxy_web_root/index.html b/examples/proxy_web_root/index.html index 23be9c56a..d8343bf3b 100644 --- a/examples/proxy_web_root/index.html +++ b/examples/proxy_web_root/index.html @@ -1,6 +1,6 @@ <html> <head> - <title>example.com index</title> + <title> proxy index </title> <script type="text/javascript"> window.onload = function() { var es = new EventSource("/api/sse"); @@ -16,12 +16,12 @@ </script> </head> <body> - <h1>This is an example.com index page.</h1> + <h1> proxy index page.</h1> <ul> <li><a href="app1">App1</a> - App1 root</li> <li><a href="app2">App2</a> - App2 root</li> </ul> - + <h2>SSE pushes, done by separate threads at random times:</h2> <div id="events"></div> -- GitLab