mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2024-12-18 20:47:53 +00:00
feat: static file server
this lets you host web apps out of :9993/app/{app_name} :9993/app/{other_app} from $ZT_HOME/app/{app_name}
This commit is contained in:
parent
6be0e67a5c
commit
b4eb39fb16
20
README.md
20
README.md
@ -175,3 +175,23 @@ Metrics are also available on disk in ZeroTier's working directory:
|
|||||||
| zt_peer_packet_errors | node_id | Counter | number of incoming packet errors from a peer |
|
| zt_peer_packet_errors | node_id | Counter | number of incoming packet errors from a peer |
|
||||||
|
|
||||||
If there are other metrics you'd like to see tracked, ask us in an Issue or send us a Pull Request!
|
If there are other metrics you'd like to see tracked, ask us in an Issue or send us a Pull Request!
|
||||||
|
|
||||||
|
### HTTP / App server
|
||||||
|
|
||||||
|
There is a static http file server suitable for hosting Single Page Apps at http://localhost:9993/app/<app-path>
|
||||||
|
|
||||||
|
Use `zerotier-cli info -j` to find your zerotier-one service's homeDir
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
cd $ZT_HOME
|
||||||
|
sudo mkdir -p app/app1
|
||||||
|
sudo mkdir -p app/appB
|
||||||
|
echo '<html><meta charset=utf-8><title>appA</title><body><h1>hello world A' | sudo tee app/appA/index.html
|
||||||
|
echo '<html><meta charset=utf-8><title>app2</title><body><h1>hello world 2' | sudo tee app/app2/index.html
|
||||||
|
curl -sL http://localhost:9993/app/appA http://localhost:9993/app/app2
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit [http://localhost:9993/app/app1/](http://localhost:9993/app/app1/) and [http://localhost:9993/app/appB/](http://localhost:9993/app/appB/)
|
||||||
|
|
||||||
|
Requests to paths don't exist return the app root index.html, as is customary for SPAs.
|
||||||
|
If you want, you can write some javascript that talks to the service or controller [api](https://docs.zerotier.com/service/v1).
|
||||||
|
@ -876,6 +876,7 @@ void EmbeddedNetworkController::configureHTTPControlPlane(
|
|||||||
std::string memberListPath2 = "/unstable/controller/network/([0-9a-fA-F]{16})/member";
|
std::string memberListPath2 = "/unstable/controller/network/([0-9a-fA-F]{16})/member";
|
||||||
std::string memberPath = "/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})";
|
std::string memberPath = "/controller/network/([0-9a-fA-F]{16})/member/([0-9a-fA-F]{10})";
|
||||||
|
|
||||||
|
|
||||||
auto controllerGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
|
auto controllerGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
|
||||||
char tmp[4096];
|
char tmp[4096];
|
||||||
const bool dbOk = _db.isReady();
|
const bool dbOk = _db.isReady();
|
||||||
@ -887,11 +888,11 @@ void EmbeddedNetworkController::configureHTTPControlPlane(
|
|||||||
(unsigned long long)OSUtils::now(),
|
(unsigned long long)OSUtils::now(),
|
||||||
dbOk ? "true" : "false");
|
dbOk ? "true" : "false");
|
||||||
|
|
||||||
if (!dbOk) {
|
if (!dbOk) {
|
||||||
res.status = 503;
|
res.status = 503;
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent(req, res, tmp);
|
setContent(req, res, tmp);
|
||||||
};
|
};
|
||||||
s.Get(controllerPath, controllerGet);
|
s.Get(controllerPath, controllerGet);
|
||||||
sv6.Get(controllerPath, controllerGet);
|
sv6.Get(controllerPath, controllerGet);
|
||||||
|
@ -796,6 +796,7 @@ public:
|
|||||||
bool _allowTcpFallbackRelay;
|
bool _allowTcpFallbackRelay;
|
||||||
bool _forceTcpRelay;
|
bool _forceTcpRelay;
|
||||||
bool _allowSecondaryPort;
|
bool _allowSecondaryPort;
|
||||||
|
bool _enableWebServer;
|
||||||
|
|
||||||
unsigned int _primaryPort;
|
unsigned int _primaryPort;
|
||||||
unsigned int _secondaryPort;
|
unsigned int _secondaryPort;
|
||||||
@ -1558,6 +1559,7 @@ public:
|
|||||||
|
|
||||||
std::vector<std::string> noAuthEndpoints { "/sso", "/health" };
|
std::vector<std::string> noAuthEndpoints { "/sso", "/health" };
|
||||||
|
|
||||||
|
|
||||||
auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) {
|
auto setContent = [=] (const httplib::Request &req, httplib::Response &res, std::string content) {
|
||||||
if (req.has_param("jsonp")) {
|
if (req.has_param("jsonp")) {
|
||||||
if (content.length() > 0) {
|
if (content.length() > 0) {
|
||||||
@ -1574,8 +1576,98 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// static file server for app ui'
|
||||||
|
//
|
||||||
|
if (_enableWebServer) {
|
||||||
|
static std::string appUiPath = "/app";
|
||||||
|
static char appUiDir[16384];
|
||||||
|
sprintf(appUiDir,"%s%s",_homePath.c_str(),appUiPath.c_str());
|
||||||
|
|
||||||
auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) {
|
auto ret = _controlPlane.set_mount_point(appUiPath, appUiDir);
|
||||||
|
_controlPlaneV6.set_mount_point(appUiPath, appUiDir);
|
||||||
|
if (!ret) {
|
||||||
|
fprintf(stderr, "Mounting app directory failed. Creating it. Path: %s - Dir: %s\n", appUiPath.c_str(), appUiDir);
|
||||||
|
if (!OSUtils::mkdir(appUiDir)) {
|
||||||
|
fprintf(stderr, "Could not create app directory either. Path: %s - Dir: %s\n", appUiPath.c_str(), appUiDir);
|
||||||
|
} else {
|
||||||
|
ret = _controlPlane.set_mount_point(appUiPath, appUiDir);
|
||||||
|
_controlPlaneV6.set_mount_point(appUiPath, appUiDir);
|
||||||
|
if (!ret) {
|
||||||
|
fprintf(stderr, "Really could not create and mount directory. Path: %s - Dir: %s\nWeb apps won't work.\n", appUiPath.c_str(), appUiDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
// fallback to /index.html for paths that don't exist for SPAs
|
||||||
|
auto indexFallbackGet = [](const httplib::Request &req, httplib::Response &res) {
|
||||||
|
// fprintf(stderr, "fallback \n");
|
||||||
|
|
||||||
|
auto match = req.matches[1];
|
||||||
|
if (match.matched) {
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
char indexHtmlPath[16384];
|
||||||
|
sprintf(indexHtmlPath,"%s/%s/%s", appUiDir, match.str().c_str(), "index.html");
|
||||||
|
// fprintf(stderr, "fallback path %s\n", indexHtmlPath);
|
||||||
|
|
||||||
|
std::string indexHtml;
|
||||||
|
|
||||||
|
if (!OSUtils::readFile(indexHtmlPath, indexHtml)) {
|
||||||
|
res.status = 500;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set_content(indexHtml.c_str(), "text/html");
|
||||||
|
} else {
|
||||||
|
res.status = 500;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto slashRedirect = [](const httplib::Request &req, httplib::Response &res) {
|
||||||
|
// fprintf(stderr, "redirect \n");
|
||||||
|
|
||||||
|
// add .html
|
||||||
|
std::string htmlFile;
|
||||||
|
char htmlPath[16384];
|
||||||
|
sprintf(htmlPath,"%s%s%s", appUiDir, (req.path).substr(appUiPath.length()).c_str(), ".html");
|
||||||
|
// fprintf(stderr, "path: %s\n", htmlPath);
|
||||||
|
if (OSUtils::readFile(htmlPath, htmlFile)) {
|
||||||
|
res.set_content(htmlFile.c_str(), "text/html");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
res.status = 301;
|
||||||
|
res.set_header("location", req.path + "/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// auto missingAssetGet = [&, setContent](const httplib::Request &req, httplib::Response &res) {
|
||||||
|
// fprintf(stderr, "missing \n");
|
||||||
|
// res.status = 404;
|
||||||
|
// std::string html = "oops";
|
||||||
|
// res.set_content(html, "text/plain");
|
||||||
|
// res.set_header("Content-Type", "text/plain");
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// auto fix no trailing slash by adding .html or redirecting to path/
|
||||||
|
_controlPlane.Get(appUiPath + R"((/[\w|-]+)+$)", slashRedirect);
|
||||||
|
_controlPlaneV6.Get(appUiPath + R"((/[\w|-]+)+$)", slashRedirect);
|
||||||
|
|
||||||
|
// // 404 missing assets for *.ext paths
|
||||||
|
// s.Get(appUiPath + R"(/\.\w+$)", missingAssetGet);
|
||||||
|
// sv6.Get(appUiPath + R"(/\.\w+$)", missingAssetGet);
|
||||||
|
|
||||||
|
// fallback to index.html for unknown paths/files
|
||||||
|
_controlPlane.Get(appUiPath + R"((/[\w|-]+)(/[\w|-]+)*/$)", indexFallbackGet);
|
||||||
|
_controlPlaneV6.Get(appUiPath + R"((/[\w|-]+)(/[\w|-]+)*/$)", indexFallbackGet);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto authCheck = [=] (const httplib::Request &req, httplib::Response &res) {
|
||||||
if (req.path == "/metrics") {
|
if (req.path == "/metrics") {
|
||||||
|
|
||||||
if (req.has_header("x-zt1-auth")) {
|
if (req.has_header("x-zt1-auth")) {
|
||||||
@ -1625,6 +1717,11 @@ public:
|
|||||||
isAuth = true;
|
isAuth = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web Apps base path
|
||||||
|
if (req.path.rfind("/app", 0) == 0) { //starts with /app
|
||||||
|
isAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
// check auth token
|
// check auth token
|
||||||
if (req.has_header("x-zt1-auth")) {
|
if (req.has_header("x-zt1-auth")) {
|
||||||
@ -2452,6 +2549,7 @@ public:
|
|||||||
}
|
}
|
||||||
_allowTcpFallbackRelay = (OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true) && !_node->bondController()->inUse());
|
_allowTcpFallbackRelay = (OSUtils::jsonBool(settings["allowTcpFallbackRelay"],true) && !_node->bondController()->inUse());
|
||||||
_forceTcpRelay = (_forceTcpRelayTmp && !_node->bondController()->inUse());
|
_forceTcpRelay = (_forceTcpRelayTmp && !_node->bondController()->inUse());
|
||||||
|
_enableWebServer = (OSUtils::jsonBool(settings["enableWebServer"],false));
|
||||||
|
|
||||||
#ifdef ZT_TCP_FALLBACK_RELAY
|
#ifdef ZT_TCP_FALLBACK_RELAY
|
||||||
_fallbackRelayAddress = InetAddress(OSUtils::jsonString(settings["tcpFallbackRelay"], ZT_TCP_FALLBACK_RELAY).c_str());
|
_fallbackRelayAddress = InetAddress(OSUtils::jsonString(settings["tcpFallbackRelay"], ZT_TCP_FALLBACK_RELAY).c_str());
|
||||||
|
Loading…
Reference in New Issue
Block a user