-
-
Notifications
You must be signed in to change notification settings - Fork 191
Expand file tree
/
Copy pathhooks_handler_exception_fallback_to_hardcoded_500.cpp
More file actions
140 lines (117 loc) · 4.8 KB
/
Copy pathhooks_handler_exception_fallback_to_hardcoded_500.cpp
File metadata and controls
140 lines (117 loc) · 4.8 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
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
*/
// Backstop of the handler error-propagation contract:
//
// "The hardcoded empty-body-500 fallback still fires when every hook in
// the chain (including the internal_error_handler alias) either throws
// or returns pass()."
//
// Scenario: hook A returns pass(); hook B throws; the
// internal_error_handler alias C itself throws too. Every entry in the
// chain has had its turn and produced nothing -> the dispatcher must
// emit an empty-body 500. The log_error capture should record both A's
// (no), B's (yes) and C's (yes) throws.
//
// Critically, the user internal_error_handler callable must be observed
// EXACTLY ONCE on this request (the alias-slot invocation). The
// dispatcher must NOT re-enter run_internal_error_handler_safely after
// the chain ran, which would call C a second time.
#include <curl/curl.h>
#include <atomic>
#include <cstddef>
#include <functional>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <string_view>
#include "./httpserver.hpp"
#include "./littletest.hpp"
#include "./server_ready.hpp"
#include "./log_capture.hpp"
using httpserver::create_webserver;
using httpserver::handler_exception_ctx;
using httpserver::hook_action;
using httpserver::hook_phase;
using httpserver::http_request;
using httpserver::http_response;
using httpserver::webserver;
#define PORT 8232
namespace {
size_t writefunc(void* ptr, size_t size, size_t nmemb, std::string* s) {
s->append(reinterpret_cast<char*>(ptr), size * nmemb);
return size * nmemb;
}
class throwing_resource : public httpserver::http_resource {
public:
http_response render_get(const http_request&) override {
throw std::runtime_error("boom");
}
};
// log_capture is defined in log_capture.hpp.
} // namespace
LT_BEGIN_SUITE(hooks_handler_exception_fallback_suite)
void set_up() {}
void tear_down() {}
LT_END_SUITE(hooks_handler_exception_fallback_suite)
LT_BEGIN_AUTO_TEST(hooks_handler_exception_fallback_suite,
all_hooks_fail_yields_hardcoded_empty_500)
std::atomic<std::size_t> a_called{0};
std::atomic<std::size_t> b_called{0};
std::atomic<std::size_t> c_called{0};
log_capture cap;
auto logger = [&cap](const std::string& msg) { cap.append(msg); };
auto alias_c =
[&c_called](const http_request&, std::string_view) -> http_response {
c_called.fetch_add(1, std::memory_order_relaxed);
throw std::runtime_error("alias-also-throws");
};
webserver ws{create_webserver(PORT)
.internal_error_handler(alias_c)
.log_error(logger)};
auto ha = ws.add_hook(hook_phase::handler_exception,
std::function<hook_action(const handler_exception_ctx&)>(
[&a_called](const handler_exception_ctx&) {
a_called.fetch_add(1, std::memory_order_relaxed);
return hook_action::pass();
}));
auto hb = ws.add_hook(hook_phase::handler_exception,
std::function<hook_action(const handler_exception_ctx&)>(
[&b_called](const handler_exception_ctx&) -> hook_action {
b_called.fetch_add(1, std::memory_order_relaxed);
throw std::runtime_error("bravo-throws");
}));
auto resource = std::make_shared<throwing_resource>();
ws.register_path("/boom", resource);
ws.start(false);
httpserver_test::wait_for_server_ready(PORT);
CURL* curl = curl_easy_init();
LT_ASSERT_NEQ(curl, static_cast<CURL*>(nullptr));
std::string url = "http://127.0.0.1:" + std::to_string(PORT) + "/boom";
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
std::string resp_body;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp_body);
CURLcode res = curl_easy_perform(curl);
long http_code = 0; // NOLINT(runtime/int)
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
ws.stop();
LT_CHECK_EQ(res, CURLE_OK);
LT_CHECK_EQ(http_code, 500L);
LT_CHECK_EQ(resp_body, std::string(""));
LT_CHECK_EQ(a_called.load(), static_cast<std::size_t>(1));
LT_CHECK_EQ(b_called.load(), static_cast<std::size_t>(1));
// C must have been called exactly once -- not twice. This pins that
// the dispatcher does NOT re-enter run_internal_error_handler_safely
// after the alias-slot has already invoked the user callable.
LT_CHECK_EQ(c_called.load(), static_cast<std::size_t>(1));
std::string log_buf = cap.read();
LT_CHECK_NEQ(log_buf.find("bravo-throws"), std::string::npos);
LT_CHECK_NEQ(log_buf.find("alias-also-throws"), std::string::npos);
LT_END_AUTO_TEST(all_hooks_fail_yields_hardcoded_empty_500)
LT_BEGIN_AUTO_TEST_ENV()
AUTORUN_TESTS()
LT_END_AUTO_TEST_ENV()