最早的web应用程序都是把HTML嵌入到程序编码里,达到动态生成HTML页面的目的,不过这样做有效率低、不直观等诸多缺点,于是出现了诸如JSP等语言,反其道而行之,把程序代码嵌入到HTML页面里。drogon采用的当然是后一种方案,不过,很明显,由于C++是编译执行的,我们需要把这种嵌入了C++代码的页面转换成C++源程序,才能编译进应用程序。所以,drogon定义了自己专门的CSP(C++ Server Pages)描述语言,使用命令行工具可以把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会把该变量的内容或表达式的值输出到页面的对应位置。容易知道等效于<%c++$$<<val.xx;%>,只是前者更为简单直观。同样的,两个标签不要分行写;
  • 夹在标签<%view%>之间的内容被认为是子视图的名字,框架会找到相应的子视图并把它的内容填充到该标签所在位置;视图名字前后的空格会被忽略,同时<%view%>不要分行写,子视图和父视图共用控制器的数据, 可以多级嵌套但不要循环嵌套。
  • 夹在标签<%layout%>之间的内容被认为是布局的名字,框架会找到相应的布局并把本视图的内容填充到该布局的某个位置(在布局中由占位符[[]]标定该位置);布局名字前后的空格会被忽略,同时<%layout%>不要分行写,可以多级嵌套但不要循环嵌套。

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

这个接口是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. {
  6. HttpViewData data;
  7. data.insert("title","ListParameters");
  8. data.insert("parameters",para);
  9. auto resp=HttpResponse::newHttpViewResponse("ListParameters.csp",data);
  10. callback(resp);
  11. }
  12. );

我们可以通过下面的命令将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,就可以看到如下页面:

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

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

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

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

    视图的动态编译和加载

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

    该接口是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