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
// Copyright (c) 2014 Baidu, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Authors: Ge,Jun (gejun@baidu.com)
#include <gflags/gflags.h>
#include <butil/logging.h>
#include <brpc/server.h>
#include <brpc/channel.h>
#include "view.pb.h"
DEFINE_int32(port, 8888, "TCP Port of this server");
DEFINE_string(target, "", "The server to view");
// handle HTTP response of accessing builtin services of the target server.
static void handle_response(brpc::Controller* client_cntl,
std::string target,
brpc::Controller* server_cntl,
google::protobuf::Closure* server_done) {
// Copy all headers. The "Content-Length" will be overwriteen.
server_cntl->http_response() = client_cntl->http_response();
// Copy content.
server_cntl->response_attachment() = client_cntl->response_attachment();
// Insert "rpc_view: <target>" before </body> so that users are always
// visually notified with target server w/o confusions.
butil::IOBuf& content = server_cntl->response_attachment();
butil::IOBuf before_body;
if (content.cut_until(&before_body, "</body>") == 0) {
before_body.append(
"<style type=\"text/css\">\n"
".rpcviewlogo {position: fixed; bottom: 0px; right: 0px;"
" color: #ffffff; background-color: #000000; }\n"
" </style>\n"
"<span class='rpcviewlogo'> rpc_view: ");
before_body.append(target);
before_body.append(" </span></body>");
before_body.append(content);
content = before_body;
}
// Notice that we don't set RPC to failed on http errors because we
// want to pass unchanged content to the users otherwise RPC replaces
// the content with ErrorText.
if (client_cntl->Failed() &&
client_cntl->ErrorCode() != brpc::EHTTP) {
server_cntl->SetFailed(client_cntl->ErrorCode(),
"%s", client_cntl->ErrorText().c_str());
}
delete client_cntl;
server_done->Run();
}
// A http_master_service.
class ViewServiceImpl : public ViewService {
public:
ViewServiceImpl() {}
virtual ~ViewServiceImpl() {};
virtual void default_method(google::protobuf::RpcController* cntl_base,
const HttpRequest*,
HttpResponse*,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
brpc::Controller* server_cntl =
static_cast<brpc::Controller*>(cntl_base);
// Get or set target. Notice that we don't access FLAGS_target directly
// which is thread-unsafe (for string flags).
std::string target;
const std::string* newtarget =
server_cntl->http_request().uri().GetQuery("changetarget");
if (newtarget) {
if (GFLAGS_NS::SetCommandLineOption("target", newtarget->c_str()).empty()) {
server_cntl->SetFailed("Fail to change value of -target");
return;
}
target = *newtarget;
} else {
if (!GFLAGS_NS::GetCommandLineOption("target", &target)) {
server_cntl->SetFailed("Fail to get value of -target");
return;
}
}
// Create the http channel on-the-fly. Notice that we've set
// `defer_close_second' in main() so that dtor of channels do not
// close connections immediately and ad-hoc creation of channels
// often reuses the not-yet-closed connections.
brpc::Channel http_chan;
brpc::ChannelOptions http_chan_opt;
http_chan_opt.protocol = brpc::PROTOCOL_HTTP;
if (http_chan.Init(target.c_str(), &http_chan_opt) != 0) {
server_cntl->SetFailed(brpc::EINTERNAL,
"Fail to connect to %s", target.c_str());
return;
}
// Remove "Accept-Encoding". We need to insert additional texts
// before </body>, preventing the server from compressing the content
// simplifies our code. The additional bandwidth consumption shouldn't
// be an issue for infrequent checking out of builtin services pages.
server_cntl->http_request().RemoveHeader("Accept-Encoding");
brpc::Controller* client_cntl = new brpc::Controller;
client_cntl->http_request() = server_cntl->http_request();
// Remove "Host" so that RPC will laterly serialize the (correct)
// target server in.
client_cntl->http_request().RemoveHeader("host");
// Setup the URI.
const brpc::URI& server_uri = server_cntl->http_request().uri();
std::string uri = server_uri.path();
if (!server_uri.query().empty()) {
uri.push_back('?');
uri.append(server_uri.query());
}
if (!server_uri.fragment().empty()) {
uri.push_back('#');
uri.append(server_uri.fragment());
}
client_cntl->http_request().uri() = uri;
// /hotspots pages may take a long time to finish, since they all have
// query "seconds", we set the timeout to be longer than "seconds".
const std::string* seconds =
server_cntl->http_request().uri().GetQuery("seconds");
int64_t timeout_ms = 5000;
if (seconds) {
timeout_ms += atoll(seconds->c_str()) * 1000;
}
client_cntl->set_timeout_ms(timeout_ms);
// Keep content as it is.
client_cntl->request_attachment() = server_cntl->request_attachment();
http_chan.CallMethod(NULL, client_cntl, NULL, NULL,
brpc::NewCallback(
handle_response, client_cntl, target,
server_cntl, done_guard.release()));
}
};
int main(int argc, char* argv[]) {
GFLAGS_NS::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_target.empty() &&
(argc != 2 ||
GFLAGS_NS::SetCommandLineOption("target", argv[1]).empty())) {
LOG(ERROR) << "Usage: ./rpc_view <ip>:<port>";
return -1;
}
// This keeps ad-hoc creation of channels reuse previous connections.
GFLAGS_NS::SetCommandLineOption("defer_close_seconds", "10");
brpc::Server server;
server.set_version("rpc_view_server");
brpc::ServerOptions server_opt;
server_opt.http_master_service = new ViewServiceImpl;
if (server.Start(FLAGS_port, &server_opt) != 0) {
LOG(ERROR) << "Fail to start ViewServer";
return -1;
}
server.RunUntilAskedToQuit();
return 0;
}