NAWA  0.8
Web Application Framework for C++
http.cpp
Go to the documentation of this file.
1 
6 /*
7  * Copyright (C) 2019-2021 Tobias Flaig.
8  *
9  * This file is part of nawa.
10  *
11  * nawa is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License,
13  * version 3, as published by the Free Software Foundation.
14  *
15  * nawa is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with nawa. If not, see <https://www.gnu.org/licenses/>.
22  */
23 
24 #include <boost/network/protocol/http/client.hpp>
25 #include <catch2/catch.hpp>
26 #include <nawa/Exception.h>
29 #include <nawa/util/utils.h>
30 
31 using namespace nawa;
32 using namespace std;
33 
34 namespace http = boost::network::http;
35 
37 
38 namespace {
39  bool isEnvironmentInitialized = false;
40  Config config;
41  string port;
42  string baseUrl;
43  unique_ptr<RequestHandler> httpRequestHandler;
44 
49  bool initializeEnvironmentIfNotYetDone() {
50  if (isEnvironmentInitialized) {
51  return true;
52  }
53 
54  config = loadConfig();
55  config.insert({
56  {{"http", "reuseaddr"}, "on"},
57  {{"post", "max_size"}, "1"},
58  {{"system", "request_handler"}, "http"},
59  {{"logging", "level"}, "debug"},
60  {{"logging", "extended"}, "on"},
61  });
62 
63  port = config[{"http", "port"}].empty() ? "8080" : config[{"http", "port"}];
64  baseUrl = "http://127.0.0.1:" + port;
65 
66  REQUIRE_NOTHROW(
67  httpRequestHandler = RequestHandler::newRequestHandler([](Connection&) { return 0; }, config, 1));
68 
69  isEnvironmentInitialized = true;
70  return true;
71  }
72 }// namespace
73 
74 TEST_CASE("Basic request handling (HTTP)", "[basic][http]") {
75  REQUIRE(initializeEnvironmentIfNotYetDone());
76  // GENERATE can be used to run test cases with both HTTP and FCGI request handler
77  auto& requestHandler = httpRequestHandler;
78 
79  auto handlingFunction = [](Connection& connection) -> int {
80  connection.responseStream() << "Hello World!";
81  return 0;
82  };
83  REQUIRE_NOTHROW(
84  requestHandler->reconfigure(make_shared<HandleRequestFunctionWrapper>(handlingFunction), nullopt, config));
85  REQUIRE_NOTHROW(requestHandler->start());
86 
87  http::client client;
88  http::client::request request(baseUrl);
89  http::client::response response;
90  REQUIRE_NOTHROW(response = client.get(request));
91  CHECK(response.body() == "Hello World!");
92 }
93 
94 TEST_CASE("Environment and headers (HTTP)", "[headers][http]") {
95  REQUIRE(initializeEnvironmentIfNotYetDone());
96  auto& requestHandler = httpRequestHandler;
97 
98  auto handlingFunction = [](Connection& connection) -> int {
99  auto& resp = connection.responseStream();
100  auto& env = connection.request().env();
101 
102  resp << env.getRequestPath().size() << "\n";// [0]
103  resp << env["REMOTE_ADDR"] << "\n"; // [1]
104  resp << env["REQUEST_URI"] << "\n"; // [2]
105  resp << env["REMOTE_PORT"] << "\n"; // [3]
106  resp << env["REQUEST_METHOD"] << "\n"; // [4]
107  resp << env["SERVER_ADDR"] << "\n"; // [5]
108  resp << env["SERVER_PORT"] << "\n"; // [6]
109  resp << env["SERVER_SOFTWARE"] << "\n"; // [7]
110  resp << env["BASE_URL"] << "\n"; // [8]
111  resp << env["FULL_URL_WITH_QS"] << "\n"; // [9]
112  resp << env["FULL_URL_WITHOUT_QS"] << "\n"; // [10]
113  resp << env["host"] << "\n"; // [11]
114  resp << env["content-type"] << "\n"; // [12]
115 
116  // response headers
117  connection.setHeader("x-test-header", "test");
118  connection.setHeader("x-second-test", "test");
119  connection.unsetHeader("x-second-test");
120  return 0;
121  };
122  REQUIRE_NOTHROW(
123  requestHandler->reconfigure(make_shared<HandleRequestFunctionWrapper>(handlingFunction), nullopt, config));
124  REQUIRE_NOTHROW(requestHandler->start());
125 
126  http::client client;
127  http::client::response response;
128 
129  auto checkResponse = [&](string const& method) {
130  auto respLines = utils::splitString(response.body(), '\n');
131  REQUIRE(respLines.size() == 13);
132  CHECK(respLines[0] == "3");
133  CHECK(respLines[1] == "127.0.0.1");
134  CHECK(respLines[2] == "/tp0/tp1/test?qse0=v0&qse1=v1");
135  CHECK(respLines[4] == method);
136  CHECK(respLines[5] == "127.0.0.1");
137  CHECK(respLines[6] == port);
138  CHECK(respLines[8] == baseUrl);
139  CHECK(respLines[9] == baseUrl + "/tp0/tp1/test?qse0=v0&qse1=v1");
140  CHECK(respLines[10] == baseUrl + "/tp0/tp1/test");
141  CHECK(respLines[11] == "127.0.0.1:" + port);
142 
143  REQUIRE(response.headers().count("x-test-header") == 1);
144  CHECK(response.headers().equal_range("x-test-header").first->second == "test");
145  CHECK(response.headers().count("x-second-test") == 0);
146  };
147 
148  SECTION("GET request") {
149  http::client::request request(baseUrl + "/tp0/tp1/test?qse0=v0&qse1=v1");
150  REQUIRE_NOTHROW(response = client.get(request));
151  checkResponse("GET");
152  }
153 
154  SECTION("POST request") {
155  http::client::request request(baseUrl + "/tp0/tp1/test?qse0=v0&qse1=v1");
156  REQUIRE_NOTHROW(response = client.post(request));
157  checkResponse("POST");
158  }
159 }
160 
161 TEST_CASE("Sessions (HTTP)", "[sessions][http]") {
162  REQUIRE(initializeEnvironmentIfNotYetDone());
163  auto& requestHandler = httpRequestHandler;
164 
165  auto handlingFunction = [](Connection& connection) -> int {
166  auto& resp = connection.responseStream();
167  auto& env = connection.request().env();
168  auto& session = connection.session();
169 
170  session.start();
171  if (!session.isSet("testKey")) {
172  resp << "not set";
173  session.set("testKey", "testVal");
174  } else {
175  try {
176  resp << any_cast<string>(session["testKey"]);
177  } catch (bad_any_cast const&) {
178  resp << "bad cast";
179  }
180  }
181 
182  return 0;
183  };
184 
185  REQUIRE_NOTHROW(
186  requestHandler->reconfigure(make_shared<HandleRequestFunctionWrapper>(handlingFunction), nullopt, config));
187  REQUIRE_NOTHROW(requestHandler->start());
188 
189  http::client client;
190  http::client::response response;
191 
192  http::client::request request(baseUrl);
193  REQUIRE_NOTHROW(response = client.get(request));
194  CHECK(response.body() == "not set");
195  auto cookieIterator = response.headers().find("Set-Cookie");
196  REQUIRE(cookieIterator != response.headers().end());
197  auto parsedCookie = utils::parseCookies(cookieIterator->second);
198  auto parsedCookieIt = parsedCookie.find("SESSION");
199  REQUIRE(parsedCookieIt != parsedCookie.end());
200  string sessionId = parsedCookieIt->second;
201 
202  request << boost::network::header("Cookie", "SESSION=" + sessionId);
203  REQUIRE_NOTHROW(response = client.get(request));
204  CHECK(response.body() == "testVal");
205 }
Response object to be passed back to NAWA and accessor to the request.
Exception class that can be used by apps to catch errors resulting from nawa function calls.
Handles and serves incoming requests via the NAWA app.
void insert(std::initializer_list< std::pair< std::pair< std::string, std::string >, std::string >> init)
Definition: Config.cpp:71
static std::unique_ptr< RequestHandler > newRequestHandler(std::shared_ptr< HandleRequestFunctionWrapper > const &handleRequestFunction, Config config, int concurrency)
TEST_CASE("Basic request handling (HTTP)", "[basic][http]")
Definition: http.cpp:74
Config loadConfig()
Definition: main.cpp:34
std::unordered_multimap< std::string, std::string > parseCookies(std::string const &rawCookies)
Definition: utils.cpp:569
std::vector< std::string > splitString(std::string str, char delimiter, bool ignoreEmpty=false)
Definition: utils.cpp:448
Definition: AppInit.h:31
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.