NAWA 0.9
Web Application Framework for C++
http.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2019-2022 Tobias Flaig.
3 *
4 * This file is part of nawa.
5 *
6 * nawa is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License,
8 * version 3, as published by the Free Software Foundation.
9 *
10 * nawa is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with nawa. If not, see <https://www.gnu.org/licenses/>.
17 */
18
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
31using namespace nawa;
32using namespace std;
33
34namespace http = boost::network::http;
35
37
38namespace {
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
74TEST_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
94TEST_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
161TEST_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
TEST_CASE("Basic request handling (HTTP)", "[basic][http]")
Definition: http.cpp:74
Config loadConfig()
Definition: main.cpp:34
std::vector< std::string > splitString(std::string str, char delimiter, bool ignoreEmpty=false)
Definition: utils.cpp:448
std::unordered_multimap< std::string, std::string > parseCookies(std::string const &rawCookies)
Definition: utils.cpp:569
Definition: AppInit.h:31
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.