C++快速开发WEB应用 第2节: 用模板输出HTML
2.1 不良设计:在C++代码中,硬编码HTML
MVC: 模型 | Model、视图 | View、控制器 | Controller 是一种软件设计典范,它被广泛地、成熟地运用在长达20多年的Web系统编程。
MVC结构下最重要的设计思想,就一句话:基础数据(Model)、逻辑控制(Controller)和展现交互界面(View) 要分开一点。
在Web应用中,网页的内容,是典型的“View”的角色,重点负责“展现和交互”。通常它由控制器结合业务逻辑,将处理后的业务数据,“填充”到展现模板。
但是,早先的WEB刚到来人间,可没有这些理论,模型、视频、业务逻辑常常混合在一起。更有意思的是,在本地应用或C/S结构下的C 端,也就是“客户端”,C/C++程序都是直接上手,在屏幕上“画出”界面,技术很难,工作很累,但C/C++都干得不错。到了B/S结构,后端的C/C++程序不负责“画图”,只是生成字符串(HTML),然后交给浏览器“画出”界面(浏览器当然也是C/C++主力);后端的C/C++程序(当然,主要是程序员)却觉得生成一堆字符串太难了……于是退出Web编程。
时代变了,今天不管后台是什么编程语言,基本都不用为生成HTML而直接处理字符串;但有些事不知苦就不懂甜;因此今天我们先来忆苦思甜。
放心:完全是浅尝辄止,不会真苦到大家。
在上一节的基础上,我们这次希望向浏览器输出真正的HTML内容,HTML将直接在C++代码以字符串的形式存在:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply("127.0.0.1", 4098);
svc->AddHandler(_GET_, "/", [](Context ctx)
{
ctx->Res().ReplyOk("<html><body><h1>Hello world</h1></body></html>");
ctx->Pass();
});
svc->Run();
return 0;
}
除了看起来乱了一些,确实也没有感觉到什么苦;但从些每次想修改一些展现格式(布局、字体、颜色等),都得修改C++代码,然后还得编译,而C++的编译速度比较慢;这倒是仅人心烦。
说到字符串,转义符算是一件仅程序员闹心的事,还好C++11有“Row String Literals”(原始字符串字面量),在这里我们可以用上。因为我们这次想让“Hello world”变成蓝色,而在HTML里,这通常需要用到双引号:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply("127.0.0.1", 4098);
svc->AddHandler(_GET_, "/", [](Context ctx)
{
std::string html = R"(<html><body><h1 style="color:blue">Hello world</h1></body></html>)";
ctx->Res().ReplyOk(html);
ctx->Pass();
});
svc->Run();
return 0;
}
字符串更长了……我已经开始烦了。
正经做法,就是将HTML相关的内容,独立写成文件,这样需要修改展现形式,多数情况只需要修改外部文件即可。在大器框架上,我们把程序运行时,主体不变,仅需修改业务数据内容的这种外部文件,称为“模板文件”。在本课中,由于几乎没有业务逻辑,所以模板文件中几乎没有业务数据,完全都事先可定下的内容。
2.2 引入 “应用/Application” 的概念
一个在线商城的软件系统,通常会有两大类用户:一类是顾客,一类是商城的后台管理者。顾客还是管理者需要使用的数据“模型 | Model”有很大的重叠。比如都需要知道商品名称 、价格、数量等。因此这两类用户可以使用同一套系统(Server)。但在系统内部,二者又有一定的划分;同时也可以为二者提供一些相对独立的配置。比如安全配置、比如界面风格等等。当采用模板机制时,二者通常也各自拥有一套模板。在大器框架中,这种划分可以通过不同“应用 | Application”实现
今天我们要演示的,无关“一套服务多套应用”,仅仅是模板文件的存放位置。需要用到模板,当然需要知道模板文件存放在哪里?在大器框架中,此类配置需要通过“Application”对象实现(而不是Server)。
前面的所有例子中,都没有“Application ”的身影,这里因为对于简单的示例,大器框架允许我们无需手工创建应,Server会帮我创建一个应用,称为“默认应用 | DefaultApplication”。通过Server对象,可以得到这个默认应用:
auto app = svc->DefaultApp();
然后,通过app设置模板文件所在的根目录,为方便演示,直接使用绝对路径(实际项目其实也推荐使用绝对路径,只是会由外面配置读入路径的位置)。假设这个路径是“/home/tom/daqi_demo/view”(请确保该目录一定要事先存在,大器框架不会尝试自作主张创建目录),设置代码为:
app->SetTemplateRoot("/home/tom/daqi_demo/view/");
app 还有一个方法,是设置Web Server的临时路径,叫“SetTemporaryRoot()”,不要搞混二者。
知道模板文件所在路径后,再调用下面的方法,该app就会尝试从指定的路径下读出并预处理(编译为字节码)模板文件。
app->InitTemplates();
得到默认App,并且告诉它模板文件在哪里,而后由它读取并预处理其下模板文件的Server端主函数代码如下:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply("127.0.0.1", 4098);
auto app = svc->DefaultApp(); //取得由Server自动创建的默认应用
app->SetTemplateRoot("/home/tom/daqi_demo/view/");
app->InitTemplates();
svc->Run();
}
不过,像视频课程所演示的,我们需要在模板目录下,至少放一个模板文件,并且命名为“ index.daqi.HTML” (注意大小写);内容为:
<!DOCTYPE html>
<html lang="zh">
<head>
<title>首页</title>
<meta content="text/html; charset=UTF-8">
</head>
<body>
<p>欢迎来到我的主页</p>
</body>
</html>
一切准备就緒,编译、运行程序,在浏览器地址栏内输入“http://127.0.0.1:4098/”,应可以看到一句(段)话:“欢迎来到我的主页”。如果嫌它字太小,同样可以将“<p>欢迎来到我的主页</p>”改为“<h1>欢迎来到我的主页</h1>”。这次不用改C++代码,只需重启C++写的Web Server程序即可。未来我们很快会学习如何通过一行代码调用,实现模板文件更改后(但没有改到业务数据或相关逻辑),无需重启Web Server。
2.3 理解“基于约定”的路由匹配
最后一段C++代码中,我们从第一节课就熟悉的“AddHandler(...)”调用临时消失了;但不管需不需要调用“AddHandler()”,我们都将广泛地使用“基于约定”的路由匹配。简单地说,就是这么一个过程:身为Web Server程序的大器框架,可以读出当前浏览器(前端)前来请求的URL是什么,假设扣除前面的服务网址,那么,凡是“xxx/yyy/zzz”的请求,大器框架就会前往模板文件目录,查找其下 “xxx/yyy/”的子文件夹内,是否存在名为“zzz.daqi.HTML”的模板文件。如果有,就直接用后者加以响应——这个过程是自动的,除非我们手工写代码处理某些例外。如果请求止于某个“/”,则有另一个约定:改为访问其下“index.daqi.HTML”的模板。
至于“AddHandler(...)”,只是临时消失。因为如果接到所有请求,都只是找到一个模板文件然后返回一成不变的模板文件,那这个网站就无需开发,事先准备好所有文件即可了——对每次请求,都完全无需更改内容就可以返回的文件,不叫模板文件,叫“静态文件”。典型的如网站的图片文件。静态文件不是这节课的演示内容。
大器框架支持不写任何代码,直接写好并部署好模板文件就可以访问这一功能,这里一个在实际业务中非常不常用,但在开发、调试、测试、乃至项目管理中,都能给我们带来极大的方便的功能。它源于实际一线开发的真实需求与经验。
下节课,我们就需要演示,在收到用户请求后,如何返回一个结过动态修改的模板内容;那时候,AddHandler(...) 将会回归。