4 CHN 06 视图
antao edited this page 2023-09-02 16:13:38 +08:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

English | 简体中文

视图介绍

虽然目前前端渲染技术大行其道使后端应用服务只需要返回相应数据给前端即可不过一个好的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++程序里变量的名字或表达式而不是控制器传过来的数据的keywordview会把该变量的内容或表达式的值输出到页面的对应位置。容易知道{%val.xx%}等效于<%c++$$<<val.xx;%>,只是前者更为简单直观。同样的,两个标签不要分行写;
  • 夹在标签<%view%>之间的内容被认为是子视图的名字,框架会找到相应的子视图并把它的内容填充到该标签所在位置;视图名字前后的空格会被忽略,同时<%view%>不要分行写,子视图和父视图共用控制器的数据, 可以多级嵌套但不要循环嵌套。
  • 夹在标签<%layout%>之间的内容被认为是布局的名字,框架会找到相应的布局并把本视图的内容填充到该布局的某个位置(在布局中由占位符[[]]标定该位置);布局名字前后的空格会被忽略,同时<%layout%>不要分行写,可以多级嵌套但不要循环嵌套。

视图的使用

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

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

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

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

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

一个简单的例子

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

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

drogon::HttpAppFramework::instance()
 .registerHandler
  ("/list_para",
   [=](const HttpRequestPtr &req,
       std::function<void (const HttpResponsePtr &)> &&callback)
       {
            auto para=req->getParameters();
            HttpViewData data;
            data.insert("title","ListParameters");
            data.insert("parameters",para);
            auto resp=HttpResponse::newHttpViewResponse("ListParameters.csp",data);
            callback(resp);
        }
   );

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

<!DOCTYPE html>
<html>
<%c++
    auto para=@@.get<std::unordered_map<std::string,std::string,utils::internal::SafeStringHash>>("parameters");
%>
<head>
    <meta charset="UTF-8">
    <title>[[ title ]]</title>
</head>
<body>
    <%c++ if(para.size()>0){%>
    <H1>Parameters</H1>
    <table border="1">
      <tr>
        <th>name</th>
        <th>value</th>
      </tr>
      <%c++ for(auto iter:para){%>
      <tr>
        <td>{%iter.first%}</td>
        <td><%c++ $$<<iter.second;%></td>
      </tr>
      <%c++}%>
    </table>
    <%c++ }else{%>
    <H1>no parameter</H1>
    <%c++}%>
</body>
</html>

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

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可以添加如下处理

FILE(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/views/*.csp)
foreach(cspFile ${SCP_LIST})
    message(STATUS "cspFile:" ${cspFile})
    EXEC_PROGRAM(basename ARGS "-s .csp ${cspFile}" OUTPUT_VARIABLE classname)
    message(STATUS "view classname:" ${classname})
    add_custom_command(OUTPUT ${classname}.h ${classname}.cc
        COMMAND drogon_ctl
        ARGS create view ${cspFile}
        DEPENDS ${cspFile}
        VERBATIM )
   set(VIEWSRC ${VIEWSRC} ${classname}.cc)
endforeach()

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

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

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

视图的动态编译和加载

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

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 会话