]> git.proxmox.com Git - ceph.git/blob - ceph/src/exporter/http_server.cc
import ceph quincy 17.2.4
[ceph.git] / ceph / src / exporter / http_server.cc
1 #include "http_server.h"
2 #include "common/debug.h"
3 #include "common/hostname.h"
4 #include "global/global_init.h"
5 #include "global/global_context.h"
6 #include "exporter/DaemonMetricCollector.h"
7
8 #include <boost/asio.hpp>
9 #include <boost/beast/core.hpp>
10 #include <boost/beast/http.hpp>
11 #include <boost/beast/version.hpp>
12 #include <boost/thread/thread.hpp>
13 #include <chrono>
14 #include <cstdlib>
15 #include <ctime>
16 #include <iostream>
17 #include <map>
18 #include <memory>
19 #include <string>
20
21 #define dout_context g_ceph_context
22 #define dout_subsys ceph_subsys_ceph_exporter
23
24 namespace beast = boost::beast; // from <boost/beast.hpp>
25 namespace http = beast::http; // from <boost/beast/http.hpp>
26 namespace net = boost::asio; // from <boost/asio.hpp>
27 using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
28
29 class http_connection : public std::enable_shared_from_this<http_connection> {
30 public:
31 http_connection(tcp::socket socket) : socket_(std::move(socket)) {}
32
33 // Initiate the asynchronous operations associated with the connection.
34 void start() {
35 read_request();
36 check_deadline();
37 }
38
39 private:
40 tcp::socket socket_;
41 beast::flat_buffer buffer_{8192};
42 http::request<http::dynamic_body> request_;
43 http::response<http::string_body> response_;
44
45 net::steady_timer deadline_{socket_.get_executor(), std::chrono::seconds(60)};
46
47 // Asynchronously receive a complete request message.
48 void read_request() {
49 auto self = shared_from_this();
50
51 http::async_read(socket_, buffer_, request_,
52 [self](beast::error_code ec, std::size_t bytes_transferred) {
53 boost::ignore_unused(bytes_transferred);
54 if (ec) {
55 dout(1) << "ERROR: " << ec.message() << dendl;
56 return;
57 }
58 else {
59 self->process_request();
60 }
61 });
62 }
63
64 // Determine what needs to be done with the request message.
65 void process_request() {
66 response_.version(request_.version());
67 response_.keep_alive(request_.keep_alive());
68
69 switch (request_.method()) {
70 case http::verb::get:
71 response_.result(http::status::ok);
72 create_response();
73 break;
74
75 default:
76 // We return responses indicating an error if
77 // we do not recognize the request method.
78 response_.result(http::status::method_not_allowed);
79 response_.set(http::field::content_type, "text/plain");
80 std::string body("Invalid request-method '" +
81 std::string(request_.method_string()) + "'");
82 response_.body() = body;
83 break;
84 }
85
86 write_response();
87 }
88
89 // Construct a response message based on the program state.
90 void create_response() {
91 if (request_.target() == "/") {
92 response_.set(http::field::content_type, "text/html; charset=utf-8");
93 std::string body("<html>\n"
94 "<head><title>Ceph Exporter</title></head>\n"
95 "<body>\n"
96 "<h1>Ceph Exporter</h1>\n"
97 "<p><a href='/metrics'>Metrics</a></p>"
98 "</body>\n"
99 "</html>\n");
100 response_.body() = body;
101 } else if (request_.target() == "/metrics") {
102 response_.set(http::field::content_type, "text/plain; charset=utf-8");
103 DaemonMetricCollector &collector = collector_instance();
104 std::string metrics = collector.get_metrics();
105 response_.body() = metrics;
106 } else {
107 response_.result(http::status::method_not_allowed);
108 response_.set(http::field::content_type, "text/plain");
109 response_.body() = "File not found \n";
110 }
111 }
112
113 // Asynchronously transmit the response message.
114 void write_response() {
115 auto self = shared_from_this();
116
117 response_.prepare_payload();
118
119 http::async_write(socket_, response_,
120 [self](beast::error_code ec, std::size_t) {
121 self->socket_.shutdown(tcp::socket::shutdown_send, ec);
122 self->deadline_.cancel();
123 if (ec) {
124 dout(1) << "ERROR: " << ec.message() << dendl;
125 return;
126 }
127 });
128 }
129
130 // Check whether we have spent enough time on this connection.
131 void check_deadline() {
132 auto self = shared_from_this();
133
134 deadline_.async_wait([self](beast::error_code ec) {
135 if (!ec) {
136 // Close socket to cancel any outstanding operation.
137 self->socket_.close(ec);
138 }
139 });
140 }
141 };
142
143 // "Loop" forever accepting new connections.
144 void http_server(tcp::acceptor &acceptor, tcp::socket &socket) {
145 acceptor.async_accept(socket, [&](beast::error_code ec) {
146 if (!ec)
147 std::make_shared<http_connection>(std::move(socket))->start();
148 http_server(acceptor, socket);
149 });
150 }
151
152 void http_server_thread_entrypoint() {
153 try {
154 std::string exporter_addr = g_conf().get_val<std::string>("exporter_addr");
155 auto const address = net::ip::make_address(exporter_addr);
156 unsigned short port = g_conf().get_val<int64_t>("exporter_http_port");
157
158 net::io_context ioc{1};
159
160 tcp::acceptor acceptor{ioc, {address, port}};
161 tcp::socket socket{ioc};
162 http_server(acceptor, socket);
163 dout(1) << "Http server running on " << exporter_addr << ":" << port << dendl;
164 ioc.run();
165 } catch (std::exception const &e) {
166 dout(1) << "Error: " << e.what() << dendl;
167 exit(EXIT_FAILURE);
168 }
169 }