drogon
C++14/17-based HTTP application framework
Loading...
Searching...
No Matches
HttpBinder.h
Go to the documentation of this file.
1
14
17
18#pragma once
19
20#include <drogon/exports.h>
21#include <drogon/DrClassMap.h>
22#include <drogon/DrObject.h>
23#include <drogon/utils/FunctionTraits.h>
25#include <drogon/HttpRequest.h>
26#include <deque>
27#include <memory>
28#include <sstream>
29#include <string>
30
31namespace drogon
32{
33namespace internal
34{
35// we only accept value type or const lreference type or right reference type as
36// the handle method parameters type
37template <typename T>
39{
40 static const bool isValid = true;
41};
42
43template <typename T>
45{
46 static const bool isValid = false;
47};
48
49template <typename T>
51{
52 static const bool isValid = false;
53};
54
55template <typename T>
57{
58 static const bool isValid = true;
59};
60
61template <typename T>
62struct BinderArgTypeTraits<const T &&>
63{
64 static const bool isValid = false;
65};
66
67template <typename T>
68struct BinderArgTypeTraits<const T &>
69{
70 static const bool isValid = true;
71};
72
73template <typename T>
74T getHandlerArgumentValue(std::string &&p)
75{
77 {
78 return T(std::move(p));
79 }
81 {
82 T value{T()};
83 if (!p.empty())
84 {
85 std::stringstream ss(std::move(p));
86 ss >> value;
87 }
88 return value;
89 }
90 else if constexpr (internal::CanConvertFromString<T>::value)
91 {
92 T value;
93 value = p;
94 return value;
95 }
96 else
97 {
98 LOG_ERROR << "Can't convert string to type " << typeid(T).name();
99 return T();
100 }
101}
102
103template <>
104inline std::string getHandlerArgumentValue<std::string>(std::string &&p)
105{
106 return std::move(p);
107}
108
109template <>
110inline int getHandlerArgumentValue<int>(std::string &&p)
111{
112 return std::stoi(p);
113}
114
115template <>
116inline long getHandlerArgumentValue<long>(std::string &&p)
117{
118 return std::stol(p);
119}
120
121template <>
122inline long long getHandlerArgumentValue<long long>(std::string &&p)
123{
124 return std::stoll(p);
125}
126
127template <>
128inline unsigned long getHandlerArgumentValue<unsigned long>(std::string &&p)
129{
130 return std::stoul(p);
131}
132
133template <>
134inline unsigned long long getHandlerArgumentValue<unsigned long long>(
135 std::string &&p)
136{
137 return std::stoull(p);
138}
139
140template <>
141inline float getHandlerArgumentValue<float>(std::string &&p)
142{
143 return std::stof(p);
144}
145
146template <>
147inline double getHandlerArgumentValue<double>(std::string &&p)
148{
149 return std::stod(p);
150}
151
152template <>
153inline long double getHandlerArgumentValue<long double>(std::string &&p)
154{
155 return std::stold(p);
156}
157
159{
160 public:
161 virtual void handleHttpRequest(
162 std::deque<std::string> &pathArguments,
163 const HttpRequestPtr &req,
164 std::function<void(const HttpResponsePtr &)> &&callback) = 0;
165 virtual size_t paramCount() = 0;
166 virtual const std::string &handlerName() const = 0;
167 virtual bool isStreamHandler() = 0;
168
169 virtual ~HttpBinderBase()
170 {
171 }
172};
173
174template <typename T>
175T &getControllerObj()
176{
177 // Initialization of function-local statics is guaranteed to occur only once
178 // even when
179 // called from multiple threads, and may be more efficient than the
180 // equivalent code using std::call_once.
181 static T obj;
182 return obj;
183}
184
185DROGON_EXPORT void handleException(
186 const std::exception &,
187 const HttpRequestPtr &,
188 std::function<void(const HttpResponsePtr &)> &&);
189
190using HttpBinderBasePtr = std::shared_ptr<HttpBinderBase>;
191
192template <typename FUNCTION>
193class HttpBinder : public HttpBinderBase
194{
195 public:
196 using traits = FunctionTraits<FUNCTION>;
197 using FunctionType = FUNCTION;
198
199 void handleHttpRequest(
200 std::deque<std::string> &pathArguments,
201 const HttpRequestPtr &req,
202 std::function<void(const HttpResponsePtr &)> &&callback) override
203 {
204 if (!pathArguments.empty())
205 {
206 std::vector<std::string> args;
207 args.reserve(pathArguments.size());
208 for (auto &arg : pathArguments)
209 {
210 args.emplace_back(arg);
211 }
212 req->setRoutingParameters(std::move(args));
213 }
214 run(pathArguments, req, std::move(callback));
215 }
216
217 size_t paramCount() override
218 {
219 return traits::arity;
220 }
221
222 bool isStreamHandler() override
223 {
224 return traits::isStreamHandler;
225 }
226
227 HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
228 {
229 static_assert(traits::isHTTPFunction,
230 "Your API handler function interface is wrong!");
231 handlerName_ = DrClassMap::demangle(typeid(FUNCTION).name());
232 }
233
234 void test()
235 {
236 std::cout << "argument_count=" << argument_count << " "
237 << traits::isHTTPFunction << std::endl;
238 }
239
240 const std::string &handlerName() const override
241 {
242 return handlerName_;
243 }
244
245 template <bool isClassFunction = traits::isClassFunction,
246 bool isDrObjectClass = traits::isDrObjectClass>
247 void createHandlerInstance()
248 {
249 if constexpr (isClassFunction)
250 {
251 if constexpr (isDrObjectClass)
252 {
253 auto objPtr = DrClassMap::getSingleInstance<
254 typename traits::class_type>();
255 LOG_TRACE << "create handler class object: " << objPtr.get();
256 }
257 else
258 {
259 auto &obj = getControllerObj<typename traits::class_type>();
260 LOG_TRACE << "create handler class object: " << &obj;
261 }
262 }
263 }
264
265 private:
266 FUNCTION func_;
267 template <std::size_t Index>
268 using nth_argument_type = typename traits::template argument<Index>;
269
270 static const size_t argument_count = traits::arity;
271 std::string handlerName_;
272
273 template <typename... Values,
274 std::size_t Boundary = argument_count,
275 bool isStreamHandler = traits::isStreamHandler,
276 bool isCoroutine = traits::isCoroutine>
277 void run(std::deque<std::string> &pathArguments,
278 const HttpRequestPtr &req,
279 std::function<void(const HttpResponsePtr &)> &&callback,
280 Values &&...values)
281 {
282 if constexpr (sizeof...(Values) < Boundary)
283 { // Call this function recursively until parameter's count equals to
284 // the count of target function parameters
285 static_assert(
287 nth_argument_type<sizeof...(Values)>>::isValid,
288 "your handler argument type must be value type or const left "
289 "reference type or right reference type");
290 using ValueType = std::remove_cv_t<
291 std::remove_reference_t<nth_argument_type<sizeof...(Values)>>>;
292 if (!pathArguments.empty())
293 {
294 std::string v{std::move(pathArguments.front())};
295 pathArguments.pop_front();
296 try
297 {
298 if (!v.empty())
299 {
300 auto value =
301 getHandlerArgumentValue<ValueType>(std::move(v));
302 run(pathArguments,
303 req,
304 std::move(callback),
305 std::forward<Values>(values)...,
306 std::move(value));
307 return;
308 }
309 }
310 catch (const std::exception &e)
311 {
312 handleException(e, req, std::move(callback));
313 return;
314 }
315 }
316 else
317 {
318 try
319 {
320 auto value = req->as<ValueType>();
321 run(pathArguments,
322 req,
323 std::move(callback),
324 std::forward<Values>(values)...,
325 std::move(value));
326 return;
327 }
328 catch (const std::exception &e)
329 {
330 handleException(e, req, std::move(callback));
331 return;
332 }
333 catch (...)
334 {
335 LOG_ERROR << "Exception not derived from std::exception";
336 return;
337 }
338 }
339
340 run(pathArguments,
341 req,
342 std::move(callback),
343 std::forward<Values>(values)...,
344 ValueType());
345 }
346 else if constexpr (sizeof...(Values) == Boundary)
347 {
348 if constexpr (!isCoroutine)
349 {
350 try
351 {
352 // Explicit copy because `callFunction` moves it
353 auto cb = callback;
354 if constexpr (isStreamHandler)
355 {
356 callFunction(req,
357 createRequestStream(req),
358 cb,
359 std::move(values)...);
360 }
361 else
362 {
363 callFunction(req, cb, std::move(values)...);
364 }
365 }
366 catch (const std::exception &except)
367 {
368 handleException(except, req, std::move(callback));
369 }
370 catch (...)
371 {
372 LOG_ERROR << "Exception not derived from std::exception";
373 return;
374 }
375 }
376#ifdef __cpp_impl_coroutine
377 else
378 {
379 static_assert(!isStreamHandler);
380 [this](HttpRequestPtr req,
381 std::function<void(const HttpResponsePtr &)> callback,
382 Values &&...values) -> AsyncTask {
383 try
384 {
385 if constexpr (std::is_same_v<
386 AsyncTask,
387 typename traits::return_type>)
388 {
389 // Explicit copy because `callFunction` moves it
390 auto cb = callback;
391 callFunction(req, cb, std::move(values)...);
392 }
393 else if constexpr (std::is_same_v<
394 Task<>,
395 typename traits::return_type>)
396 {
397 // Explicit copy because `callFunction` moves it
398 auto cb = callback;
399 co_await callFunction(req,
400 cb,
401 std::move(values)...);
402 }
403 else if constexpr (std::is_same_v<
405 typename traits::return_type>)
406 {
407 auto resp =
408 co_await callFunction(req,
409 std::move(values)...);
410 callback(std::move(resp));
411 }
412 }
413 catch (const std::exception &except)
414 {
415 handleException(except, req, std::move(callback));
416 }
417 catch (...)
418 {
419 LOG_ERROR
420 << "Exception not derived from std::exception";
421 }
422 co_return;
423 }(req, std::move(callback), std::move(values)...);
424 }
425#endif
426 }
427 }
428
429 template <typename... Values,
430 bool isClassFunction = traits::isClassFunction,
431 bool isDrObjectClass = traits::isDrObjectClass,
432 bool isNormal = std::is_same_v<typename traits::first_param_type,
433 HttpRequestPtr>>
434 typename traits::return_type callFunction(const HttpRequestPtr &req,
435 Values &&...values)
436 {
437 if constexpr (isNormal)
438 {
439 if constexpr (isClassFunction)
440 {
441 if constexpr (!isDrObjectClass)
442 {
443 static auto &obj =
444 getControllerObj<typename traits::class_type>();
445 return (obj.*func_)(req, std::move(values)...);
446 }
447 else
448 {
449 static auto objPtr = DrClassMap::getSingleInstance<
450 typename traits::class_type>();
451 return (*objPtr.*func_)(req, std::move(values)...);
452 }
453 }
454 else
455 {
456 return func_(req, std::move(values)...);
457 }
458 }
459 else
460 {
461 if constexpr (isClassFunction)
462 {
463 if constexpr (!isDrObjectClass)
464 {
465 static auto &obj =
466 getControllerObj<typename traits::class_type>();
467 return (obj.*func_)((*req), std::move(values)...);
468 }
469 else
470 {
471 static auto objPtr = DrClassMap::getSingleInstance<
472 typename traits::class_type>();
473 return (*objPtr.*func_)((*req), std::move(values)...);
474 }
475 }
476 else
477 {
478 return func_((*req), std::move(values)...);
479 }
480 }
481 }
482};
483
484} // namespace internal
485} // namespace drogon
static std::string demangle(const char *mangled_name)
demangle the type name which is returned by typeid(T).name().
Definition DrClassMap.h:116
static const std::shared_ptr< DrObjectBase > & getSingleInstance(const std::string &className)
Get the singleton object of the class named by className.
Definition HttpBinder.h:159
Drogon Test is a minimal effort test framework developed because the major C++ test frameworks doesn'...
Definition Attribute.h:23
Definition coroutine.h:355
Definition coroutine.h:161
Definition HttpBinder.h:39
Definition FunctionTraits.h:132