• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

llnl / dftracer-utils / 29787659139

20 Jul 2026 11:34PM UTC coverage: 51.578% (-1.1%) from 52.66%
29787659139

Pull #99

github

web-flow
Merge ee805adeb into 06bc84ec9
Pull Request #99: Support CM time_metric (NS/MS/SEC/US) across reader and viz

34832 of 86278 branches covered (40.37%)

Branch coverage included in aggregate %.

1034 of 1319 new or added lines in 30 files covered. (78.39%)

5193 existing lines in 197 files now uncovered.

35349 of 49791 relevant lines covered (70.99%)

9765.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.13
/src/dftracer/utils/server/router.cpp
1
#include <dftracer/utils/server/http_request.h>
2
#include <dftracer/utils/server/http_response.h>
3
#include <dftracer/utils/server/router.h>
4

5
#include <algorithm>
6
#include <cctype>
7
#include <charconv>
8
#include <cstdlib>
9

10
namespace dftracer::utils::server {
11

12
// ============================================================================
13
// QueryParams
14
// ============================================================================
15

16
static std::string url_decode(std::string_view sv) {
186✔
17
    std::string result;
186✔
18
    result.reserve(sv.size());
186!
19
    for (std::size_t i = 0; i < sv.size(); ++i) {
1,220✔
20
        if (sv[i] == '%' && i + 2 < sv.size()) {
1,034!
21
            char hex[3] = {sv[i + 1], sv[i + 2], '\0'};
62✔
22
            char* end = nullptr;
62✔
23
            unsigned long val = std::strtoul(hex, &end, 16);
62!
24
            if (end == hex + 2) {
62!
25
                result.push_back(static_cast<char>(val));
62!
26
                i += 2;
62✔
27
                continue;
62✔
28
            }
29
        } else if (sv[i] == '+') {
972✔
30
            result.push_back(' ');
1!
31
            continue;
1✔
32
        }
33
        result.push_back(sv[i]);
971!
34
    }
971✔
35
    return result;
186✔
36
}
186!
37

38
QueryParams QueryParams::parse(std::string_view query) {
72✔
39
    QueryParams params;
72✔
40
    while (!query.empty()) {
167✔
41
        auto amp = query.find('&');
95✔
42
        auto pair = query.substr(0, amp);
95!
43
        query = (amp == std::string_view::npos) ? std::string_view{}
152✔
44
                                                : query.substr(amp + 1);
57!
45

46
        auto eq = pair.find('=');
95✔
47
        if (eq == std::string_view::npos) {
95✔
48
            params.params_.emplace_back(url_decode(pair), "");
4!
49
        } else {
4✔
50
            params.params_.emplace_back(url_decode(pair.substr(0, eq)),
182!
51
                                        url_decode(pair.substr(eq + 1)));
91!
52
        }
53
    }
54
    return params;
72✔
55
}
72!
56

57
std::string_view QueryParams::get(std::string_view key,
448✔
58
                                  std::string_view default_value) const {
59
    for (const auto& [k, v] : params_) {
1,587✔
60
        if (k == key) return v;
1,231✔
61
    }
62
    return default_value;
356✔
63
}
448✔
64

65
bool QueryParams::has(std::string_view key) const {
57✔
66
    for (const auto& [k, v] : params_) {
95✔
67
        if (k == key) return true;
92✔
68
    }
69
    return false;
3✔
70
}
57✔
71

72
int QueryParams::get_int(std::string_view key, int default_value) const {
41✔
73
    auto sv = get(key);
41✔
74
    if (sv.empty()) return default_value;
41✔
75
    int val = default_value;
19✔
76
    std::from_chars(sv.data(), sv.data() + sv.size(), val);
19✔
77
    return val;
19✔
78
}
41✔
79

80
double QueryParams::get_double(std::string_view key,
85✔
81
                               double default_value) const {
82
    auto sv = get(key);
85✔
83
    if (sv.empty()) return default_value;
85✔
84
    // std::from_chars for doubles not universally available in C++17
85
    // compilers. Use strtod as fallback.
86
    char* end = nullptr;
43✔
87
    std::string tmp(sv);
43✔
88
    double val = std::strtod(tmp.c_str(), &end);
43!
89
    if (end == tmp.c_str()) return default_value;
43✔
90
    return val;
42✔
91
}
85✔
92

93
// ============================================================================
94
// Router
95
// ============================================================================
96

97
void Router::get(const std::string& path, RouteHandler handler) {
16✔
98
    routes_.push_back(Route{"GET", path, std::move(handler), {}});
16!
99
}
16✔
100

101
void Router::get(const std::string& path, RouteHandler handler, RouteDoc doc) {
144✔
102
    routes_.push_back(Route{"GET", path, std::move(handler), std::move(doc)});
144!
103
}
144✔
104

105
void Router::post(const std::string& path, RouteHandler handler) {
×
106
    routes_.push_back(Route{"POST", path, std::move(handler), {}});
×
107
}
×
108

109
void Router::post(const std::string& path, RouteHandler handler, RouteDoc doc) {
8✔
110
    routes_.push_back(Route{"POST", path, std::move(handler), std::move(doc)});
8!
111
}
8✔
112

113
namespace {
114

115
// RFC 6750: the auth scheme name is case-insensitive.
116
bool strip_bearer_prefix(std::string_view h, std::string_view& token) {
×
117
    constexpr std::string_view BEARER = "Bearer ";
×
118
    if (h.size() <= BEARER.size()) return false;
×
119
    for (std::size_t i = 0; i < BEARER.size(); ++i) {
×
120
        if (std::tolower(static_cast<unsigned char>(h[i])) !=
×
121
            std::tolower(static_cast<unsigned char>(BEARER[i])))
×
122
            return false;
×
UNCOV
123
    }
×
124
    token = h.substr(BEARER.size());
×
125
    return true;
×
UNCOV
126
}
×
127

128
}  // namespace
129

130
coro::CoroTask<HttpResponse> Router::handle(const HttpRequest& req) {
275!
131
    // Split path and query string.
132
    auto path = req.path;
55✔
133
    std::string_view query_str;
55✔
134
    auto qpos = path.find('?');
55✔
135
    if (qpos != std::string_view::npos) {
55✔
136
        query_str = path.substr(qpos + 1);
22!
137
        path = path.substr(0, qpos);
22!
138
    }
22✔
139

140
    auto params = QueryParams::parse(query_str);
55!
141

142
    // Before the token check: browsers omit Authorization from the preflight.
143
    if (req.method == "OPTIONS") {
55!
144
        co_return HttpResponse{
57!
145
            .status_code = 204,
146
            .status_text = "No Content",
1!
147
            .headers = {{"Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
1!
148
                        {"Access-Control-Allow-Headers",
1!
149
                         "Authorization, "
150
                         "Content-Type, "
151
                         "X-Request-Id"},
152
                        {"Access-Control-Max-Age", "86400"}},
1!
153
            .body = ""};
1!
154
    }
155

156
    // Optional access token: accept ?token= or "Authorization: Bearer <token>".
157
    if (!auth_token_.empty()) {
54!
UNCOV
158
        bool ok = params.get("token") == auth_token_;
×
UNCOV
159
        if (!ok) {
×
UNCOV
160
            std::string_view token;
×
UNCOV
161
            if (strip_bearer_prefix(req.header("Authorization"), token))
×
UNCOV
162
                ok = token == auth_token_;
×
UNCOV
163
        }
×
UNCOV
164
        if (!ok) {
×
UNCOV
165
            co_return HttpResponse{.status_code = 401,
×
UNCOV
166
                                   .status_text = "Unauthorized",
×
UNCOV
167
                                   .headers = {{"Content-Type", "text/plain"}},
×
UNCOV
168
                                   .body = "Unauthorized"};
×
169
        }
UNCOV
170
    }
×
171

172
    // Match routes (exact prefix match).
173
    for (const auto& route : routes_) {
605✔
174
        if (req.method == route.method && path == route.path) {
551✔
175
            co_return co_await route.handler(req, params);
212!
176
        }
177
    }
445✔
178

179
    // Try prefix matches for parameterized routes
180
    // (e.g., "/api/v1/files/:file/info" matches "/api/v1/files/foo/info")
181
    // For now, use exact match only — parameterized routes can be added later.
182

183
    co_return HttpResponse::not_found();
1!
184
}
161!
185

186
}  // namespace dftracer::utils::server
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc