Skip to content
Snippets Groups Projects
Commit 3671b263 authored by Daniel O'Connell's avatar Daniel O'Connell
Browse files

proxy unit test reference implementation

parent 25115a69
No related branches found
No related tags found
No related merge requests found
...@@ -6,18 +6,18 @@ ...@@ -6,18 +6,18 @@
// git clone https://github.com/cesanta/mongoose.git // git clone https://github.com/cesanta/mongoose.git
// cd mongoose/examples // cd mongoose/examples
// make proxy // 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 "net_skeleton.h"
#include "mongoose.h" #include "mongoose.h"
static int s_received_signal = 0; static int s_received_signal = 0;
static const char *s_ca_cert = NULL; static struct mg_server *s_server = NULL;
static const char *s_cert = NULL;
static const char *s_sse_port = "2014"; #define SSE_CONNECTION ((void *) 1)
static const char *s_proxy_port = "2015";
static struct mg_server *s_sse_server = NULL;
static struct mg_server *s_proxy_server = NULL;
static void elog(int do_exit, const char *fmt, ...) { static void elog(int do_exit, const char *fmt, ...) {
va_list ap; va_list ap;
...@@ -34,8 +34,7 @@ static void signal_handler(int sig_num) { ...@@ -34,8 +34,7 @@ static void signal_handler(int sig_num) {
} }
static int sse_push(struct mg_connection *conn, enum mg_event ev) { static int sse_push(struct mg_connection *conn, enum mg_event ev) {
const char *str = (const char *) conn->connection_param; if (ev == MG_POLL && conn->connection_param == SSE_CONNECTION) {
if (ev == MG_POLL && str != NULL && strcmp(str, "sse") == 0) {
mg_printf(conn, "data: %s\r\n\r\n", (const char *) conn->callback_param); mg_printf(conn, "data: %s\r\n\r\n", (const char *) conn->callback_param);
} }
return MG_TRUE; return MG_TRUE;
...@@ -43,57 +42,99 @@ static int sse_push(struct mg_connection *conn, enum mg_event ev) { ...@@ -43,57 +42,99 @@ static int sse_push(struct mg_connection *conn, enum mg_event ev) {
static void *sse_pusher_thread_func(void *param) { static void *sse_pusher_thread_func(void *param) {
while (s_received_signal == 0) { 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); (unsigned long) time(NULL), (const char *) param);
sleep(1); sleep(1);
} }
return NULL; 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) { switch (ev) {
case MG_REQUEST: case MG_REQUEST:
if (strcmp(conn->uri, "/") == 0) { host = mg_get_header(conn, "Host");
mg_printf(conn, "%s", "HTTP/1.1 302 Moved\r\n" printf("[%s] [%s] [%s]\n", conn->request_method, conn->uri,
"Location: /sse.html\r\n\r\n"); host == NULL ? "" : host);
return MG_TRUE; 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) { if (strcmp(conn->uri, "/api/sse") == 0) {
conn->connection_param = (void *) "sse"; conn->connection_param = SSE_CONNECTION;
mg_printf(conn, "%s", mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n"
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/event-stream\r\n" "Content-Type: text/event-stream\r\n"
"Cache-Control: no-cache\r\n" "Cache-Control: no-cache\r\n\r\n");
"\r\n");
return MG_MORE; return MG_MORE;
} }
if (host != NULL && strstr(host, "cesanta") != NULL) {
return try_to_serve_locally(conn);
}
return MG_FALSE; return MG_FALSE;
case MG_AUTH: case MG_AUTH:
return MG_TRUE; return MG_TRUE;
case MG_POLL:
return MG_FALSE;
default: default:
return MG_FALSE; 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) { static void setopt(struct mg_server *s, const char *opt, const char *val) {
const char *err_msg = mg_set_option(s, opt, val); const char *err_msg = mg_set_option(s, opt, val);
if (err_msg != NULL) { if (err_msg != NULL) {
...@@ -102,47 +143,50 @@ static void setopt(struct mg_server *s, const char *opt, const char *val) { ...@@ -102,47 +143,50 @@ static void setopt(struct mg_server *s, const char *opt, const char *val) {
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
const char *cert = NULL, *ca_cert = NULL, *port = "2014";
const char *mitm = NULL, *dump = NULL;
int i; int i;
// Parse command line options // Parse command line options
for (i = 1; i < argc; i++) { for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-ca_cert") == 0 && i + 1 < argc) { 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) { } else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) {
s_cert = argv[++i]; cert = argv[++i];
} else if (strcmp(argv[i], "-sse_port") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "-port") == 0 && i + 1 < argc) {
s_sse_port = argv[++i]; port = argv[++i];
} else if (strcmp(argv[i], "-proxy_port") == 0 && i + 1 < argc) { } else if (strcmp(argv[i], "-mitm") == 0 && i + 1 < argc) {
s_proxy_port = argv[++i]; mitm = argv[++i];
} else if (strcmp(argv[i], "-dump") == 0 && i + 1 < argc) {
dump = argv[++i];
} else { } 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(SIGTERM, signal_handler);
signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
// Create, configure and start proxy server in a separate thread // Create and configure proxy server
s_proxy_server = mg_create_server(NULL, &sse_handler); s_server = mg_create_server(NULL, &proxy_event_handler);
setopt(s_proxy_server, "listening_port", s_proxy_port); setopt(s_server, "document_root", "proxy_web_root");
setopt(s_proxy_server, "ssl_certificate", s_cert); setopt(s_server, "listening_port", port);
setopt(s_proxy_server, "ssl_ca_certificate", s_ca_cert); setopt(s_server, "ssl_certificate", cert);
setopt(s_proxy_server, "hexdump_file", "/dev/stdout"); setopt(s_server, "ssl_ca_certificate", ca_cert);
mg_start_thread(serve_thread_func, s_proxy_server); setopt(s_server, "hexdump_file", dump);
setopt(s_server, "ssl_mitm_certs", mitm);
// Create, configure and start SSE server and SSE pusher threads
// Start two SSE pushing threads // 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_1");
mg_start_thread(sse_pusher_thread_func, (void *) "sse_pusher_thread_2"); 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); printf("Existing on signal %d\n", s_received_signal);
mg_destroy_server(&s_sse_server); mg_destroy_server(&s_server);
mg_destroy_server(&s_proxy_server);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
<html> <html>
<head> <head>
<title>App1 Index</title> <title>App1 Index</title>
<style>
img { height: 40px; }
</style>
</head> </head>
<body> <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> </body>
</html> </html>
<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>
<html> <html>
<head> <head>
<title>example.com index</title> <title> proxy index </title>
<script type="text/javascript"> <script type="text/javascript">
window.onload = function() { window.onload = function() {
var es = new EventSource("/api/sse"); var es = new EventSource("/api/sse");
...@@ -16,12 +16,12 @@ ...@@ -16,12 +16,12 @@
</script> </script>
</head> </head>
<body> <body>
<h1>This is an example.com index page.</h1> <h1> proxy index page.</h1>
<ul> <ul>
<li><a href="app1">App1</a> - App1 root</li> <li><a href="app1">App1</a> - App1 root</li>
<li><a href="app2">App2</a> - App2 root</li> <li><a href="app2">App2</a> - App2 root</li>
</ul> </ul>
<h2>SSE pushes, done by separate threads at random times:</h2> <h2>SSE pushes, done by separate threads at random times:</h2>
<div id="events"></div> <div id="events"></div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment