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: ...@@ -8,6 +8,7 @@ items:
- { name: mg_get_http_var.md } - { name: mg_get_http_var.md }
- { name: mg_http_check_digest_auth.md } - { name: mg_http_check_digest_auth.md }
- { name: mg_http_parse_header.md } - { name: mg_http_parse_header.md }
- { name: mg_http_send_error.md }
- { name: mg_http_send_redirect.md } - { name: mg_http_send_redirect.md }
- { name: mg_http_serve_file.md } - { name: mg_http_serve_file.md }
- { name: mg_parse_http.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: | ...@@ -100,16 +100,19 @@ signature: |
/* IP ACL. By default, NULL, meaning all IPs are allowed to connect */ /* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
const char *ip_acl; const char *ip_acl;
#if MG_ENABLE_HTTP_URL_REWRITES
/* 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 * When HTTP request is received, Mongoose constructs a file name from the
* requested URI by combining `document_root` and the URI. However, if the * requested URI by combining `document_root` and the URI. However, if the
* rewrite option is used and `uri_pattern` matches requested URI, then * 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 * 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 * current working directory. It can also be an URI (http:// or https://)
* patterns, is a prefix pattern. * 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 * 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 * HOST header of the request. If they are equal, Mongoose sets document root
...@@ -123,6 +126,7 @@ signature: | ...@@ -123,6 +126,7 @@ signature: |
* automatically appended to the redirect location. * automatically appended to the redirect location.
*/ */
const char *url_rewrites; const char *url_rewrites;
#endif
/* DAV document root. If NULL, DAV requests are going to fail. */ /* DAV document root. If NULL, DAV requests are going to fail. */
const char *dav_document_root; 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) { ...@@ -1181,6 +1181,19 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
if (i < str2.len) return -1; if (i < str2.len) return -1;
return 0; 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 #ifdef MG_MODULE_LINES
#line 1 "common/sha1.c" #line 1 "common/sha1.c"
#endif #endif
...@@ -3970,6 +3983,10 @@ struct mg_http_proto_data { ...@@ -3970,6 +3983,10 @@ struct mg_http_proto_data {
}; };
static void mg_http_conn_destructor(void *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( static struct mg_http_proto_data *mg_http_get_proto_data(
struct mg_connection *c) { struct mg_connection *c) {
...@@ -5039,40 +5056,37 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) { ...@@ -5039,40 +5056,37 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) {
nc->proto_handler = mg_http_handler; nc->proto_handler = mg_http_handler;
} }
void mg_send_response_line_s(struct mg_connection *nc, int status_code, const char *mg_status_message(int status_code) {
const struct mg_str extra_headers) {
const char *status_message = "OK";
switch (status_code) { switch (status_code) {
case 206: case 206:
status_message = "Partial Content"; return "Partial Content";
break;
case 301: case 301:
status_message = "Moved"; return "Moved";
break;
case 302: case 302:
status_message = "Found"; return "Found";
break;
case 401: case 401:
status_message = "Unauthorized"; return "Unauthorized";
break;
case 403: case 403:
status_message = "Forbidden"; return "Forbidden";
break;
case 404: case 404:
status_message = "Not Found"; return "Not Found";
break;
case 416: case 416:
status_message = "Requested range not satisfiable"; return "Requested range not satisfiable";
break;
case 418: case 418:
status_message = "I'm a teapot"; return "I'm a teapot";
break;
case 500: case 500:
status_message = "Internal Server Error"; return "Internal Server Error";
break; 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) { if (extra_headers.len > 0) {
mg_printf(nc, "%.*s\r\n", (int) extra_headers.len, extra_headers.p); 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, ...@@ -5116,10 +5130,9 @@ void mg_send_head(struct mg_connection *c, int status_code,
mg_send(c, "\r\n", 2); mg_send(c, "\r\n", 2);
} }
#if MG_ENABLE_FILESYSTEM void mg_http_send_error(struct mg_connection *nc, int code,
static void mg_http_send_error(struct mg_connection *nc, int code,
const char *reason) { const char *reason) {
if (!reason) reason = ""; if (!reason) reason = mg_status_message(code);
DBG(("%p %d %s", nc, code, reason)); DBG(("%p %d %s", nc, code, reason));
mg_send_head(nc, code, strlen(reason), mg_send_head(nc, code, strlen(reason),
"Content-Type: text/plain\r\nConnection: close"); "Content-Type: text/plain\r\nConnection: close");
...@@ -5127,6 +5140,7 @@ static void mg_http_send_error(struct mg_connection *nc, int code, ...@@ -5127,6 +5140,7 @@ static void mg_http_send_error(struct mg_connection *nc, int code,
nc->flags |= MG_F_SEND_AND_CLOSE; nc->flags |= MG_F_SEND_AND_CLOSE;
} }
#if MG_ENABLE_FILESYSTEM
static void mg_http_construct_etag(char *buf, size_t buf_len, static void mg_http_construct_etag(char *buf, size_t buf_len,
const cs_stat_t *st) { const cs_stat_t *st) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime, 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, ...@@ -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 : ""))); DBG(("[%s] [%s]", path, (*index_file ? *index_file : "")));
} }
#if MG_ENABLE_HTTP_URL_REWRITES
static int mg_http_send_port_based_redirect( static int mg_http_send_port_based_redirect(
struct mg_connection *c, struct http_message *hm, struct mg_connection *c, struct http_message *hm,
const struct mg_serve_http_opts *opts) { const struct mg_serve_http_opts *opts) {
...@@ -5807,6 +5822,103 @@ static int mg_http_send_port_based_redirect( ...@@ -5807,6 +5822,103 @@ static int mg_http_send_port_based_redirect(
return 0; 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, MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
const struct mg_serve_http_opts *opts, const struct mg_serve_http_opts *opts,
char **local_path, char **local_path,
...@@ -5820,7 +5932,12 @@ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm, ...@@ -5820,7 +5932,12 @@ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
remainder->len = 0; remainder->len = 0;
{ /* 1. Determine which root to use. */ { /* 1. Determine which root to use. */
#if MG_ENABLE_HTTP_URL_REWRITES
const char *rewrites = opts->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 *hh = mg_get_http_header(hm, "Host");
struct mg_str a, b; struct mg_str a, b;
/* Check rewrites first. */ /* Check rewrites first. */
...@@ -6154,9 +6271,15 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm, ...@@ -6154,9 +6271,15 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
return; 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)) { if (mg_http_send_port_based_redirect(nc, hm, &opts)) {
return; return;
} }
#endif
if (opts.document_root == NULL) { if (opts.document_root == NULL) {
opts.document_root = "."; opts.document_root = ".";
......
...@@ -1354,6 +1354,7 @@ int mg_vcasecmp(const struct mg_str *str2, const char *str1); ...@@ -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); 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_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 #ifdef __cplusplus
} }
...@@ -1779,6 +1780,11 @@ char *strdup(const char *src); ...@@ -1779,6 +1780,11 @@ char *strdup(const char *src);
#define MG_ENABLE_MQTT 1 #define MG_ENABLE_MQTT 1
#endif #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_ */ #endif /* CS_MONGOOSE_SRC_FEATURES_H_ */
#ifdef MG_MODULE_LINES #ifdef MG_MODULE_LINES
#line 1 "mongoose/src/net.h" #line 1 "mongoose/src/net.h"
...@@ -3226,16 +3232,19 @@ struct mg_serve_http_opts { ...@@ -3226,16 +3232,19 @@ struct mg_serve_http_opts {
/* IP ACL. By default, NULL, meaning all IPs are allowed to connect */ /* IP ACL. By default, NULL, meaning all IPs are allowed to connect */
const char *ip_acl; const char *ip_acl;
#if MG_ENABLE_HTTP_URL_REWRITES
/* 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 * When HTTP request is received, Mongoose constructs a file name from the
* requested URI by combining `document_root` and the URI. However, if the * requested URI by combining `document_root` and the URI. However, if the
* rewrite option is used and `uri_pattern` matches requested URI, then * 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 * 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 * current working directory. It can also be an URI (http:// or https://)
* patterns, is a prefix pattern. * 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 * 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 * HOST header of the request. If they are equal, Mongoose sets document root
...@@ -3249,6 +3258,7 @@ struct mg_serve_http_opts { ...@@ -3249,6 +3258,7 @@ struct mg_serve_http_opts {
* automatically appended to the redirect location. * automatically appended to the redirect location.
*/ */
const char *url_rewrites; const char *url_rewrites;
#endif
/* DAV document root. If NULL, DAV requests are going to fail. */ /* DAV document root. If NULL, DAV requests are going to fail. */
const char *dav_document_root; const char *dav_document_root;
...@@ -3449,6 +3459,12 @@ void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...); ...@@ -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, void mg_send_response_line(struct mg_connection *nc, int status_code,
const char *extra_headers); 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. * Sends a redirect response.
* `status_code` should be either 301 or 302 and `location` point to the * `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