-
-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathresource_hook_table.cpp
More file actions
289 lines (261 loc) · 10 KB
/
Copy pathresource_hook_table.cpp
File metadata and controls
289 lines (261 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
*/
// Out-of-line bodies for detail::resource_hook_table.
//
// Mirrors the fire_hooks_for_phase / fire_short_circuit_hooks_for_phase
// templates in src/detail/hook_phase_dispatch.cpp, but the firing
// helpers here are members on a per-resource table (not on
// webserver_impl).
// The lock discipline (snapshot under shared_lock, release, iterate)
// is identical, so re-entrant add_hook / remove() calls from inside a
// hook callback do not deadlock.
#include "httpserver/detail/resource_hook_table.hpp"
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <functional>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "httpserver/hook_action.hpp"
#include "httpserver/hook_context.hpp"
#include "httpserver/hook_phase.hpp"
#include "httpserver/http_response.hpp"
namespace httpserver {
namespace detail {
namespace {
// append_impl -- shared body for all five append_* methods.
// Each public overload delegates here with its phase constant and
// target vector. Mirrors the DRY pattern already applied to fire_* in
// fire_short_circuit_impl / fire_void_impl below.
template <hook_phase P, typename Sig>
std::uint64_t append_impl(
std::atomic<std::uint64_t>& next_slot_id,
std::shared_mutex& mtx,
std::vector<resource_hook_table::entry<Sig>>& vec,
std::array<std::atomic<bool>,
static_cast<std::size_t>(hook_phase::count_)>& any_hooks,
std::function<Sig> fn) {
const std::uint64_t id =
next_slot_id.fetch_add(1, std::memory_order_relaxed);
{
std::unique_lock lock(mtx);
vec.push_back({id, std::move(fn)});
any_hooks[static_cast<std::size_t>(P)]
.store(true, std::memory_order_release);
}
return id;
}
// fire_short_circuit_impl -- shared dispatch for the three hook_action-
// returning phases. Snapshots the vector, releases the lock, then
// iterates. A short-circuit (action.is_pass() == false) abandons the
// chain and returns the extracted response.
template <typename Vec, typename Ctx>
std::optional<::httpserver::http_response>
fire_short_circuit_impl(std::shared_mutex& mtx,
const Vec& hook_vec,
Ctx& ctx,
std::string_view phase_name,
const resource_hook_table::log_fn& log) {
Vec snapshot;
try {
{
std::shared_lock lock(mtx);
snapshot = hook_vec;
}
for (auto& e : snapshot) {
try {
auto action = e.fn(ctx);
if (!action.is_pass()) {
return std::move(action).take_response();
}
} catch (const std::exception& ex) {
if (log) {
log(std::string("per-route hook[") +
std::string(phase_name) + "] threw: " + ex.what());
}
} catch (...) {
if (log) {
log(std::string("per-route hook[") +
std::string(phase_name) + "] threw unknown exception");
}
}
}
} catch (...) {
if (log) {
log(std::string("per-route fire_short_circuit[") +
std::string(phase_name) + "]: snapshot copy failed");
}
}
return std::nullopt;
}
// fire_void_impl -- shared dispatch for the two void-returning phases.
template <typename Vec, typename Ctx>
void fire_void_impl(std::shared_mutex& mtx,
const Vec& hook_vec,
const Ctx& ctx,
std::string_view phase_name,
const resource_hook_table::log_fn& log) {
Vec snapshot;
try {
{
std::shared_lock lock(mtx);
snapshot = hook_vec;
}
for (const auto& e : snapshot) {
try {
e.fn(ctx);
} catch (const std::exception& ex) {
if (log) {
log(std::string("per-route hook[") +
std::string(phase_name) + "] threw: " + ex.what());
}
} catch (...) {
if (log) {
log(std::string("per-route hook[") +
std::string(phase_name) + "] threw unknown exception");
}
}
}
} catch (...) {
if (log) {
log(std::string("per-route fire_void[") +
std::string(phase_name) + "]: snapshot copy failed");
}
}
}
} // namespace
// ---- append_* -----------------------------------------------------------
// Each public overload delegates to the shared append_impl<P> template
// (see the anonymous namespace above). The only per-overload variation
// is the hook_phase constant and the target vector.
std::uint64_t resource_hook_table::append_before_handler(
std::function<hook_action(before_handler_ctx&)> fn) {
return append_impl<hook_phase::before_handler>(
next_slot_id_, hook_table_mutex_,
hooks_before_handler_, any_hooks_, std::move(fn));
}
std::uint64_t resource_hook_table::append_handler_exception(
std::function<hook_action(const handler_exception_ctx&)> fn) {
return append_impl<hook_phase::handler_exception>(
next_slot_id_, hook_table_mutex_,
hooks_handler_exception_, any_hooks_, std::move(fn));
}
std::uint64_t resource_hook_table::append_after_handler(
std::function<hook_action(after_handler_ctx&)> fn) {
return append_impl<hook_phase::after_handler>(
next_slot_id_, hook_table_mutex_,
hooks_after_handler_, any_hooks_, std::move(fn));
}
std::uint64_t resource_hook_table::append_response_sent(
std::function<void(const response_sent_ctx&)> fn) {
return append_impl<hook_phase::response_sent>(
next_slot_id_, hook_table_mutex_,
hooks_response_sent_, any_hooks_, std::move(fn));
}
std::uint64_t resource_hook_table::append_request_completed(
std::function<void(const request_completed_ctx&)> fn) {
return append_impl<hook_phase::request_completed>(
next_slot_id_, hook_table_mutex_,
hooks_request_completed_, any_hooks_, std::move(fn));
}
// ---- remove_slot --------------------------------------------------------
void resource_hook_table::remove_slot(
hook_phase phase, std::uint64_t slot_id) noexcept {
std::unique_lock lock(hook_table_mutex_);
auto erase_if_found = [slot_id](auto& vec) {
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (it->slot_id == slot_id) {
vec.erase(it);
return;
}
}
};
auto reset_if_empty = [&](const auto& vec) {
if (vec.empty()) {
any_hooks_[static_cast<std::size_t>(phase)]
.store(false, std::memory_order_release);
}
};
switch (phase) {
case hook_phase::before_handler:
erase_if_found(hooks_before_handler_);
reset_if_empty(hooks_before_handler_);
break;
case hook_phase::handler_exception:
erase_if_found(hooks_handler_exception_);
reset_if_empty(hooks_handler_exception_);
break;
case hook_phase::after_handler:
erase_if_found(hooks_after_handler_);
reset_if_empty(hooks_after_handler_);
break;
case hook_phase::response_sent:
erase_if_found(hooks_response_sent_);
reset_if_empty(hooks_response_sent_);
break;
case hook_phase::request_completed:
erase_if_found(hooks_request_completed_);
reset_if_empty(hooks_request_completed_);
break;
default:
// The other six phases are never registered on a resource (the
// public add_hook overloads reject them with std::invalid_argument
// before reaching here). A defensive no-op for forward compat.
break;
}
}
// ---- fire_* -------------------------------------------------------------
std::optional<http_response>
resource_hook_table::fire_before_handler(
before_handler_ctx& ctx, const log_fn& log) {
return fire_short_circuit_impl(
hook_table_mutex_, hooks_before_handler_, ctx,
"before_handler", log);
}
std::optional<http_response>
resource_hook_table::fire_handler_exception(
const handler_exception_ctx& ctx, const log_fn& log) {
return fire_short_circuit_impl(
hook_table_mutex_, hooks_handler_exception_, ctx,
"handler_exception", log);
}
std::optional<http_response>
resource_hook_table::fire_after_handler(
after_handler_ctx& ctx, const log_fn& log) {
return fire_short_circuit_impl(
hook_table_mutex_, hooks_after_handler_, ctx,
"after_handler", log);
}
void resource_hook_table::fire_response_sent(
const response_sent_ctx& ctx, const log_fn& log) {
fire_void_impl(hook_table_mutex_, hooks_response_sent_, ctx,
"response_sent", log);
}
void resource_hook_table::fire_request_completed(
const request_completed_ctx& ctx, const log_fn& log) {
fire_void_impl(hook_table_mutex_, hooks_request_completed_, ctx,
"request_completed", log);
}
} // namespace detail
} // namespace httpserver