drogonframework / drogon

Drogon: A C++14/17/20 based HTTP web application framework running on Linux/macOS/Unix/Windows
MIT License
11.48k stars 1.1k forks source link

Add CSP Layouts #338

Open rbugajewski opened 4 years ago

rbugajewski commented 4 years ago

It is currently possible to concatenate multiple CSP files into one view by using the <%view%> directive. Whenever I have a website with the same header & footer, but different main content, I can add a new CSP file for the main content, then integrate the header & footer with two <%view%> statements.

The problem here is that now I have to keep in mind to add both, the header, and the footer, to every page. Another issue is that now, after the header & footer are separated, they do no longer represent a full HTML file.

I would like to see some support for layouts (which would ideally reside in a separate directory). In this case I could say that a specific page extends from a layout (which is a CSP page by itself). The layout page should be an (almost) well formed HTML page that has a placeholder / statement where the concrete page will be substituted.

an-tao commented 4 years ago

@rbugajewski , Hey, I have read those issues from you, thanks for all the suggestions. As you said, I mainly use drogon in REST API backends, so its support for views is still very primitive, and I will carefully consider these suggestions, thank you very much.

an-tao commented 4 years ago

@rbugajewski I added layouts support for csp files. You could use the <%layout layout_view_name%> tag to specify a layout, for example:

hw.csp

<%layout global_layout%>
Hello world !

global_layout.csp

<header>
<h1>Welcome to my homepage</h1>
</header>
[[]]
<footer>
  <p>Contact information: <a href="antao2002@gmail.com">antao2002@gmail.com</a>.</p>
</footer>

The [[]] is the placeholder for subviews that uses the layout, and of cause, layouts can be nested.

rbugajewski commented 4 years ago

Thanks for adding support for layouts, this is a well-maintained project.

Layouts work on my setup, but I’m a little bit confused and not sure if this is something Drogon-related or if I’ve screwed something up.

I have added the following directive to one of my template files (Index.csp):

<%layout Layout %>

I also have a filed called views/components/Layout.csp.

The problem I see is that the generated Index class makes my layout name lowercase (std::string layoutName{"layout"};), and then crashes on return templ->genText(data);, because of the missing layout called layout.

Is it expected behavior that layouts always have to be lowercase?

an-tao commented 4 years ago

Sorry, this is a wrong conversion. I fixed it.

rbugajewski commented 4 years ago

I just discovered that the dynamic view loading mechanism doesn’t generate the layout portion for the templates while the regular compilation works. Is this something you can confirm or did I break my local setup, @an-tao?

an-tao commented 4 years ago

You could find the code in templates:

auto static templ = DrTemplateBase::newTemplate(layoutName);

Sorry, I didn't think carefully about the scenario where the layout view is dynamically loaded, the static key-word is for performance reason and this means the layout template is loaded only once. I'll fix it. @rbugajewski

an-tao commented 4 years ago

@rbugajewski , please check the PR #378 , thanks.

rbugajewski commented 4 years ago

@an-tao I checked out the dev branch, but I don’t think that the static declaration causes this behavior. What I observe is that the layout generation is completely skipped on my machine when using dynamic view generation. The correct output is:

std::string layoutName{"Layout"};

While the dynamic compilation generates:

Index_tmp_stream << "<%layout Layout %>\n";

Furthermore the layoutName.empty() check is missing and also the whole else block:

{
auto templ = DrTemplateBase::newTemplate(layoutName);
if(!templ) return "";
HttpViewData data;
auto str = std::move(Index_tmp_stream.str());
if(!str.empty() && str[str.length()-1] == '\n') str.resize(str.length()-1);
data[""] = std::move(str);
return templ->genText(data);
}

My wild guess is that this is somehow configuration or path related.

By the way: What is the easiest way / workflow to debug the framework itself?

an-tao commented 4 years ago

@an-tao I checked out the dev branch, but I don’t think that the static declaration causes this behavior. What I observe is that the layout generation is completely skipped on my machine when using dynamic view generation. The correct output is:

std::string layoutName{"Layout"};

While the dynamic compilation generates:

Index_tmp_stream << "<%layout Layout %>\n";

This line means that the drogon_ctl used by 'dynamic loading component is a old version one, please make sure the drogon_ctl installed in your system is the latest.

Furthermore the layoutName.empty() check is missing and also the whole else block:

