build: Remove server (#3685)

Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
This commit is contained in:
Julien Jerphanion 2024-12-11 17:05:42 +01:00 committed by GitHub
parent dd5abc39b4
commit 392c5f15c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 0 additions and 654 deletions

View File

@ -23,7 +23,6 @@
{
"cacheVariables": {
"BUILD_MICROMAMBA": "ON",
"BUILD_MICROMAMBA_SERVER": "ON",
"BUILD_STATIC": "ON"
},
"hidden": true,

View File

@ -46,11 +46,6 @@ set(
${CMAKE_CURRENT_SOURCE_DIR}/src/version.hpp
)
if(UNIX AND BUILD_MICROMAMBA_SERVER)
list(APPEND MICROMAMBA_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/server.cpp)
add_definitions(-DMICROMAMBA_SERVER)
endif()
# Targets and link
# ================

View File

@ -1,414 +0,0 @@
// Copyright (c) Alex Movsisyan
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the 'Software'), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright notice and this
// permission notice shall be included in all copies or substantial portions of the Software. THE
// SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. original
// source: https://github.com/konteck/wpp
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <vector>
#include <arpa/inet.h>
#include <fmt/format.h>
#include <limits.h>
#include <netinet/in.h>
#include <poll.h>
#include <spdlog/logger.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "mamba/core/output.hpp"
#include "mamba/core/thread_utils.hpp"
#include "mamba/util/string.hpp"
#include "version.hpp"
#define BUFSIZE 8096
#define SERVER_NAME "micromamba"
#define SERVER_VERSION UMAMBA_VERSION_STRING
namespace microserver
{
struct Request
{
std::string method;
std::string path;
std::string params;
std::string body;
std::map<std::string, std::string> headers;
std::map<std::string, std::string> query;
std::map<std::string, std::string> cookies;
};
class Response
{
public:
Response()
{
code = 200;
phrase = "OK";
type = "text/html";
body << "";
// set current date and time for "Date: " header
char buffer[100];
time_t now = time(0);
struct tm tstruct = *gmtime(&now);
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S %Z", &tstruct);
date = buffer;
}
int code;
std::string phrase;
std::string type;
std::string date;
std::stringstream body;
void send(std::string_view str)
{
body << str;
}
};
class server_exception : public std::runtime_error
{
using std::runtime_error::runtime_error;
};
using callback_function_t = std::function<void(const Request&, Response&)>;
struct Route
{
std::string path;
std::string method;
callback_function_t callback;
std::string params;
};
class Server
{
public:
Server(const spdlog::logger& logger)
: m_logger(logger)
{
}
void get(std::string, callback_function_t);
void post(std::string, callback_function_t);
void all(std::string, callback_function_t);
bool start(int port = 80);
private:
void main_loop(int port);
std::pair<std::string, std::string> parse_header(std::string_view);
void parse_headers(const std::string&, Request&, Response&);
bool match_route(Request&, Response&);
std::vector<Route> m_routes;
spdlog::logger m_logger;
};
std::pair<std::string, std::string> Server::parse_header(std::string_view header)
{
assert(header.size() >= 2);
assert(header[header.size() - 1] == '\n' && header[header.size() - 2] == '\r');
auto colon_idx = header.find(':');
if (colon_idx != std::string_view::npos)
{
std::string_view key, value;
key = header.substr(0, colon_idx);
colon_idx++;
// remove spaces
while (std::isspace(header[colon_idx]))
{
++colon_idx;
}
// remove \r\n header ending
value = header.substr(colon_idx, header.size() - colon_idx - 2);
// http headers are case insensitive!
std::string lkey = mamba::util::to_lower(key);
return std::make_pair(lkey, std::string(value));
}
return std::make_pair(std::string(), std::string(header));
}
void Server::parse_headers(const std::string& headers, Request& req, Response&)
{
// Parse request headers
int i = 0;
// parse headers into lines delimited by newlines
std::size_t delim_pos = headers.find_first_of("\n");
std::string line = headers.substr(0, delim_pos + 1);
while (line.size() > 2 && i < 10)
{
if (i++ == 0)
{
auto R = mamba::util::split(line, " ", 3);
if (R.size() != 3)
{
throw server_exception("Header split returned wrong number");
}
req.method = R[0];
req.path = R[1];
size_t pos = req.path.find('?');
// We have GET params here
if (pos != std::string::npos)
{
auto Q1 = mamba::util::split(req.path.substr(pos + 1), "&");
for (std::vector<std::string>::size_type q = 0; q < Q1.size(); q++)
{
auto Q2 = mamba::util::split(Q1[q], "=");
if (Q2.size() == 2)
{
req.query[Q2[0]] = Q2[1];
}
}
req.path = req.path.substr(0, pos);
}
}
else
{
req.headers.insert(parse_header(line));
}
std::size_t prev_pos = delim_pos;
delim_pos = headers.find_first_of("\n", prev_pos + 1);
line = headers.substr(prev_pos + 1, delim_pos - prev_pos);
}
}
void Server::get(std::string path, callback_function_t callback)
{
Route r = { path, "GET", callback, "" };
m_routes.push_back(r);
}
void Server::post(std::string path, callback_function_t callback)
{
Route r = { path, "POST", callback, "" };
m_routes.push_back(r);
}
void Server::all(std::string path, callback_function_t callback)
{
Route r = { path, "ALL", callback, "" };
m_routes.push_back(r);
}
bool Server::match_route(Request& req, Response& res)
{
for (std::vector<Route>::size_type i = 0; i < m_routes.size(); i++)
{
if (m_routes[i].path == req.path
&& (m_routes[i].method == req.method || m_routes[i].method == "ALL"))
{
req.params = m_routes[i].params;
try
{
m_routes[i].callback(req, res);
}
catch (const std::exception& e)
{
m_logger.error("Error in callback: {}", e.what());
res.code = 500;
res.body << fmt::format("Internal server error. {}", e.what());
}
return true;
}
}
return false;
}
bool wait_for_socket(int fd)
{
struct pollfd fds[1];
int ret;
fds[0].fd = fd;
fds[0].events = POLLIN;
ret = poll(fds, 1, 500);
if (ret == -1)
{
throw microserver::server_exception("ERROR on poll");
}
// There is data to read
return fds[0].revents & POLLIN;
}
void Server::main_loop(int port)
{
int newsc;
int sc = socket(AF_INET, SOCK_STREAM, 0);
if (sc < 0)
{
throw microserver::server_exception("ERROR opening socket");
}
struct sockaddr_in serv_addr, cli_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
// allow faster reuse of the address
int optval = 1;
setsockopt(sc, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
if (::bind(sc, reinterpret_cast<struct sockaddr*>(&serv_addr), sizeof(serv_addr)) != 0)
{
throw microserver::server_exception("ERROR on binding");
}
listen(sc, 5);
socklen_t clilen;
clilen = sizeof(cli_addr);
while (!mamba::is_sig_interrupted())
{
bool have_data = wait_for_socket(sc);
if (have_data)
{
std::chrono::time_point request_start = std::chrono::high_resolution_clock::now();
newsc = accept(sc, reinterpret_cast<struct sockaddr*>(&cli_addr), &clilen);
if (newsc < 0)
{
throw microserver::server_exception("ERROR on accept");
}
// handle new connection
Request req;
Response res;
static char buf[BUFSIZE + 1];
std::string content;
std::streamsize ret = read(newsc, buf, BUFSIZE);
assert(ret >= 0);
content = std::string(buf, static_cast<std::size_t>(ret));
std::size_t header_end = content.find("\r\n\r\n");
if (header_end == std::string::npos)
{
throw microserver::server_exception("ERROR on parsing headers");
}
parse_headers(content, req, res);
if (req.method == "POST")
{
std::string body = content.substr(header_end + 4, BUFSIZE - header_end - 4);
std::streamsize content_length = stoll(req.headers["content-length"]);
if (content_length - static_cast<std::streamsize>(body.size()) > 0)
{
// read the rest of the data and add to body
std::streamsize remainder = content_length
- static_cast<std::streamsize>(body.size());
while (ret && remainder > 0)
{
std::streamsize read_ret = read(newsc, buf, BUFSIZE);
body += std::string(buf, static_cast<std::size_t>(read_ret));
remainder -= read_ret;
}
}
req.body = body;
}
if (!match_route(req, res))
{
res.code = 404;
res.phrase = "Not Found";
res.type = "text/plain";
res.send("Not found");
}
std::stringstream buffer;
std::string body = res.body.str();
std::size_t body_len = body.size();
// build http response
buffer << fmt::format("HTTP/1.0 {} {}\r\n", res.code, res.phrase)
<< fmt::format("Server: {} {}\r\n", SERVER_NAME, SERVER_VERSION)
<< fmt::format("Date: {}\r\n", res.date)
<< fmt::format("Content-Type: {}\r\n", res.type)
<< fmt::format("Content-Length: {}\r\n", body_len)
// append extra crlf to indicate start of body
<< "\r\n";
char addrbuf[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &cli_addr.sin_addr, addrbuf, sizeof(addrbuf));
uint16_t used_port = htons(cli_addr.sin_port);
std::chrono::time_point request_end = std::chrono::high_resolution_clock::now();
m_logger.info(
"{}:{} - {} {} {} (took {} ms)",
addrbuf,
used_port,
req.method,
req.path,
fmt::styled(
res.code,
fmt::fg(res.code < 300 ? fmt::terminal_color::green : fmt::terminal_color::red)
),
std::chrono::duration_cast<std::chrono::milliseconds>(request_end - request_start)
.count()
);
std::string header_buffer = buffer.str();
auto written = write(newsc, header_buffer.c_str(), header_buffer.size());
if (written != static_cast<std::streamsize>(header_buffer.size()))
{
LOG_ERROR << "Could not write to socket " << strerror(errno);
continue;
}
written = write(newsc, body.c_str(), body_len);
if (written != static_cast<std::streamsize>(body_len))
{
LOG_ERROR << "Could not write to socket " << strerror(errno);
continue;
}
}
}
}
bool Server::start(int port)
{
this->main_loop(port);
return true;
}
}

View File

@ -1,224 +0,0 @@
// Server side C/C++ program to demonstrate Socket
// programming
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define PORT 8080
#include <optional>
#include <unordered_map>
#include <CLI/CLI.hpp>
#include <spdlog/sinks/stdout_color_sinks.h>
#include "mamba/api/channel_loader.hpp"
#include "mamba/api/configuration.hpp"
#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/solver.hpp"
#include "mamba/core/transaction.hpp"
#include "mamba/core/virtual_packages.hpp"
#include "mamba/util/string.hpp"
#include "common_options.hpp"
#include "microserver.cpp"
#include "umamba.hpp"
#include "version.hpp"
using namespace mamba;
Database
load_pool(
const std::vector<std::string>& channels,
MultiPackageCache& package_caches,
mamba::Context& ctx,
mamba::ChannelContext& channel_context
)
{
ctx.channels = channels;
mamba::Database pool{ ctx, channel_context };
auto exp_load = load_channels(ctx, pool, package_caches, false);
if (!exp_load)
{
throw std::runtime_error(exp_load.error().what());
}
return pool;
}
void
handle_solve_request(
const microserver::Request& req,
microserver::Response& res,
mamba::Context& ctx,
mamba::ChannelContext& channel_context
)
{
struct cache
{
std::optional<mamba::Database> pool;
std::chrono::time_point<std::chrono::system_clock> last_update;
};
static std::unordered_map<std::string, cache> cache_map;
auto j = nlohmann::json::parse(req.body);
std::vector<std::string> specs = j["specs"].get<std::vector<std::string>>();
std::vector<std::string> channels = j["channels"].get<std::vector<std::string>>();
std::vector<std::string> virtual_packages = j["virtual_packages"].get<std::vector<std::string>>();
std::string platform = j["platform"];
ctx.platform = platform;
for (const auto& s : specs)
{
if (auto m = MatchSpec::parse(s); m.channel.has_value())
{
channels.push_back(m.channel->str());
}
}
std::string cache_key = mamba::util::join(", ", channels) + fmt::format(", {}", platform);
MultiPackageCache package_caches(ctx.pkgs_dirs, ctx.validation_params);
if (cache_map.find(cache_key) == cache_map.end())
{
cache_map.insert_or_assign(
cache_key,
cache{ load_pool(channels, package_caches, ctx, channel_context),
std::chrono::system_clock::now() }
);
}
else
{
cache& c = cache_map[cache_key];
if (std::chrono::system_clock::now() - c.last_update > std::chrono::minutes(30))
{
cache_map.insert_or_assign(
cache_key,
cache{ load_pool(channels, package_caches, ctx, channel_context),
std::chrono::system_clock::now() }
);
}
}
auto entry_it = cache_map.find(cache_key);
if (entry_it == cache_map.end())
{
throw std::runtime_error("invalid cache state");
}
cache cache_entry = entry_it->second;
TemporaryDirectory tmp_dir;
auto exp_prefix_data = PrefixData::create(tmp_dir.path(), channel_context);
// if (!exp_prefix_data)
// {
// throw std::runtime_error(exp_prefix_data.error().what());
// }
PrefixData& prefix_data = exp_prefix_data.value();
std::vector<PackageInfo> vpacks;
for (const auto& s : virtual_packages)
{
auto elements = util::split(s, "=");
vpacks.push_back(detail::make_virtual_package(
elements[0],
ctx.platform,
elements.size() >= 2 ? elements[1] : "",
elements.size() >= 3 ? elements[2] : ""
));
}
prefix_data.add_packages(vpacks);
auto installed_repo = MRepo(*cache_entry.pool, prefix_data);
MSolver solver(
*cache_entry.pool,
{ { SOLVER_FLAG_ALLOW_UNINSTALL, ctx.allow_uninstall },
{ SOLVER_FLAG_ALLOW_DOWNGRADE, ctx.allow_downgrade },
{ SOLVER_FLAG_STRICT_REPO_PRIORITY, ctx.channel_priority == ChannelPriority::Strict } }
);
solver.add_jobs(specs, SOLVER_INSTALL);
bool solved = solver.try_solve();
if (!solved)
{
nlohmann::json jout;
jout["error_msg"] = solver.problems_to_str();
res.send(jout.dump());
}
else
{
MTransaction trans{ *cache_entry.pool, solver, package_caches };
auto to_install = std::get<1>(trans.to_conda());
std::vector<nlohmann::json> packages;
for (auto& p : to_install)
{
packages.push_back(nlohmann::json::parse(std::get<2>(p)));
}
nlohmann::json jout;
jout["packages"] = packages;
res.send(jout.dump());
}
cache_entry.pool->remove_repo(installed_repo.id(), /* reuse_ids= */ true);
pool_set_installed(*cache_entry.pool, nullptr);
}
int
run_server(int port, mamba::Context& ctx, mamba::ChannelContext& channel_context, Configuration& config)
{
config.load();
std::signal(SIGPIPE, SIG_IGN);
auto server_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
server_sink->set_level(spdlog::level::debug);
server_sink->set_pattern("%^[%H:%M:%S]%$ %v");
spdlog::logger logger("server", { server_sink });
microserver::Server xserver(logger);
xserver.get(
"/hello",
[](const microserver::Request&, microserver::Response& res) { res.send("Hello World!"); }
);
xserver.get(
"/",
[](const microserver::Request&, microserver::Response& res)
{
res.type = "text/plain";
std::stringstream ss;
ss << "Micromamba version " << UMAMBA_VERSION_STRING << "\n";
res.send(ss.str());
}
);
xserver.post(
"/solve",
[&](const microserver::Request& req, microserver::Response& res)
{ return handle_solve_request(req, res, ctx, channel_context); }
);
Console::stream() << "Starting server on port http://localhost:" << port << std::endl;
xserver.start(port);
return 0;
}
void
set_server_command(CLI::App* subcom, mamba::Configuration& config)
{
init_general_options(subcom, config);
static int port = 1234;
subcom->add_option("--port,-p", port, "The port to use for the server");
subcom->callback(
[&config]
{
auto channel_context = mamba::ChannelContext::make_conda_compatible(config.context());
return run_server(port, config.context(), channel_context, config);
}
);
}

View File

@ -110,10 +110,5 @@ set_umamba_command(CLI::App* com, mamba::Configuration& config)
);
set_repoquery_search_command(search_subcom, config);
#if !defined(_WIN32) && defined(MICROMAMBA_SERVER)
CLI::App* server_subcom = com->add_subcommand("server", "Run micromamba server");
set_server_command(server_subcom, config);
#endif
com->require_subcommand(/* min */ 0, /* max */ 1);
}

View File

@ -86,9 +86,4 @@ get_completions(CLI::App* app, mamba::Configuration& config, int argc, char** ar
void
set_auth_command(CLI::App* subcom);
#if !defined(_WIN32) && defined(MICROMAMBA_SERVER)
void
set_server_command(CLI::App* subcom, mamba::Configuration& config);
#endif
#endif