NAWA 0.9
Web Application Framework for C++
FastcgiRequestHandler.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 <fastcgi++/log.hpp>
25#include <fastcgi++/manager.hpp>
26#include <fastcgi++/request.hpp>
27#include <nawa/Exception.h>
32#include <nawa/logging/Log.h>
33#include <nawa/util/utils.h>
34
35using namespace nawa;
36using namespace std;
37
38namespace {
39 Log logger("fastcgi");
40
44 enum class RawPostAccess {
45 NEVER,
46 NONSTANDARD,
47 ALWAYS
48 };
49
50 class FastcgippRequestAdapter : public Fastcgipp::Request<char> {
51 shared_ptr<string> rawPost;
52
53 public:
58 FastcgippRequestAdapter() : Fastcgipp::Request<char>() {}
59
64 bool response() override;
65
71 bool inProcessor() override;
72 };
73}// namespace
74
75bool FastcgippRequestAdapter::response() {
76 RequestInitContainer requestInit;
77 auto requestHandler = any_cast<RequestHandler*>(m_externalObject);
78
79 // fill environment
80 {
81 auto const& renv = environment();
82 auto renvp = [&](string const& k) {
83 return renv.parameters.count(k) ? renv.parameters.at(k) : string();
84 };
85 requestInit.environment = {
86 {"content-type", renvp("CONTENT_TYPE")},
87 };
88
89 {
90 // the base URL is the URL without the request URI, e.g., https://www.example.com
91 stringstream baseUrl;
92 auto https = renv.parameters.count("HTTPS");
93 baseUrl << (https ? "https://" : "http://")
94 << renvp("HTTP_HOST");
95 auto baseUrlStr = baseUrl.str();
96 requestInit.environment["BASE_URL"] = baseUrlStr;
97 auto requestUri = renvp("REQUEST_URI");
98
99 // fullUrlWithQS is the full URL, e.g., https://www.example.com/test?a=b&c=d
100 requestInit.environment["FULL_URL_WITH_QS"] = baseUrlStr + requestUri;
101
102 // fullUrlWithoutQS is the full URL without query string, e.g., https://www.example.com/test
103 baseUrl << requestUri.substr(0, requestUri.find_first_of('?'));
104 requestInit.environment["FULL_URL_WITHOUT_QS"] = baseUrl.str();
105 }
106
107 for (auto const& [k, v] : renv.parameters) {
108 string envKey;
109 // parameters starting with HTTP_ are usually HTTP headers
110 // convert them so they should match their original name (lowercase), e.g., HTTP_USER_AGENT => user-agent
111 if (k.substr(0, 5) == "HTTP_") {
112 envKey = utils::stringReplace(utils::toLowercase(k.substr(5)), {{'_', '-'}});
113 } else {
114 // other FastCGI parameters are used as they are
115 envKey = k;
116 }
117 if (requestInit.environment.count(envKey) == 0) {
118 requestInit.environment[envKey] = v;
119 }
120 }
121
122 // GET, POST, COOKIE vars, raw POST, POST content type
123 requestInit.getVars = utils::toUnorderedMultimap(renv.gets);
124 requestInit.postVars = utils::toUnorderedMultimap(renv.posts);
125 requestInit.cookieVars = utils::toUnorderedMultimap(renv.cookies);
126 requestInit.rawPost = rawPost;
127 requestInit.postContentType = renv.contentType;
128
129 // POST files
130 for (auto const& [k, fcgiFile] : renv.files) {
131 requestInit.postFiles.insert({k, File(fcgiFile.data, fcgiFile.size).filename(fcgiFile.filename).contentType(fcgiFile.contentType)});
132 }
133 }
134
135 ConnectionInitContainer connectionInit;
136 connectionInit.requestInit = std::move(requestInit);
137 connectionInit.config = *requestHandler->getConfig();
138
139 connectionInit.flushCallback = [this](FlushCallbackContainer flushInfo) {
140 string response;
141 if (!flushInfo.flushedBefore) {
142 response = "status: " + flushInfo.getStatusString() + "\r\n";
143 }
144 response += flushInfo.getFullHttp();
145 dump(response.c_str(), response.size());
146 };
147
148 Connection connection(connectionInit);
149 requestHandler->handleRequest(connection);
150 connection.flushResponse();
151
152 return true;
153}
154
155bool FastcgippRequestAdapter::inProcessor() {
156 auto requestHandler = any_cast<RequestHandler*>(m_externalObject);
157 auto postContentType = environment().contentType;
158 auto configPtr = requestHandler->getConfig();
159
160 string rawPostAccess = (*configPtr)[{"post", "raw_access"}];
161 if (postContentType.empty() || rawPostAccess == "never" ||
162 (rawPostAccess != "always" && (postContentType == "multipart/form-data" || postContentType == "application/x-www-form-urlencoded"))) {
163 return false;
164 }
165
166 auto postBuffer = environment().postBuffer();
167 rawPost = make_shared<string>(postBuffer.data(), postBuffer.size());
168 return false;
169}
170
171struct FastcgiRequestHandler::Data {
172 unique_ptr<Fastcgipp::Manager<FastcgippRequestAdapter>> fastcgippManager;
173 bool requestHandlingActive = false;
174 bool joined = false;
175};
176
177FastcgiRequestHandler::FastcgiRequestHandler(std::shared_ptr<HandleRequestFunctionWrapper> handleRequestFunction,
178 Config config, int concurrency) {
179 data = make_unique<Data>();
180
181 setAppRequestHandler(std::move(handleRequestFunction));
182 setConfig(std::move(config));
183 auto configPtr = getConfig();
184
185 size_t postMax = 0;
186 try {
187 postMax = configPtr->isSet({"post", "max_size"})
188 ? static_cast<size_t>(stoul((*configPtr)[{"post", "max_size"}])) * 1024
189 : 0;
190 } catch (invalid_argument& e) {
191 NLOG_WARNING(logger, "WARNING: Invalid value given for post/max_size given in the config file.")
192 }
193
194 // set up fastcgilite logging
195 Fastcgipp::Logging::addHeader = false;
196 Fastcgipp::Logging::logFunction = [&](string const& msg, Fastcgipp::Logging::Level level) {
197 Log::Level nawaLevel;
198 switch (level) {
199 case Fastcgipp::Logging::INFO:
200 nawaLevel = Log::Level::INFORMATIONAL;
201 break;
202 case Fastcgipp::Logging::FAIL:
203 case Fastcgipp::Logging::ERROR:
204 nawaLevel = Log::Level::ERROR;
205 break;
206 case Fastcgipp::Logging::WARNING:
207 nawaLevel = Log::Level::WARNING;
208 break;
209 case Fastcgipp::Logging::DEBUG:
210 case Fastcgipp::Logging::DIAG:
211 nawaLevel = Log::Level::DEBUG;
212 break;
213 default:
214 nawaLevel = Log::Level::INFORMATIONAL;
215 }
216 logger.write(msg, nawaLevel);
217 };
218
219 data->fastcgippManager = make_unique<Fastcgipp::Manager<FastcgippRequestAdapter>>(concurrency, postMax,
220 static_cast<RequestHandler*>(this));
221
222 // socket handling
223 string mode = (*configPtr)[{"fastcgi", "mode"}];
224 if (mode == "tcp") {
225 auto fastcgiListen = (*configPtr)[{"fastcgi", "listen"}];
226 auto fastcgiPort = (*configPtr)[{"fastcgi", "port"}];
227 if (fastcgiListen.empty())
228 fastcgiListen = "127.0.0.1";
229 char const* fastcgiListenC = fastcgiListen.c_str();
230 if (fastcgiListen == "all")
231 fastcgiListenC = nullptr;
232 if (fastcgiPort.empty())
233 fastcgiPort = "8000";
234 if (!data->fastcgippManager->listen(fastcgiListenC, fastcgiPort.c_str())) {
235 throw Exception(__PRETTY_FUNCTION__, 1,
236 "Could not create TCP socket for FastCGI.");
237 }
238 } else if (mode == "unix") {
239 uint32_t permissions = 0xffffffffUL;
240 string permStr = (*configPtr)[{"fastcgi", "permissions"}];
241 // convert the value from the config file
242 if (!permStr.empty()) {
243 char const* psptr = permStr.c_str();
244 char* endptr;
245 long perm = strtol(psptr, &endptr, 8);
246 if (*endptr == '\0') {
247 permissions = (uint32_t) perm;
248 }
249 }
250
251 auto fastcgiSocketPath = (*configPtr)[{"fastcgi", "path"}];
252 if (fastcgiSocketPath.empty()) {
253 fastcgiSocketPath = "/etc/nawarun/sock.d/nawarun.sock";
254 }
255 auto fastcgiOwner = (*configPtr)[{"fastcgi", "owner"}];
256 auto fastcgiGroup = (*configPtr)[{"fastcgi", "group"}];
257
258 if (!data->fastcgippManager->listen(fastcgiSocketPath.c_str(), permissions,
259 fastcgiOwner.empty() ? nullptr : fastcgiOwner.c_str(),
260 fastcgiGroup.empty() ? nullptr : fastcgiGroup.c_str())) {
261 throw Exception(__PRETTY_FUNCTION__, 2,
262 "Could not create UNIX socket for FastCGI.");
263 }
264 } else {
265 throw Exception(__PRETTY_FUNCTION__, 3,
266 "Unknown FastCGI socket mode in configuration.");
267 }
268
269 // tell fastcgi to use SO_REUSEADDR if enabled in config
270 if ((*configPtr)[{"fastcgi", "reuseaddr"}] != "off") {
271 data->fastcgippManager->reuseAddress(true);
272 }
273}
274
276 if (data->requestHandlingActive && !data->joined) {
277 data->fastcgippManager->terminate();
278 }
279 if (!data->joined) {
280 data->fastcgippManager->join();
281 data->fastcgippManager.reset(nullptr);
282 }
283}
284
286 if (data->requestHandlingActive) {
287 return;
288 }
289 if (data->joined) {
290 throw Exception(__PRETTY_FUNCTION__, 10, "FastcgiRequestHandler was already joined.");
291 }
292 if (data->fastcgippManager) {
293 try {
294 data->fastcgippManager->start();
295 data->requestHandlingActive = true;
296 } catch (...) {
297 throw Exception(__PRETTY_FUNCTION__, 1,
298 "An unknown error occurred during start of request handling.");
299 }
300 } else {
301 throw Exception(__PRETTY_FUNCTION__, 2, "FastCGI manager is not available.");
302 }
303}
304
306 if (data->joined) {
307 return;
308 }
309 if (data->fastcgippManager) {
310 data->fastcgippManager->stop();
311 }
312}
313
315 if (data->joined) {
316 return;
317 }
318 if (data->fastcgippManager) {
319 data->fastcgippManager->terminate();
320 }
321}
322
324 if (data->joined) {
325 return;
326 }
327 if (data->fastcgippManager) {
328 data->fastcgippManager->join();
329 data->joined = true;
330 data->fastcgippManager.reset(nullptr);
331 }
332}
Container used by request handlers to initiate the nawa::Connection object.
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.
Class which connects NAWA to the fastcgi++ library.
Simple class for (not (yet) thread-safe) logging to stderr or to any other output stream.
#define NLOG_WARNING(Logger, Message)
Definition: Log.h:189
Handles and serves incoming requests via the NAWA app.
void terminate() noexcept override
std::string & filename() noexcept
Definition: Log.h:38
Level
Definition: Log.h:47
std::shared_ptr< Config const > getConfig() const noexcept
void setConfig(Config config) noexcept
void setAppRequestHandler(std::shared_ptr< HandleRequestFunctionWrapper > handleRequestFunction) noexcept
std::string toLowercase(std::string s)
Definition: utils.cpp:256
std::unordered_multimap< KeyType, ValueType > toUnorderedMultimap(MapType< KeyType, ValueType, Args... > inputMap)
Definition: utils.h:214
std::string stringReplace(std::string input, std::unordered_map< char, char > const &patterns)
Definition: utils.cpp:514
Definition: AppInit.h:31
std::unordered_multimap< std::string, std::string > getVars
std::unordered_map< std::string, std::string > environment
std::unordered_multimap< std::string, std::string > cookieVars
std::unordered_multimap< std::string, File > postFiles
std::unordered_multimap< std::string, std::string > postVars
std::shared_ptr< std::string > rawPost
Contains useful functions that improve the readability and facilitate maintenance of the NAWA code.