{
auto templ = DrTemplateBase::newTemplate(layoutName);
if(!templ) return "";
HttpViewData data;
auto str = std::move(Index_tmp_stream.str());
if(!str.empty() && str[str.length()-1] == '\n') str.resize(str.length()-1);
data[""] = std::move(str);
return templ->genText(data);
}

My wild guess is that this is somehow configuration or path related.

By the way: What is the easiest way / workflow to debug the framework itself?

You could configure drogon with cmake .. -DCMAKE_BUILD_TYPE=debug and then drogon can be debuged drogon step by step. Or a more effective way is to make drogon a submodule of your project so you can debug it more easily.

@rbugajewski

rbugajewski commented 4 years ago

This line means that the drogon_ctl used by 'dynamic loading component is a old version one, please make sure the drogon_ctl installed in your system is the latest.

Stupid me 😅 This was indeed the case, I forgot to copy the new executable to $PATH.

You could configure drogon with cmake .. -DCMAKE_BUILD_TYPE=debug and then drogon can be debuged drogon step by step. Or a more effective way is to make drogon a submodule of your project so you can debug it more easily.

Thanks for the info, I’ll have to check my IDE setup, because my breakpoints currently disappear.

I have copied the executable and now the layout gets processed as expected, but I got a crash after saving & reloading the index page:

