视图介绍

虽然目前前端渲染技术大行其道,使后端应用服务只需要返回相应数据给前端即可,不过,一个好的web框架还是应该提供后端渲染技术,使服务端程序可以动态生成HTML页面。视图(View)可以帮助使用者生成这些页面,顾名思义,它只负责做跟展示相关的工作,而复杂的业务逻辑都应该交给控制器完成。

最早的web应用程序都是把HTML嵌入到程序编码里,达到动态生成HTML页面的目的,不过这样做有效率低、不直观等诸多缺点,于是出现了诸如JSP等语言,反其道而行之,把程序代码嵌入到HTML页面里。drogon采用的当然是后一种方案,不过,很明显,由于C++是编译执行的,我们需要把这种嵌入了C++代码的页面转换成C++源程序,才能编译进应用程序。所以,drogon定义了自己专门的CSP(C++ Server Pages)描述语言,使用命令行工具drogon_ctl可以把csp文件转换成C++源文件以供编译。

Drogon的csp

drogon的csp方案很简单,我们用特殊的标记符号把C++代码嵌入到HTML页面里就可以了。其中:

  • 夹在标签<%inc%>之间的内容被视为需要引用的头文件部分,这里只能写入#include语句,如<%inc#include "xx.h" %>,不过很多常见的头文件drogon都自动包含了,用户基本上用不到这个标签;
  • 夹在标签<%c++%>之间的所有内容都被视为C++的代码,如<c++ std:string name="drogon"; %>
  • C++的代码一般都会原封不动的转移到目标源文件中,除了下面两种特殊标记:
    • @@代表控制器传过来的data变量,类型是HttpViewData,可以从中获取需要的内容;
    • $$代表表示页面内容的流对象,可以把需要显示的内容通过<<操作符显示在页面上;
  • 夹在标签[[]]之间的内容被认为是变量名字,view会以这个名字为keyword从控制器传过来的数据里找到对应的变量,并把它输出到页面的对应位置,变量名字前后的空格会被省略,[[]]不要分行写,同时,出于性能考虑,只支持三种字符串数据类型(const char *,std::string和const std::string,因为输出时涉及数据类型判断,过多类型会导致过多的条件语句),其他数据类型请用上面提到的方式输出(或者将需要输出的变量以string类型存入data中);
  • 夹在标签{%%}之间的内容被认为是C++程序里变量的名字或表达式(而不是控制器传过来的数据的keyword),view会把该变量的内容或表达式的值输出到页面的对应位置。容易知道{%val.xx%}等效于<%c++$$<<val.xx;%>,只是前者更为简单直观。同样的,两个标签不要分行写;
  • 夹在标签<%view%>之间的内容被认为是子视图的名字,框架会找到相应的子视图并把它的内容填充到该标签所在位置;视图名字前后的空格会被忽略,同时<%view%>不要分行写,子视图和父视图共用控制器的数据, 可以多级嵌套但不要循环嵌套。
  • 夹在标签<%layout%>之间的内容被认为是布局的名字,框架会找到相应的布局并把本视图的内容填充到该布局的某个位置(在布局中由占位符[[]]标定该位置);布局名字前后的空格会被忽略,同时<%layout%>不要分行写,可以多级嵌套但不要循环嵌套。

视图的使用

drogon应用程序的http响应都是由控制器handler生成的,所以,由视图渲染的响应也由handler生成,通过调用如下接口生成:

  1. static HttpResponsePtr newHttpViewResponse(const std::string &viewName,
  2. const HttpViewData &data);

这个接口是HttpResponse类的静态方法,它有两个参数:

  • viewName: 视图的名字,传入的csp文件名(扩展名可省略);
  • data: 控制器的handler传给视图的数据,类型是HttpViewData,这是个特殊的map,可以存入和取出任意类型的对象,具体使用请参考HttpViewData API说明;

可以看到,控制器不需要引用视图的头文件,控制器和视图实现了很好的解耦;他们唯一的联系是data变量,对data的内容,控制器和视图要有一致的约定;

一个简单的例子

现在我们做一个例子,把浏览器发来的HTTP请求的参数显示在返回的html页面里。

我们这里直接用HttpAppFramework的接口定义handler,在main文件中,调用run()方法之前加入如下代码:

  1. drogon::HttpAppFramework::instance()
  2. .registerHandler
  3. ("/list_para",
  4. [=](const HttpRequestPtr &req,
  5. std::function<void (const HttpResponsePtr &)> &&callback)
  6. {
  7. auto para=req->getParameters();
  8. HttpViewData data;
  9. data.insert("title","ListParameters");
  10. data.insert("parameters",para);
  11. auto resp=HttpResponse::newHttpViewResponse("ListParameters.csp",data);
  12. callback(resp);
  13. }
  14. );

