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

llnl / dftracer-utils / 30069185152

24 Jul 2026 05:20AM UTC coverage: 50.687% (-2.0%) from 52.66%
30069185152

Pull #99

github

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

18062 of 48203 branches covered (37.47%)

Branch coverage included in aggregate %.

1510 of 2205 new or added lines in 59 files covered. (68.48%)

742 existing lines in 114 files now uncovered.

23711 of 34210 relevant lines covered (69.31%)

39853.91 hits per line

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

66.82
/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) {
209✔
17
    std::string result;
209✔
18
    result.reserve(sv.size());
209!
19
    for (std::size_t i = 0; i < sv.size(); ++i) {
1,260✔
20
        if (sv[i] == '%' && i + 2 < sv.size()) {
1,051!
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] == '+') {
989✔
30
            result.push_back(' ');
1!
31
            continue;
1✔
32
        }
33
        result.push_back(sv[i]);
988!
34
    }
35
    return result;
209✔
UNCOV
36
}
×
37

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

46
        auto eq = pair.find('=');
107✔
47
        if (eq == std::string_view::npos) {
107✔
48
            params.params_.emplace_back(url_decode(pair), "");
5!
49
        } else {
50
            params.params_.emplace_back(url_decode(pair.substr(0, eq)),
102!
51
                                        url_decode(pair.substr(eq + 1)));
204!
52
        }
53
    }
54
    return params;
88✔
UNCOV
55
}
×
56

57
std::string_view QueryParams::get(std::string_view key,
487✔
58
                                  std::string_view default_value) const {
59
    for (const auto& [k, v] : params_) {
1,757✔
60
        if (k == key) return v;
1,365✔
61
    }
62
    return default_value;
392✔
63
}
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
}
71

72
std::string QueryParams::canonical_key() const {
18✔
73
    std::vector<const std::pair<std::string, std::string>*> sorted;
18✔
74
    sorted.reserve(params_.size());
18!
75
    for (const auto& p : params_) sorted.push_back(&p);
65!
76
    std::sort(sorted.begin(), sorted.end(), [](const auto* a, const auto* b) {
18!
77
        if (a->first != b->first) return a->first < b->first;
62!
NEW
78
        return a->second < b->second;
×
79
    });
80
    std::string key;
18✔
81
    for (const auto* p : sorted) {
65✔
82
        key.append(p->first);
47!
83
        key.push_back('=');
47!
84
        key.append(p->second);
47!
85
        key.push_back('\x1f');
47!
86
    }
87
    return key;
36✔
88
}
18✔
89

90
int QueryParams::get_int(std::string_view key, int default_value) const {
48✔
91
    auto sv = get(key);
48!
92
    if (sv.empty()) return default_value;
48✔
93
    int val = default_value;
19✔
94
    std::from_chars(sv.data(), sv.data() + sv.size(), val);
19!
95
    return val;
19✔
96
}
97

98
double QueryParams::get_double(std::string_view key,
85✔
99
                               double default_value) const {
100
    auto sv = get(key);
85!
101
    if (sv.empty()) return default_value;
85✔
102
    // std::from_chars for doubles not universally available in C++17
103
    // compilers. Use strtod as fallback.
104
    char* end = nullptr;
43✔
105
    std::string tmp(sv);
43!
106
    double val = std::strtod(tmp.c_str(), &end);
43✔
107
    if (end == tmp.c_str()) return default_value;
43✔
108
    return val;
42✔
109
}
43✔
110

111
// ============================================================================
112
// Router
113
// ============================================================================
114

115
void Router::get(const std::string& path, RouteHandler handler) {
22✔
116
    routes_.push_back(Route{"GET", path, std::move(handler), {}});
22!
117
}
22✔
118

119
void Router::get(const std::string& path, RouteHandler handler, RouteDoc doc) {
209✔
120
    routes_.push_back(Route{"GET", path, std::move(handler), std::move(doc)});
209!
121
}
209✔
122

123
void Router::post(const std::string& path, RouteHandler handler) {
×
124
    routes_.push_back(Route{"POST", path, std::move(handler), {}});
×
125
}
×
126

127
void Router::post(const std::string& path, RouteHandler handler, RouteDoc doc) {
11✔
128
    routes_.push_back(Route{"POST", path, std::move(handler), std::move(doc)});
11!
129
}
11✔
130

131
namespace {
132

133
// RFC 6750: the auth scheme name is case-insensitive.
134
bool strip_bearer_prefix(std::string_view h, std::string_view& token) {
×
135
    constexpr std::string_view BEARER = "Bearer ";
×
136
    if (h.size() <= BEARER.size()) return false;
×
137
    for (std::size_t i = 0; i < BEARER.size(); ++i) {
×
138
        if (std::tolower(static_cast<unsigned char>(h[i])) !=
×
139
            std::tolower(static_cast<unsigned char>(BEARER[i])))
×
140
            return false;
×
141
    }
142
    token = h.substr(BEARER.size());
×
143
    return true;
×
144
}
145

146
}  // namespace
147

148
coro::CoroTask<HttpResponse> Router::handle(const HttpRequest& req) {
64!
149
    // Split path and query string.
150
    auto path = req.path;
151
    std::string_view query_str;
152
    auto qpos = path.find('?');
153
    if (qpos != std::string_view::npos) {
154
        query_str = path.substr(qpos + 1);
155
        path = path.substr(0, qpos);
156
    }
157

158
    auto params = QueryParams::parse(query_str);
159

160
    // Before the token check: browsers omit Authorization from the preflight.
161
    if (req.method == "OPTIONS") {
162
        co_return HttpResponse{
163
            .status_code = 204,
164
            .status_text = "No Content",
165
            .headers = {{"Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
166
                        {"Access-Control-Allow-Headers",
167
                         "Authorization, "
168
                         "Content-Type, "
169
                         "X-Request-Id"},
170
                        {"Access-Control-Max-Age", "86400"}},
171
            .body = ""};
172
    }
173

174
    // Optional access token: accept ?token= or "Authorization: Bearer <token>".
175
    if (!auth_token_.empty()) {
176
        bool ok = params.get("token") == auth_token_;
177
        if (!ok) {
178
            std::string_view token;
179
            if (strip_bearer_prefix(req.header("Authorization"), token))
180
                ok = token == auth_token_;
181
        }
182
        if (!ok) {
183
            co_return HttpResponse{.status_code = 401,
184
                                   .status_text = "Unauthorized",
185
                                   .headers = {{"Content-Type", "text/plain"}},
186
                                   .body = "Unauthorized"};
187
        }
188
    }
189

190
    // Match routes (exact prefix match).
191
    for (const auto& route : routes_) {
192
        if (req.method == route.method && path == route.path) {
193
            co_return co_await route.handler(req, params);
194
        }
195
    }
196

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

201
    co_return HttpResponse::not_found();
202
}
128!
203

204
}  // 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