std::__1::__function::__value_func<drogon::DrObjectBase* ()>::operator()() const functional:1799
std::__1::function<drogon::DrObjectBase* ()>::operator()() const functional:2347
drogon::DrClassMap::newObject(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) DrClassMap.cc:54
drogon::DrTemplateBase::newTemplate(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >) DrTemplateBase.cc:35
drogon::genHttpResponse(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, drogon::HttpViewData const&) HttpResponseImpl.cc:36
drogon::HttpResponse::newHttpViewResponse(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, drogon::HttpViewData const&) HttpResponseImpl.cc:143
IndexController::asyncHandleHttpRequest(std::__1::shared_ptr<drogon::HttpRequest> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) IndexController.cc:6
drogon::HttpSimpleControllersRouter::doControllerHandler(std::__1::shared_ptr<drogon::HttpSimpleControllersRouter::CtrlBinder> const&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) HttpSimpleControllersRouter.cc:220
drogon::HttpSimpleControllersRouter::doPreHandlingAdvices(std::__1::shared_ptr<drogon::HttpSimpleControllersRouter::CtrlBinder> const&, drogon::HttpSimpleControllersRouter::SimpleControllerRouterItem const&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) HttpSimpleControllersRouter.cc:352
drogon::HttpSimpleControllersRouter::route(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) HttpSimpleControllersRouter.cc:146
drogon::HttpAppFrameworkImpl::onAsyncRequest(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) HttpAppFrameworkImpl.cc:748
decltype(*(std::__1::forward<drogon::HttpAppFrameworkImpl*&>(fp0)).*fp(std::__1::forward<std::__1::shared_ptr<drogon::HttpRequestImpl> const&>(fp1), std::__1::forward<std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)> >(fp1))) std::__1::__invoke<void (drogon::HttpAppFrameworkImpl::*&)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>, void>(void (drogon::HttpAppFrameworkImpl::*&)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) type_traits:4302
std::__1::__bind_return<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&>, __is_valid_bind_return<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&> >::value>::type std::__1::__apply_functor<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, 0ul, 1ul, 2ul, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&> >(void (drogon::HttpAppFrameworkImpl::*&)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >&, std::__1::__tuple_indices<0ul, 1ul, 2ul>, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&>&&) functional:2644
std::__1::__bind_return<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&>, __is_valid_bind_return<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), std::__1::tuple<drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&> >::value>::type std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>::operator()<std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)> >(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) functional:2677
decltype(std::__1::forward<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&>(fp)(std::__1::forward<std::__1::shared_ptr<drogon::HttpRequestImpl> const&>(fp0), std::__1::forward<std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)> >(fp0))) std::__1::__invoke<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)> >(std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) type_traits:4361
void std::__1::__invoke_void_return_wrapper<void>::__call<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)> >(std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) __functional_base:349
std::__1::__function::__alloc_func<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>, std::__1::allocator<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> >, void (std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&)>::operator()(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) functional:1527
std::__1::__function::__func<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>, std::__1::allocator<std::__1::__bind<void (drogon::HttpAppFrameworkImpl::*)(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&), drogon::HttpAppFrameworkImpl*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> >, void (std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&)>::operator()(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) functional:1651
std::__1::__function::__value_func<void (std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&)>::operator()(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) const functional:1799
std::__1::function<void (std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&)>::operator()(std::__1::shared_ptr<drogon::HttpRequestImpl> const&, std::__1::function<void (std::__1::shared_ptr<drogon::HttpResponse> const&)>&&) const functional:2347
drogon::HttpServer::onRequests(std::__1::shared_ptr<trantor::TcpConnection> const&, std::__1::vector<std::__1::shared_ptr<drogon::HttpRequestImpl>, std::__1::allocator<std::__1::shared_ptr<drogon::HttpRequestImpl> > > const&, std::__1::shared_ptr<drogon::HttpRequestParser> const&) HttpServer.cc:320
drogon::HttpServer::onMessage(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*) HttpServer.cc:241
decltype(*(std::__1::forward<drogon::HttpServer*&>(fp0)).*fp(std::__1::forward<std::__1::shared_ptr<trantor::TcpConnection> const&>(fp1), std::__1::forward<trantor::MsgBuffer*>(fp1))) std::__1::__invoke<void (drogon::HttpServer::*&)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*, void>(void (drogon::HttpServer::*&)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) type_traits:4302
std::__1::__bind_return<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&>, __is_valid_bind_return<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&> >::value>::type std::__1::__apply_functor<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, 0ul, 1ul, 2ul, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&> >(void (drogon::HttpServer::*&)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >&, std::__1::__tuple_indices<0ul, 1ul, 2ul>, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&>&&) functional:2644
std::__1::__bind_return<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&>, __is_valid_bind_return<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), std::__1::tuple<drogon::HttpServer*, std::__1::placeholders::__ph<1>, std::__1::placeholders::__ph<2> >, std::__1::tuple<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&> >::value>::type std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>::operator()<std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*>(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) functional:2677
decltype(std::__1::forward<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&>(fp)(std::__1::forward<std::__1::shared_ptr<trantor::TcpConnection> const&>(fp0), std::__1::forward<trantor::MsgBuffer*>(fp0))) std::__1::__invoke<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*>(std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) type_traits:4361
void std::__1::__invoke_void_return_wrapper<void>::__call<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*>(std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>&, std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) __functional_base:349
std::__1::__function::__alloc_func<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>, std::__1::allocator<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> >, void (std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*)>::operator()(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) functional:1527
std::__1::__function::__func<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&>, std::__1::allocator<std::__1::__bind<void (drogon::HttpServer::*)(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*), drogon::HttpServer*, std::__1::placeholders::__ph<1> const&, std::__1::placeholders::__ph<2> const&> >, void (std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*)>::operator()(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) functional:1651
std::__1::__function::__value_func<void (std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*)>::operator()(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*&&) const functional:1799
std::__1::function<void (std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*)>::operator()(std::__1::shared_ptr<trantor::TcpConnection> const&, trantor::MsgBuffer*) const functional:2347
trantor::TcpConnectionImpl::readCallback() TcpConnectionImpl.cc:319
decltype(*(std::__1::forward<trantor::TcpConnectionImpl*&>(fp0)).*fp()) std::__1::__invoke<void (trantor::TcpConnectionImpl::*&)(), trantor::TcpConnectionImpl*&, void>(void (trantor::TcpConnectionImpl::*&)(), trantor::TcpConnectionImpl*&) type_traits:4302
std::__1::__bind_return<void (trantor::TcpConnectionImpl::*)(), std::__1::tuple<trantor::TcpConnectionImpl*>, std::__1::tuple<>, __is_valid_bind_return<void (trantor::TcpConnectionImpl::*)(), std::__1::tuple<trantor::TcpConnectionImpl*>, std::__1::tuple<> >::value>::type std::__1::__apply_functor<void (trantor::TcpConnectionImpl::*)(), std::__1::tuple<trantor::TcpConnectionImpl*>, 0ul, std::__1::tuple<> >(void (trantor::TcpConnectionImpl::*&)(), std::__1::tuple<trantor::TcpConnectionImpl*>&, std::__1::__tuple_indices<0ul>, std::__1::tuple<>&&) functional:2644
std::__1::__bind_return<void (trantor::TcpConnectionImpl::*)(), std::__1::tuple<trantor::TcpConnectionImpl*>, std::__1::tuple<>, __is_valid_bind_return<void (trantor::TcpConnectionImpl::*)(), std::__1::tuple<trantor::TcpConnectionImpl*>, std::__1::tuple<> >::value>::type std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>::operator()<>() functional:2677
decltype(std::__1::forward<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>&>(fp)()) std::__1::__invoke<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>&>(std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>&) type_traits:4361
void std::__1::__invoke_void_return_wrapper<void>::__call<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>&>(std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>&) __functional_base:349
std::__1::__function::__alloc_func<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>, std::__1::allocator<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*> >, void ()>::operator()() functional:1527
std::__1::__function::__func<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*>, std::__1::allocator<std::__1::__bind<void (trantor::TcpConnectionImpl::*)(), trantor::TcpConnectionImpl*> >, void ()>::operator()() functional:1651
std::__1::__function::__value_func<void ()>::operator()() const functional:1799
std::__1::function<void ()>::operator()() const functional:2347
trantor::Channel::handleEventSafely() Channel.cc:96
trantor::Channel::handleEvent() Channel.cc:61
trantor::EventLoop::loop() EventLoop.cc:178
trantor::EventLoopThread::loopFuncs() EventLoopThread.cc:60
trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()() const EventLoopThread.cc:25
decltype(std::__1::forward<trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0>(fp)()) std::__1::__invoke<trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0>(trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0&&) type_traits:4361
void std::__1::__thread_execute<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0>(std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0>&, std::__1::__tuple_indices<>) thread:342
void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, trantor::EventLoopThread::EventLoopThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0> >(void*) thread:352
_pthread_start 0x00007fff69879e65
thread_start 0x00007fff6987583b

