in src/visualization/server/server.cpp [113:336]
void handle_request(
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send,
const WebServer::plot_map& plots,
const WebServer::table_vector& tables) {
// Returns a bad request response
auto const bad_request =
[&req](boost::beast::string_view why)
{
http::response<http::string_body> res{http::status::bad_request, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = why.to_string();
res.prepare_payload();
return res;
};
// Returns a not found response
auto const not_found =
[&req](boost::beast::string_view target)
{
http::response<http::string_body> res{http::status::not_found, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "The resource '" + target.to_string() + "' was not found.";
res.prepare_payload();
return res;
};
// Returns a server error response
auto const server_error =
[&req, &send](boost::beast::string_view what)
{
http::response<http::string_body> res{http::status::internal_server_error, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "An error occurred: '" + what.to_string() + "'";
res.prepare_payload();
return send(std::move(res));
};
// Make sure we can handle the method=
if( req.method() != http::verb::get &&
req.method() != http::verb::post &&
req.method() != http::verb::head)
return send(bad_request("Unknown HTTP-method"));
auto const respond =
[&req, &send, &server_error](const std::string& body, const std::string& content_type) {
if(req.method() == http::verb::head) {
http::response<http::empty_body> res{http::status::ok, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, content_type);
res.content_length(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
} else if(req.method() == http::verb::get || req.method() == http::verb::post) {
http::response<http::string_body> res{
std::piecewise_construct,
std::make_tuple(body),
std::make_tuple(http::status::ok, req.version())};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, content_type);
res.content_length(body.size());
res.keep_alive(req.keep_alive());
return send(std::move(res));
} else {
return server_error("Unexpected request method. Expected either HEAD or GET.");
}
};
try {
auto req_target = req.target();
if (req_target.find("/spec/plot/") == 0) {
const size_t spec_url_length = sizeof("/spec/plot/") - 1;
std::string plot_id = req_target.substr(spec_url_length).to_string();
if (plots.find(plot_id) == plots.end()) {
return server_error("Expected plot " + plot_id + " was not found");
}
const Plot& plot = plots.at(plot_id);
// For now, force light mode, until we have dark mode
// support for all visualizations and the web app itself.
std::string plot_spec = plot.get_spec(tc_plot_color_light);
std::stringstream ss;
ss << "{\"type\":\"vega\",\"data\":";
ss << plot_spec;
ss << "}";
return respond(ss.str(), "application/json");
}
if (req_target.find("/spec/table/") == 0) {
const size_t spec_url_length = sizeof("/spec/table/") - 1;
std::string table_id = req_target.substr(spec_url_length).to_string();
size_t idx = std::stoul(table_id);
if (idx >= tables.size()) {
return server_error("Expected table " + table_id + " was not found");
}
const WebServer::table& container = tables[idx];
const std::shared_ptr<turi::unity_sframe>& table = container.sf;
std::stringstream ss;
ss << "{\"type\":\"table\",\"data\":";
ss << table_spec(table, container.title, table_id);
ss << "}";
return respond(ss.str(), "application/json");
}
if (req_target.find("/data/plot/") == 0) {
const size_t data_url_length = sizeof("/data/plot/") - 1;
std::string plot_id = req_target.substr(data_url_length).to_string();
if (plots.find(plot_id) == plots.end()) {
return server_error("Expected plot " + plot_id + " was not found");
}
const Plot& plot = plots.at(plot_id);
std::stringstream plot_data;
plot_data << "{\"data_spec\":";
plot_data << plot.get_next_data();
plot_data << "}";
return respond(plot_data.str(), "application/json");
}
if (req_target.find("/data/table/") == 0) {
const size_t data_url_length = sizeof("/data/table/") - 1;
std::string table_id = req_target.substr(data_url_length).to_string();
std::string image_row, image_column;
size_t image_url_pos = table_id.find("/image/");
if (image_url_pos != std::string::npos) {
// Found image URL request
DASSERT_EQ(req.method(), http::verb::get);
// chop off everything after /image/
std::string image_requested = table_id.substr(image_url_pos + sizeof("/image/") - 1);
// chop off everything before /image/
table_id = table_id.substr(0, image_url_pos);
size_t idx = std::stoul(table_id);
if (idx >= tables.size()) {
return server_error("Expected table " + table_id + " was not found");
}
const WebServer::table& container = tables[idx];
const std::shared_ptr<turi::unity_sframe>& table = container.sf;
// extract row and column
size_t slash_pos = image_requested.find("/");
if (slash_pos == std::string::npos) {
return server_error("Could not parse image URL requested: " + req_target.to_string());
}
std::string row = image_requested.substr(0, slash_pos);
size_t row_index = std::stoul(row);
std::string column_name = image_requested.substr(slash_pos + 1);
// find the image and return it as PNG data
turi::gl_sarray gl_sa(table->select_column(column_name));
turi::flex_image value = gl_sa[row_index].get<turi::flex_image>();
std::string png_data = image_png_data(value, 200 /* resized_height */);
return respond(png_data, "image/png");
}
size_t idx = std::stoul(table_id);
if (idx >= tables.size()) {
return server_error("Expected table " + table_id + " was not found");
}
const WebServer::table& container = tables[idx];
const std::shared_ptr<turi::unity_sframe>& table = container.sf;
// Read request type out of message body
using boost::property_tree::ptree;
ptree pt;
std::string body = req.body();
std::stringstream ss(body);
read_json(ss, pt);
std::string type = pt.get<std::string>("type");
if (type == "rows") {
size_t start = pt.get<size_t>("start");
size_t end = pt.get<size_t>("end");
return respond(table_data(table, container.reader.get(), start, end), "application/json");
} else if (type == "accordion") {
size_t row_index = pt.get<size_t>("index");
std::string column_name = pt.get<std::string>("column");
return respond(table_accordion(table, column_name, row_index), "application/json");
} else {
log_and_throw("Unexpected data table request type: " + type);
}
}
// try to match a static file
// note: server-side API caller should set static URL directory prior to any HTTP requests!
DASSERT_NE(VISUALIZATION_WEB_SERVER_ROOT_DIRECTORY, "");
std::string possible_file_path = VISUALIZATION_WEB_SERVER_ROOT_DIRECTORY + req_target.to_string();
size_t query_pos = possible_file_path.rfind("?");
if (query_pos != std::string::npos) {
possible_file_path = possible_file_path.substr(0, query_pos);
}
auto possible_file_status = turi::fileio::get_file_status(possible_file_path);
if (possible_file_status.first == turi::fileio::file_status::REGULAR_FILE) {
// we can serve a file from this path
turi::general_ifstream stream(possible_file_path);
std::string file_contents{ std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>() };
std::string type = mime_type(possible_file_path).to_string();
return respond(file_contents, type);
}
// did not match any expected URL
logstream(LOG_ERROR)
<< "WebServer: unrecognized destination requested in URL: "
<< req_target << ". Err:" << possible_file_status.second
<< std::endl;
return send(not_found(req_target));
} catch (const std::exception& e) {
return server_error(e.what());
} catch (const std::string& s) {
return server_error(s);
} catch (...) {
return server_error("Unknown exception");
}
};