上面这段代码把一个lambda表达式handler注册到/list_para路径上,获取请求的参数传递给视图显示。 然后进入views文件夹,创建一个视图文件ListParameters.csp,内容如下:

  1. <!DOCTYPE html>
  2. <html>
  3. <%c++
  4. auto para=@@.get<std::unordered_map<std::string,std::string>>("parameters");
  5. %>
  6. <head>
  7. <meta charset="UTF-8">
  8. <title>[[ title ]]</title>
  9. </head>
  10. <body>
  11. <%c++ if(para.size()>0){%>
  12. <H1>Parameters</H1>
  13. <table border="1">
  14. <tr>
  15. <th>name</th>
  16. <th>value</th>
  17. </tr>
  18. <%c++ for(auto iter:para){%>
  19. <tr>
  20. <td>{%iter.first%}</td>
  21. <td><%c++ $$<<iter.second;%></td>
  22. </tr>
  23. <%c++}%>
  24. </table>
  25. <%c++ }else{%>
  26. <H1>no parameter</H1>
  27. <%c++}%>
  28. </body>
  29. </html>

我们可以通过下面的命令将ListParameters.csp文件转换成c++源文件:

  1. drogon_ctl create view ListParameters.csp

运行完毕后,当前目录会出现ListParameters.h和ListParameters.cc两个源文件,就可以用来编译进web应用程序里了。

用cmake重新编译整个工程,运行目标程序webapp,就可以在浏览器里测试效果了,在地址栏输入http://localhost/list_para?p1=a&p2=b&p3=c,就可以看到如下页面:

view页面

后端渲染的html页面就这样简单的加上了。虽然页面简陋点,但不影响我们说明视图的用法。

csp文件的自动化处理

注意:如果你的工程是使用drogon_ctl命令创建的,那么本节描述的内容已经由该命令自动帮你做了。

显然,每次修改csp文件都需要手动运行drogon_ctl命令显得太不方便了,我们可以把drogon_ctl的处理放进CMakeLists.txt文件里,仍以前面的例子为例,假设我们把所有的csp文件都放到views文件夹里,则CMakeLists.txt可以添加如下处理:

  1. FILE(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/views/*.csp)
  2. foreach(cspFile ${SCP_LIST})
  3. message(STATUS "cspFile:" ${cspFile})
  4. EXEC_PROGRAM(basename ARGS "-s .csp ${cspFile}" OUTPUT_VARIABLE classname)
  5. message(STATUS "view classname:" ${classname})
  6. add_custom_command(OUTPUT ${classname}.h ${classname}.cc
  7. COMMAND drogon_ctl
  8. ARGS create view ${cspFile}
  9. DEPENDS ${cspFile}
  10. VERBATIM )
  11. set(VIEWSRC ${VIEWSRC} ${classname}.cc)
  12. endforeach()

然后在add_executable语句中添加新的源文件集合${VIEWSRC},如下:

  1. add_executable(webapp ${SRC_DIR} ${VIEWSRC})

上述措施在drogon_ctl create project命令生成的工程里已经写入CMakeLists.txt文件,用户在views文件夹创建的csp文件都会被自动转换并编译进应用程序。

视图的动态编译和加载

drogon提供了在应用运行期动态编译和加载csp文件的方法,使用如下接口设置:

  1. void enableDynamicViewsLoading(const std::vector<std::string> &libPaths);

该接口是HttpAppFramework的成员方法,参数是一个字符串数组,代表视图csp文件所在目录的列表。调用这个接口后,drogon将自动搜索这些目录,发现新的或者被修改的csp文件后,都将自动生成源文件、编译成动态库文件并加载到应用里,整个过程应用程序无需重启。用户可以自行实验,观察csp的修改带来的页面变化。

很显然,该功能依赖于开发环境,如果drogon和webapp都在这台服务器编译,则动态加载csp页面也应该没有问题;

注意:动态加载的视图不能静态编译进程序,也就是说,如果一个视图已经静态编译进程序,那么它无法通过动态加载更新,你可以单独建一个动态视图路径,并在开发阶段把视图移动到这个路径进行调试(linux操作系统没有这个问题)。

注意: 该特性最好用于在开发阶段方便调整页面,生产环境部署还是建议直接编译成目标文件运行,这主要是出于安全性和稳定性考虑。

注意: 如果加载时遇到symbol not found错误,请使用cmake .. -DCMAKE_ENABLE_EXPORTS=on或取消CMakeLists.txt最后一行对set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)的注释,并重新编译你的工程

07 会话