The controller is a very simple one:

void IndexController::asyncHandleHttpRequest(const HttpRequestPtr &req,
                                             std::function<void(const HttpResponsePtr &)> &&callback) {
    drogon::HttpViewData data;
    auto res = drogon::HttpResponse::newHttpViewResponse("Index", data);
    callback(res);
}
rbugajewski commented 4 years ago

You could configure drogon with cmake .. -DCMAKE_BUILD_TYPE=debug and then drogon can be debuged drogon step by step. Or a more effective way is to make drogon a submodule of your project so you can debug it more easily.

Thanks for the info, I’ll have to check my IDE setup, because my breakpoints currently disappear.

I figured out what my problem initial problem was: Do you know how I can debug the drogon_ctl calls that happen on dynamic loading? The only way I can currently think off is to launch it through the debugger from the source code, but maybe there’s a better way?

an-tao commented 4 years ago

You could configure drogon with cmake .. -DCMAKE_BUILD_TYPE=debug and then drogon can be debuged drogon step by step. Or a more effective way is to make drogon a submodule of your project so you can debug it more easily.

Thanks for the info, I’ll have to check my IDE setup, because my breakpoints currently disappear.

I figured out what my problem initial problem was: Do you know how I can debug the drogon_ctl calls that happen on dynamic loading? The only way I can currently think off is to launch it through the debugger from the source code, but maybe there’s a better way?

I don't know how to debug a extranal command (drogon_ctl) calls when dynamically loading, I think you can debug drogon_ctl separately. I am thinking about compiling the create view module of drogon_ctl into drogon instead of a system call of drogon_ctl for dynamical view generations.

I tested the dynamic loading more today, I found a strange thing on MacOS when compiling drogon in debug mode, if there were more than one views being loaded dynamically, the view first dynamically loaded by my test project couldn't be updated again when it was chanaged. It was be compiled and loaded successfully, but the void DrClassMap::registerClass(const std::string &className, const DrAllocFunc &func) method was not called. If I compiled drogon in release mode or compile it on linux (in debug or release mode), everything works fine.

the registerClass method is called by a static val in DrObject<T> : https://github.com/an-tao/drogon/blob/77063e28d03930ad2c605ef72d95a30f1f9a3921/lib/inc/drogon/DrObject.h#L120 so I can say in debug mode on MacOS(clang 11.0.0), this static variable is not initialized again when the view is dynamically reloaded. I've no idea about why this happend.

To reproduce the above issue, create two views for dynamic loading, compile drogon and your test project in debug mode, set the log level to TRACE for more information. When you change both views, you will see the following log output only once: https://github.com/an-tao/drogon/blob/77063e28d03930ad2c605ef72d95a30f1f9a3921/lib/src/DrClassMap.cc#L45

@rbugajewski

rbugajewski commented 4 years ago

@an-tao I also observed that something is a little bit off.

My IDE is currently compiling the project with cmake --build ../cmake-build-debug --target www -- -j 4.

I also use Live.js to get an almost instant reload in the browser to make development easier. Live.js polls the website by sending a HEAD request every second, only on change there is a reload.

What I discovered is that this mechanism doesn’t work for the first page edited with dynamic loading enabled, but a manual refresh actually works. HEAD seems to be equivalent for the first page all the time, but all other pages work fine.

I also think there is room for improvement for dynamic loading / development mode, but I have the feeling that this should be planned instead of being hacked together. After I thought a little bit through it, layouts & dynamic load are a little bit more complicated. Currently when you change the layout of the page, you still have to recompile & relaunch the application (also changing the page that extends from the layout still uses the old layout). I thought of putting the pages, layouts & their relations into a tree-like structure. Before compilation the path to the upper-most layout should be traversed and recompilation of all dependencies in the right order triggered.

Another improvement that is crucial for me to make Drogon work with more classic Server-Side-Rendered (SSR) setups would be to cache the state of dynamically compiled views between runs. Example: Currently when I change one line in a controller, I have to recompile. This by itself is fine. But when I have dynamic layouts enabled all of them will be regenerated on the next run. For sites with more than a dozen pages you have to wait a long time until you can see the results of your changes again. In this case storing hashes of the *.csp files would be beneficial, and a recompilation would be only triggered for dynamic views if there are actually changes. Obviously, this would have also take into account dependencies to layouts as mentioned in the paragraph before.

an-tao commented 4 years ago

@rbugajewski Apart from the aforementioned exceptions, dynamic updates of the layout worked fine in my test environment (with dev branch of drogon), compared to ordinary views, the layout view is nothing special, I really can't think of any reason to explain why it can't be updated via dynamic loading, so I guess your problem is probably the problem I mentioned earlier, you can try to compile your project using release mode to confirm this. And I think caching the compiled views between runs is a good idea! I just need to load the old so files together at the beginning of the program and compile only the csp files whose modification time is later than them. Thank you for so many suggestions, I will maintain this project well and hope it will be more and more helpful to you.

rbugajewski commented 4 years ago

Thanks for the idea with the modification time!

@an-tao Please check the following PR: https://github.com/an-tao/drogon/pull/380

rbugajewski commented 4 years ago

@an-tao It looks like there are still some low hanging fruits regarding layouts on macOS. In some cases I get an empty page when Drogon returns a HttpResponsePtr via newHttpViewResponse for a csp file that contains the layout tag. When I remove the tag, the view displays without issues again. The HTTP’s response body length is 0 in the bad cases.

an-tao commented 4 years ago
auto templ = DrTemplateBase::newTemplate(layoutName);
if(!templ) return "";
HttpViewData data;
auto str = std::move(a_tmp_stream.str());
if(!str.empty() && str[str.length()-1] == '\n') str.resize(str.length()-1);
data[""] = std::move(str);
return templ->genText(data);

Maybe in some race conditions the templ is null, so the response body is empty. Please pull the latest master branch and set log level to TRACE for more information. @rbugajewski

rbugajewski commented 4 years ago

@an-tao Thanks to your info I was able to analyze the issue. I had some *.lock files lying around and didn’t discover them at first, because the layouts in my project are in a subdirectory. They weren’t compiled at all, so that the check you mention was true and an empty string returned.

Everything works fine now after I deleted the *.lock files 👍🏻

rbugajewski commented 4 years ago

I tested the dynamic loading more today, I found a strange thing on MacOS when compiling drogon in debug mode, if there were more than one views being loaded dynamically, the view first dynamically loaded by my test project couldn't be updated again when it was chanaged.

I tested this today and I confirm that this happens on my installation (macOS Catalina, clang version 11.0.0 (clang-1100.0.33.17)). As this is only the first page that is affected, I created a page called _Debug.csp that is just a placeholder. This isn’t pretty, but a fast workaround.