Impala
Impalaistheopensource,nativeanalyticdatabaseforApacheHadoop.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
webserver-test.cc
Go to the documentation of this file.
1 // Copyright 2012 Cloudera Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "util/webserver.h"
16 #include "common/init.h"
17 
18 #include <gtest/gtest.h>
19 #include <string>
20 #include <boost/asio.hpp>
21 #include <boost/bind.hpp>
22 #include <boost/lexical_cast.hpp>
23 #include <gutil/strings/substitute.h>
24 
25 DECLARE_int32(webserver_port);
26 DECLARE_string(webserver_password_file);
27 
28 #include "common/names.h"
29 
30 using boost::asio::ip::tcp;
31 using namespace impala;
32 using namespace rapidjson;
33 using namespace strings;
34 
35 const string TEST_ARG = "test-arg";
36 const string SALUTATION_KEY = "Salutation";
37 const string SALUTATION_VALUE = "Hello!";
38 const string TO_ESCAPE_KEY = "ToEscape";
39 const string TO_ESCAPE_VALUE = "<script language='javascript'>";
40 const string ESCAPED_VALUE = "&lt;script language=&apos;javascript&apos;&gt;";
41 
42 // Adapted from:
43 // http://stackoverflow.com/questions/10982717/get-html-without-header-with-boostasio
44 Status HttpGet(const string& host, const int32_t& port, const string& url_path,
45  ostream* out, int expected_code = 200) {
46  try {
47  tcp::iostream request_stream;
48  request_stream.connect(host, lexical_cast<string>(port));
49  if (!request_stream) return Status("Could not connect request_stream");
50 
51  request_stream << "GET " << url_path << " HTTP/1.0\r\n";
52  request_stream << "Host: " << host << "\r\n";
53  request_stream << "Accept: */*\r\n";
54  request_stream << "Cache-Control: no-cache\r\n";
55  request_stream << "Connection: close\r\n\r\n";
56  request_stream.flush();
57 
58  string line1;
59  getline(request_stream, line1);
60  if (!request_stream) return Status("No response");
61 
62  stringstream response_stream(line1);
63  string http_version;
64  response_stream >> http_version;
65 
66  unsigned int status_code;
67  response_stream >> status_code;
68 
69  string status_message;
70  getline(response_stream,status_message);
71  if (!response_stream || http_version.substr(0,5) != "HTTP/") {
72  return Status("Malformed response");
73  }
74 
75  if (status_code != expected_code) {
76  return Status(Substitute("Unexpected status code: $0", status_code));
77  }
78 
79  (*out) << request_stream.rdbuf();
80  return Status::OK;
81  } catch (const std::exception& e){
82  return Status(e.what());
83  }
84 }
85 
86 TEST(Webserver, SmokeTest) {
87  Webserver webserver(FLAGS_webserver_port);
88  ASSERT_TRUE(webserver.Start().ok());
89 
90  stringstream contents;
91  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port, "/", &contents).ok());
92 }
93 
94 void AssertArgsCallback(bool* success, const Webserver::ArgumentMap& args,
95  Document* document) {
96  *success = args.find(TEST_ARG) != args.end();
97 }
98 
99 TEST(Webserver, ArgsTest) {
100  Webserver webserver(FLAGS_webserver_port);
101 
102  const string ARGS_TEST_PATH = "/args-test";
103  bool success = false;
104  Webserver::UrlCallback callback = bind<void>(AssertArgsCallback, &success , _1, _2);
105  webserver.RegisterUrlCallback(ARGS_TEST_PATH, "json-test.tmpl", callback);
106 
107  ASSERT_TRUE(webserver.Start().ok());
108  stringstream contents;
109  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port, ARGS_TEST_PATH, &contents).ok());
110  ASSERT_FALSE(success) << "Unexpectedly found " << TEST_ARG;
111 
112  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port,
113  Substitute("$0?$1", ARGS_TEST_PATH, TEST_ARG), &contents).ok());
114  ASSERT_TRUE(success) << "Did not find " << TEST_ARG;
115 }
116 
117 void JsonCallback(bool always_text, const Webserver::ArgumentMap& args,
118  Document* document) {
119  document->AddMember(SALUTATION_KEY.c_str(), SALUTATION_VALUE.c_str(),
120  document->GetAllocator());
121  document->AddMember(TO_ESCAPE_KEY.c_str(), TO_ESCAPE_VALUE.c_str(),
122  document->GetAllocator());
123  if (always_text) {
124  document->AddMember(Webserver::ENABLE_RAW_JSON_KEY, true, document->GetAllocator());
125  }
126 }
127 
128 TEST(Webserver, JsonTest) {
129  Webserver webserver(FLAGS_webserver_port);
130 
131  const string JSON_TEST_PATH = "/json-test";
132  const string RAW_TEXT_PATH = "/text";
133  const string NO_TEMPLATE_PATH = "/no-template";
134  Webserver::UrlCallback callback = bind<void>(JsonCallback, false, _1, _2);
135  webserver.RegisterUrlCallback(JSON_TEST_PATH, "json-test.tmpl", callback);
136  webserver.RegisterUrlCallback(NO_TEMPLATE_PATH, "doesnt-exist.tmpl", callback);
137 
138  Webserver::UrlCallback text_callback = bind<void>(JsonCallback, true, _1, _2);
139  webserver.RegisterUrlCallback(RAW_TEXT_PATH, "json-test.tmpl", text_callback);
140  ASSERT_TRUE(webserver.Start().ok());
141 
142  stringstream contents;
143  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port, JSON_TEST_PATH, &contents).ok());
144  ASSERT_TRUE(contents.str().find(SALUTATION_VALUE) != string::npos);
145  ASSERT_TRUE(contents.str().find(SALUTATION_KEY) == string::npos);
146 
147  stringstream json_contents;
148  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port,
149  Substitute("$0?json", JSON_TEST_PATH), &json_contents).ok());
150  ASSERT_TRUE(json_contents.str().find("\"Salutation\": \"Hello!\"") != string::npos);
151 
152  stringstream error_contents;
153  ASSERT_TRUE(
154  HttpGet("localhost", FLAGS_webserver_port, NO_TEMPLATE_PATH, &error_contents).ok());
155  ASSERT_TRUE(error_contents.str().find("Could not open template: ") != string::npos);
156 
157  // Adding ?raw should send text
158  stringstream raw_contents;
159  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port,
160  Substitute("$0?raw", JSON_TEST_PATH), &raw_contents).ok());
161  ASSERT_TRUE(raw_contents.str().find("text/plain") != string::npos);
162 
163  // Any callback that includes ENABLE_RAW_JSON_KEY should always return text.
164  stringstream raw_cb_contents;
165  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port, RAW_TEXT_PATH,
166  &raw_cb_contents).ok());
167  ASSERT_TRUE(raw_cb_contents.str().find("text/plain") != string::npos);
168 }
169 
170 TEST(Webserver, EscapingTest) {
171  Webserver webserver(FLAGS_webserver_port);
172 
173  const string JSON_TEST_PATH = "/json-test";
174  Webserver::UrlCallback callback = bind<void>(JsonCallback, false, _1, _2);
175  webserver.RegisterUrlCallback(JSON_TEST_PATH, "json-test.tmpl", callback);
176  ASSERT_TRUE(webserver.Start().ok());
177  stringstream contents;
178  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port, JSON_TEST_PATH, &contents).ok());
179  ASSERT_TRUE(contents.str().find(ESCAPED_VALUE) != string::npos);
180  ASSERT_TRUE(contents.str().find(TO_ESCAPE_VALUE) == string::npos);
181 }
182 
183 TEST(Webserver, EscapeErrorUriTest) {
184  Webserver webserver(FLAGS_webserver_port);
185  ASSERT_TRUE(webserver.Start().ok());
186  stringstream contents;
187  ASSERT_TRUE(HttpGet("localhost", FLAGS_webserver_port,
188  "/dont-exist<script>alert(42);</script>", &contents, 404).ok());
189  ASSERT_EQ(contents.str().find("<script>alert(42);</script>"), string::npos);
190  ASSERT_TRUE(contents.str().find("dont-exist&lt;script&gt;alert(42);&lt;/script&gt;") !=
191  string::npos);
192 }
193 
194 TEST(Webserver, StartWithPasswordFileTest) {
195  stringstream password_file;
196  password_file << getenv("IMPALA_HOME") << "/be/src/testutil/htpasswd";
197  FLAGS_webserver_password_file = password_file.str();
198 
199  Webserver webserver(FLAGS_webserver_port);
200  ASSERT_TRUE(webserver.Start().ok());
201 
202  // Don't expect HTTP requests to work without a password
203  stringstream contents;
204  ASSERT_FALSE(HttpGet("localhost", FLAGS_webserver_port, "/", &contents).ok());
205 }
206 
207 TEST(Webserver, StartWithMissingPasswordFileTest) {
208  stringstream password_file;
209  password_file << getenv("IMPALA_HOME") << "/be/src/testutil/doesntexist";
210  FLAGS_webserver_password_file = password_file.str();
211 
212  Webserver webserver(FLAGS_webserver_port);
213  ASSERT_FALSE(webserver.Start().ok());
214 }
215 
216 int main(int argc, char **argv) {
217  InitCommonRuntime(argc, argv, false, TestInfo::BE_TEST);
218  ::testing::InitGoogleTest(&argc, argv);
219  return RUN_ALL_TESTS();
220 }
const string TO_ESCAPE_VALUE
static const char * ENABLE_RAW_JSON_KEY
Definition: webserver.h:43
const string TEST_ARG
const string TO_ESCAPE_KEY
boost::function< void(const ArgumentMap &args, rapidjson::Document *json)> UrlCallback
Definition: webserver.h:38
DECLARE_int32(webserver_port)
void InitCommonRuntime(int argc, char **argv, bool init_jvm, TestInfo::Mode m=TestInfo::NON_TEST)
Definition: init.cc:122
TEST(AtomicTest, Basic)
Definition: atomic-test.cc:28
void RegisterUrlCallback(const std::string &path, const std::string &template_filename, const UrlCallback &callback, bool is_on_nav_bar=true)
Only one callback may be registered per URL.
Definition: webserver.cc:412
std::map< std::string, std::string > ArgumentMap
Definition: webserver.h:36
const string SALUTATION_KEY
Status HttpGet(const string &host, const int32_t &port, const string &url_path, ostream *out, int expected_code=200)
const string SALUTATION_VALUE
void JsonCallback(bool always_text, const Webserver::ArgumentMap &args, Document *document)
DECLARE_string(webserver_password_file)
static const Status OK
Definition: status.h:87
int main(int argc, char **argv)
const string ESCAPED_VALUE
bool ok() const
Definition: status.h:172
void AssertArgsCallback(bool *success, const Webserver::ArgumentMap &args, Document *document)