105 lines
3.1 KiB
C++
105 lines
3.1 KiB
C++
#include "versiond/versiond.hpp"
|
|
#include <arpa/inet.h>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <netinet/in.h>
|
|
#include <sstream>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
namespace fs = std::filesystem;
|
|
namespace versiond {
|
|
static std::string dash_html() {
|
|
std::ifstream f("src/versiond/dashboard.html", std::ios::binary);
|
|
if (f) {
|
|
std::ostringstream s;
|
|
s << f.rdbuf();
|
|
return s.str();
|
|
}
|
|
return "<html><body><h1>versiond</h1></body></html>";
|
|
}
|
|
ApiResp api_build_response(Db& db, const std::string& path) {
|
|
ApiResp r{200, "application/json", ""};
|
|
if (path == "/" || path == "/dashboard") {
|
|
r.ctype = "text/html";
|
|
r.body = dash_html();
|
|
} else if (path == "/api/stats") {
|
|
auto m = collect_stats(db);
|
|
std::ostringstream o;
|
|
o << "{\"versions\":" << m["versions"] << ",\"blobs\":" << m["blobs"] << "}";
|
|
r.body = o.str();
|
|
} else if (path == "/api/versions") {
|
|
auto rows = db.query("SELECT id,path,digest,reason FROM versions ORDER BY id DESC LIMIT 100;");
|
|
std::ostringstream o;
|
|
o << "[";
|
|
bool first = true;
|
|
for (auto& x : rows) {
|
|
if (!first) o << ",";
|
|
first = false;
|
|
o << "{\"id\":" << x.at("id") << ",\"digest\":\"" << x.at("digest") << "\"}";
|
|
}
|
|
o << "]";
|
|
r.body = o.str();
|
|
} else if (path == "/api/health") {
|
|
r.body = "{\"ok\":true}";
|
|
} else if (path == "/api/blobs") {
|
|
auto m = collect_stats(db);
|
|
std::ostringstream o;
|
|
o << "{\"blobs\":" << m["blobs"] << "}";
|
|
r.body = o.str();
|
|
} else {
|
|
r.code = 404;
|
|
r.body = "{\"error\":\"not found\"}";
|
|
}
|
|
return r;
|
|
}
|
|
std::string api_format_response(const ApiResp& r) {
|
|
std::ostringstream h;
|
|
h << "HTTP/1.1 " << r.code << (r.code == 200 ? " OK" : " Not Found") << "\r\n";
|
|
h << "Content-Type: " << r.ctype << "\r\n";
|
|
h << "Content-Length: " << r.body.size() << "\r\nConnection: close\r\n\r\n";
|
|
return h.str() + r.body;
|
|
}
|
|
static void send_all(int fd, const std::string& s) {
|
|
size_t n = 0;
|
|
while (n < s.size()) {
|
|
ssize_t w = send(fd, s.data() + n, s.size() - n, 0);
|
|
if (w <= 0) break;
|
|
n += (size_t)w;
|
|
}
|
|
}
|
|
int api_main(const Config& c) {
|
|
int srv = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (srv < 0) return 1;
|
|
int one = 1;
|
|
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
sockaddr_in a{};
|
|
a.sin_family = AF_INET;
|
|
a.sin_port = htons((uint16_t)c.port);
|
|
a.sin_addr.s_addr = inet_addr(c.host.c_str());
|
|
if (bind(srv, (sockaddr*)&a, sizeof(a)) < 0) return 1;
|
|
if (listen(srv, 16) < 0) return 1;
|
|
printf("serve %s:%d\n", c.host.c_str(), c.port);
|
|
fflush(stdout);
|
|
Db db(c.index_file);
|
|
Store st(c.spool_dir);
|
|
(void)st;
|
|
char buf[65536];
|
|
for (;;) {
|
|
int fd = accept(srv, nullptr, nullptr);
|
|
if (fd < 0) break;
|
|
ssize_t n = recv(fd, buf, sizeof(buf) - 1, 0);
|
|
std::string req = n > 0 ? std::string(buf, (size_t)n) : "";
|
|
std::istringstream rs(req);
|
|
std::string method, path;
|
|
rs >> method >> path;
|
|
ApiResp r = api_build_response(db, path);
|
|
std::string full = api_format_response(r);
|
|
send_all(fd, full);
|
|
close(fd);
|
|
}
|
|
close(srv);
|
|
return 0;
|
|
}
|
|
}
|