Commit 900bbe72 authored by Marko Mikulicic's avatar Marko Mikulicic Committed by Cesanta Bot

Mongoose forwarding

PUBLISHED_FROM=51652f0157bb951a43508f0fe948c62c351e96ba
parent f5a28576
......@@ -8,6 +8,7 @@ items:
- { name: mg_get_http_var.md }
- { name: mg_http_check_digest_auth.md }
- { name: mg_http_parse_header.md }
- { name: mg_http_send_error.md }
- { name: mg_http_send_redirect.md }
- { name: mg_http_serve_file.md }
- { name: mg_parse_http.md }
......
---
title: "mg_http_send_error()"
decl_name: "mg_http_send_error"
symbol_kind: "func"
signature: |
void mg_http_send_error(struct mg_connection *nc, int code, const char *reason);
---
Sends an error response. If reason is NULL, the message will be inferred
from the error code (if supported).
......@@ -100,16 +100,19 @@ signature: |
/* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
const char *ip_acl;
#if MG_ENABLE_HTTP_URL_REWRITES
/* URL rewrites.
*
* Comma-separated list of `uri_pattern=file_or_directory_path` rewrites.
* Comma-separated list of `uri_pattern=url_file_or_directory_path` rewrites.
* When HTTP request is received, Mongoose constructs a file name from the
* requested URI by combining `document_root` and the URI. However, if the
* rewrite option is used and `uri_pattern` matches requested URI, then
* `document_root` is ignored. Instead, `file_or_directory_path` is used,
* `document_root` is ignored. Instead, `url_file_or_directory_path` is used,
* which should be a full path name or a path relative to the web server's
* current working directory. Note that `uri_pattern`, as all Mongoose
* patterns, is a prefix pattern.
* current working directory. It can also be an URI (http:// or https://)
* in which case mongoose will behave as a reverse proxy for that destination.
*
* Note that `uri_pattern`, as all Mongoose patterns, is a prefix pattern.
*
* If uri_pattern starts with `@` symbol, then Mongoose compares it with the
* HOST header of the request. If they are equal, Mongoose sets document root
......@@ -123,6 +126,7 @@ signature: |
* automatically appended to the redirect location.
*/
const char *url_rewrites;
#endif
/* DAV document root. If NULL, DAV requests are going to fail. */
const char *dav_document_root;
......
PROG = reverse_proxy
MODULE_CFLAGS =
SSL_LIB =
include ../examples.mk
<!DOCTYPE html>
<html>
<body>
Hello
</body>
</html>
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* This example shows how mongoose can be used as a reverse
* proxy for another http server.
*
* A common setup is to have a frontend web server that delegates
* some urls to a backend web server.
*
* In this example we create two webservers. The frontend listens on port
* 8000 and servers a static file and forwards any call matching the /api prefix
* to the backend.
*
* The backend listens on port 8001 and replies a simple JSON object which
* shows the request URI that the backend http server receives.
*
* Things to try out:
*
* curl http://localhost:8000/
* curl http://localhost:8000/api
* curl http://localhost:8000/api/foo
* curl http://localhost:8001/foo
*
* The reverse proxy functionality is enabled via the url rewrite functionality:
*
* ```
* s_frontend_server_opts.url_rewrites =
* "/api=http://localhost:8001,/=frontend/hello.html";
* ```
*
* This example maps the /api to a remote http server, and / to a
* specific file on the filesystem.
*
* Obviously you can use any http server as the backend, we spawn
* another web server from the same process in order to make the example easy
* to run.
*/
#include "../../mongoose.h"
static const char *s_frontend_port = "8000";
static struct mg_serve_http_opts s_frontend_server_opts;
static const char *s_backend_port = "8001";
static struct mg_serve_http_opts s_backend_server_opts;
static void frontend_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct http_message *hm = (struct http_message *) ev_data;
switch (ev) {
case MG_EV_HTTP_REQUEST:
mg_serve_http(nc, hm, s_frontend_server_opts); /* Serve static content */
break;
default:
break;
}
}
static void backend_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct http_message *hm = (struct http_message *) ev_data;
int i;
switch (ev) {
case MG_EV_HTTP_REQUEST:
mg_send_response_line(nc, 200,
"Content-Type: text/html\r\n"
"Connection: close\r\n");
mg_printf(nc,
"{\"uri\": \"%.*s\", \"method\": \"%.*s\", \"body\": \"%.*s\", "
"\"headers\": {",
(int) hm->uri.len, hm->uri.p, (int) hm->method.len,
hm->method.p, (int) hm->body.len, hm->body.p);
for (i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
struct mg_str hn = hm->header_names[i];
struct mg_str hv = hm->header_values[i];
mg_printf(nc, "%s\"%.*s\": \"%.*s\"", (i != 0 ? "," : ""), (int) hn.len,
hn.p, (int) hv.len, hv.p);
}
mg_printf(nc, "}}");
nc->flags |= MG_F_SEND_AND_CLOSE;
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
struct mg_connection *nc;
int i;
/* Open listening socket */
mg_mgr_init(&mgr, NULL);
/* configure frontend web server */
nc = mg_bind(&mgr, s_frontend_port, frontend_handler);
mg_set_protocol_http_websocket(nc);
s_frontend_server_opts.document_root = "frontend";
s_frontend_server_opts.url_rewrites =
"/api=http://localhost:8001,/=frontend/hello.html";
/* configure backend web server */
nc = mg_bind(&mgr, s_backend_port, backend_handler);
mg_set_protocol_http_websocket(nc);
s_backend_server_opts.document_root = "backend";
/* Parse command line arguments */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-D") == 0) {
mgr.hexdump_file = argv[++i];
} else if (strcmp(argv[i], "-r") == 0) {
s_frontend_server_opts.document_root = argv[++i];
}
}
printf("Starting web server on port %s\n", s_frontend_port);
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
}
......@@ -1181,6 +1181,19 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
if (i < str2.len) return -1;
return 0;
}
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
struct mg_str s1 = str1;
struct mg_str s2 = str2;
if (s1.len > n) {
s1.len = n;
}
if (s2.len > n) {
s2.len = n;
}
return mg_strcmp(s1, s2);
}
#ifdef MG_MODULE_LINES
#line 1 "common/sha1.c"
#endif
......@@ -3970,6 +3983,10 @@ struct mg_http_proto_data {
};
static void mg_http_conn_destructor(void *proto_data);
struct mg_connection *mg_connect_http_base(
struct mg_mgr *mgr, mg_event_handler_t ev_handler,
struct mg_connect_opts opts, const char *schema, const char *schema_ssl,
const char *url, const char **path, char **addr);
static struct mg_http_proto_data *mg_http_get_proto_data(
struct mg_connection *c) {
......@@ -5039,40 +5056,37 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) {
nc->proto_handler = mg_http_handler;
}
void mg_send_response_line_s(struct mg_connection *nc, int status_code,
const struct mg_str extra_headers) {
const char *status_message = "OK";
const char *mg_status_message(int status_code) {
switch (status_code) {
case 206:
status_message = "Partial Content";
break;
return "Partial Content";
case 301:
status_message = "Moved";
break;
return "Moved";
case 302:
status_message = "Found";
break;
return "Found";
case 401:
status_message = "Unauthorized";
break;
return "Unauthorized";
case 403:
status_message = "Forbidden";
break;
return "Forbidden";
case 404:
status_message = "Not Found";
break;
return "Not Found";
case 416:
status_message = "Requested range not satisfiable";
break;
return "Requested range not satisfiable";
case 418:
status_message = "I'm a teapot";
break;
return "I'm a teapot";
case 500:
status_message = "Internal Server Error";
break;
return "Internal Server Error";
case 502:
return "Bad Gateway";
default:
return "OK";
}
mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code, status_message,
mg_version_header);
}
void mg_send_response_line_s(struct mg_connection *nc, int status_code,
const struct mg_str extra_headers) {
mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code,
mg_status_message(status_code), mg_version_header);
if (extra_headers.len > 0) {
mg_printf(nc, "%.*s\r\n", (int) extra_headers.len, extra_headers.p);
}
......@@ -5116,10 +5130,9 @@ void mg_send_head(struct mg_connection *c, int status_code,
mg_send(c, "\r\n", 2);
}
#if MG_ENABLE_FILESYSTEM
static void mg_http_send_error(struct mg_connection *nc, int code,
void mg_http_send_error(struct mg_connection *nc, int code,
const char *reason) {
if (!reason) reason = "";
if (!reason) reason = mg_status_message(code);
DBG(("%p %d %s", nc, code, reason));
mg_send_head(nc, code, strlen(reason),
"Content-Type: text/plain\r\nConnection: close");
......@@ -5127,6 +5140,7 @@ static void mg_http_send_error(struct mg_connection *nc, int code,
nc->flags |= MG_F_SEND_AND_CLOSE;
}
#if MG_ENABLE_FILESYSTEM
static void mg_http_construct_etag(char *buf, size_t buf_len,
const cs_stat_t *st) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime,
......@@ -5784,6 +5798,7 @@ MG_INTERNAL void mg_find_index_file(const char *path, const char *list,
DBG(("[%s] [%s]", path, (*index_file ? *index_file : "")));
}
#if MG_ENABLE_HTTP_URL_REWRITES
static int mg_http_send_port_based_redirect(
struct mg_connection *c, struct http_message *hm,
const struct mg_serve_http_opts *opts) {
......@@ -5807,6 +5822,103 @@ static int mg_http_send_port_based_redirect(
return 0;
}
static void mg_reverse_proxy_handler(struct mg_connection *nc, int ev,
void *ev_data) {
struct http_message *hm = (struct http_message *) ev_data;
struct mg_connection *upstream = (struct mg_connection *) nc->user_data;
switch (ev) {
case MG_EV_CONNECT:
if (*(int *) ev_data != 0) {
mg_http_send_error(upstream, 502, NULL);
}
break;
/* TODO(mkm): handle streaming */
case MG_EV_HTTP_REPLY:
mg_send(upstream, hm->message.p, hm->message.len);
upstream->flags |= MG_F_SEND_AND_CLOSE;
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
break;
case MG_EV_CLOSE:
upstream->flags |= MG_F_SEND_AND_CLOSE;
break;
}
}
void mg_handle_reverse_proxy(struct mg_connection *nc, struct http_message *hm,
struct mg_str mount, struct mg_str upstream) {
struct mg_connection *be;
char burl[256], *purl = burl;
char *addr = NULL;
const char *path = NULL;
int i;
struct mg_connect_opts opts;
memset(&opts, 0, sizeof(opts));
mg_asprintf(&purl, sizeof(burl), "%.*s%.*s", (int) upstream.len, upstream.p,
(int) (hm->uri.len - mount.len), hm->uri.p + mount.len);
be = mg_connect_http_base(nc->mgr, mg_reverse_proxy_handler, opts, "http://",
"https://", purl, &path, &addr);
DBG(("Proxying %.*s to %s (rule: %.*s)", (int) hm->uri.len, hm->uri.p, purl,
(int) mount.len, mount.p));
if (be == NULL) {
mg_http_send_error(nc, 502, NULL);
goto cleanup;
}
be->user_data = nc;
/* send request upstream */
mg_printf(be, "%.*s %s HTTP/1.1\r\n", (int) hm->method.len, hm->method.p,
path);
mg_printf(be, "Host: %s\r\n", addr);
for (i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
struct mg_str hn = hm->header_names[i];
struct mg_str hv = hm->header_values[i];
/* we rewrite the host header */
if (mg_vcasecmp(&hn, "Host") == 0) continue;
/*
* Don't pass chunked transfer encoding to the client because hm->body is
* already dechunked when we arrive here.
*/
if (mg_vcasecmp(&hn, "Transfer-encoding") == 0 &&
mg_vcasecmp(&hv, "chunked") == 0) {
mg_printf(be, "Content-Length: %" SIZE_T_FMT "\r\n", hm->body.len);
continue;
}
mg_printf(be, "%.*s: %.*s\r\n", (int) hn.len, hn.p, (int) hv.len, hv.p);
}
mg_send(be, "\r\n", 2);
mg_send(be, hm->body.p, hm->body.len);
cleanup:
if (purl != burl) MG_FREE(purl);
}
static int mg_http_handle_forwarding(struct mg_connection *nc,
struct http_message *hm,
const struct mg_serve_http_opts *opts) {
const char *rewrites = opts->url_rewrites;
struct mg_str a, b;
struct mg_str p1 = MG_MK_STR("http://"), p2 = MG_MK_STR("https://");
while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
if (mg_strncmp(a, hm->uri, a.len) == 0) {
if (mg_strncmp(b, p1, p1.len) == 0 || mg_strncmp(b, p2, p2.len) == 0) {
mg_handle_reverse_proxy(nc, hm, a, b);
return 1;
}
}
}
return 0;
}
#endif
MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
const struct mg_serve_http_opts *opts,
char **local_path,
......@@ -5820,7 +5932,12 @@ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
remainder->len = 0;
{ /* 1. Determine which root to use. */
#if MG_ENABLE_HTTP_URL_REWRITES
const char *rewrites = opts->url_rewrites;
#else
const char *rewrites = "";
#endif
struct mg_str *hh = mg_get_http_header(hm, "Host");
struct mg_str a, b;
/* Check rewrites first. */
......@@ -6154,9 +6271,15 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
return;
}
#if MG_ENABLE_HTTP_URL_REWRITES
if (mg_http_handle_forwarding(nc, hm, &opts)) {
return;
}
if (mg_http_send_port_based_redirect(nc, hm, &opts)) {
return;
}
#endif
if (opts.document_root == NULL) {
opts.document_root = ".";
......
......@@ -1354,6 +1354,7 @@ int mg_vcasecmp(const struct mg_str *str2, const char *str1);
struct mg_str mg_strdup(const struct mg_str s);
int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
#ifdef __cplusplus
}
......@@ -1779,6 +1780,11 @@ char *strdup(const char *src);
#define MG_ENABLE_MQTT 1
#endif
#ifndef MG_ENABLE_HTTP_URL_REWRITES
#define MG_ENABLE_HTTP_URL_REWRITES \
(CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX)
#endif
#endif /* CS_MONGOOSE_SRC_FEATURES_H_ */
#ifdef MG_MODULE_LINES
#line 1 "mongoose/src/net.h"
......@@ -3226,16 +3232,19 @@ struct mg_serve_http_opts {
/* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
const char *ip_acl;
#if MG_ENABLE_HTTP_URL_REWRITES
/* URL rewrites.
*
* Comma-separated list of `uri_pattern=file_or_directory_path` rewrites.
* Comma-separated list of `uri_pattern=url_file_or_directory_path` rewrites.
* When HTTP request is received, Mongoose constructs a file name from the
* requested URI by combining `document_root` and the URI. However, if the
* rewrite option is used and `uri_pattern` matches requested URI, then
* `document_root` is ignored. Instead, `file_or_directory_path` is used,
* `document_root` is ignored. Instead, `url_file_or_directory_path` is used,
* which should be a full path name or a path relative to the web server's
* current working directory. Note that `uri_pattern`, as all Mongoose
* patterns, is a prefix pattern.
* current working directory. It can also be an URI (http:// or https://)
* in which case mongoose will behave as a reverse proxy for that destination.
*
* Note that `uri_pattern`, as all Mongoose patterns, is a prefix pattern.
*
* If uri_pattern starts with `@` symbol, then Mongoose compares it with the
* HOST header of the request. If they are equal, Mongoose sets document root
......@@ -3249,6 +3258,7 @@ struct mg_serve_http_opts {
* automatically appended to the redirect location.
*/
const char *url_rewrites;
#endif
/* DAV document root. If NULL, DAV requests are going to fail. */
const char *dav_document_root;
......@@ -3449,6 +3459,12 @@ void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
void mg_send_response_line(struct mg_connection *nc, int status_code,
const char *extra_headers);
/*
* Sends an error response. If reason is NULL, the message will be inferred
* from the error code (if supported).
*/
void mg_http_send_error(struct mg_connection *nc, int code, const char *reason);
/*
* Sends a redirect response.
* `status_code` should be either 301 or 302 and `location` point to the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment