Archive for the ‘web service’ Category
CppCMS 杂
放在 applications_pool 里面的都是 synchronous app,他们一般使用 factory 构造出来,其特点是其 context 被 cppcms 的 framework 构造好之后,根据匹配关系交由 threads_pool 中某个线程,该线程执行对应 applications_pool 中对应 application 的逻辑,也就是说对并发的请求,synchronous app 是直接依赖多线程来实现的。asynchronous app 自己就能 handle 并发请求,这意味着它们直接在 event loop 中接受请求,而并不通过 threads_pool。下面一段代码显示了一个 asynchronous app 的例子。
try {
cppcms::service service (argc,argv);
booster::intrusive_ptr<chat> c = new chat (service);
service.applications_pool ().mount (c);
service.run ();
} catch(std::exception const &e) {
std::cerr << "Error: " << e.what () << std::endl;
return 1;
}
return 0;
这个例子表明通过 intrusive_ptr(实际上是 boost.smart_ptr 里面的一个和 shared_ptr 类似的东西,被指向的对象自己拥有 ref count,通过 intrusive_ptr_add_ref 和 instrusive_ptr_release 两个函数来管理 ref count)产生一个 asynchronous app,并 mount 到 applications_pool。下面是一个比较典型的异步调用形式
booster::shared_ptr<cppcms::http::context> context=release_context ();
waiters_.insert (context);
context->async_on_peer_reset (
bind (
&chat::remove_context,
booster::intrusive_ptr<chat>(this),
context
)
);
通过类似的方法可以将需要 long polling 的请求弄成这样避免等待。关于 context 其他的一些异步请求代码见这里。
从这一点上来看,这篇批评 node.js 的文章是 make sense 的,即真正 asynchronous 是指将能够避免无谓等待的地方弄成避免等待(以做些别的事情),而不是说一味的把所有的事情都弄成 asynchronous(因为没有必要)。感觉 node.js 为程序员提供了一种非常方便切换到异步的方式,cppcms 相对还是更有难度一些。有时候“容易使用”很可能变成“过度使用”,这是使用 node.js 需要谨记的地方吧。但是程序员如何知道什么时候需要 synchronous call 什么时候需要 asynchronous call 呢?
CppCMS 可以使用 FastCGI(特点是使用 socket 通信,具体的 spec 见这里) 与 SCGI(特点是使用 stream 实现,相对 FastCGI 更容易实现,具体的 spec 见这里)与其他的 web server 协同工作。看这个文档比较有意思的可能是 lighthttpd、nginx 和 apache 这三个。apache 算是接触过一段时间,其他两个还不清楚,像 lighthttpd 应该算是轻量级的 web server(资源消耗少),而 nginx 算是为了高流量网站准备的也是比较 heavy 的东西了吧(似乎特点也是 asynchronous 机制,而 apache 是用 multi-threading、multi-processing 来解决的)。
前面的例子我们看到可以使用动态链接库提供 templates 的不同 rendering,这是一种比较直接、简单的 plugin,另有一种不知道是不是通过反射实现的(尚未提供)。现在的支持的方式一般是自己定义一个 interface,然后约定一些 function 供在 template 里面使用。这样做了之后,所有实现了这个 interface 的动态链接库理论上是可以运行时进行切换的(比如通过 ld 的一些函数来实现)。感觉上 plugin 这部分可能还是会依赖于 boost.reflection/extension 提供的一些手段(虽然 reflection/extension 还没正式进入 boost,啊为什么…)
简单的 solution 就是配置一个 static root,对于某个 url 下面的文件处理引用的时候直接用 std::ifstream 读取内容返回即可。但是这样做有 security issue,比如传入的文件含有对路径的修改(如相对路径),为了 detect 这类问题,比较容易的做法是为指定路径下的文件进行控制(如修改文件名为某些 hash value)。另外也可以通过 boost.filesystem 的相关功能对给定路径进行确认,对非法路径进行 filtering。这个应该是很容易做成 app level 的共享的,并且是独立于各个应用的一个模块。不过 cppcms 和 django 的不同在于现在没有太多的 contribution(连这个 static 的模块好像都没现成的)。
这个东西前面也没仔细的研究过,但是从介绍里面看来和 boost.asio 还是不一样的。boost.asio 实际上也是一个 event-based 的 IO,现在不是很清楚的是到底 node.js/nginx 和 apache 的“具体差异”到底在哪里,难道只是前者更强调 event-based?booster.aio 是对 boost.asio 的简化(这个…),提供了比如 basic_io_device 可以 attach 到 std::cin 上,这个就可以为某些 event 添加需要的 callback function 了。
数据库
似乎 C++ 不像 Java/python 之类的有一些非常好用接口统一的 library 与数据库打交道,这方面 boost 似乎也没有提供一个比较理想的 binding,不知道这个情况在今后会不会有所解决。其实 web app 很多时候是需要有一个不错的 DB 的 middleware 将应用逻辑与 DB backend 分离的,CppCMS 似乎也没有一个明确的解决方案。找了一个 sqlite3 的 C++ 的 binding(sqlite3pp),风格据说和 boost 接近。django 这部分成熟的多,可以很容易的将 model 传入到 template 中。
——————–
Let a little water, I pray you, be fetched, and wash your feet, and rest yourselves under the tree:
深入学习 CppCMS
这里我们首先 cover 一些以上笔记中右侧的内容,左侧和 web server 的 infrastructure 比较相关。我们尽量与 django 这个 web service framework 进行比较。另外所谓 libbooster 其实就是从 boost 里面弄过来的一些东西,辅助 libcppcms 的。
所谓的 service 其实实现的是 http server 的 infrastructure,为每个 app 订好了相关接口的一个类。同时它提供了 event 机制(见这里),这大概是依赖于 boost.aio 做得一个 service,我们提供的 app 分同步和异步两种,cppcms::service 会根据这个种类将解析好的 request 分发到对应的 app,而或者等待同步 app 返回再释放掉 request (在线程池里面抓个线程做),或者等待异步 app 处理(这时得保留 request)。线程间的通信可使用 cppcms::service::post 方法。
常用的方法有 settings() 返回 json 的配置,各种 pool(applications、threads、views、session、cache),同时也能显示多少个线程等相关信息。
配置文件的详细信息可以参看这个页面。django 的配置信息都放在 py 文件里面,这得益于 python 自己是动态语言。
这是一般用户建立自己的 application 继承的类,如果希望提供 JSONRPC 服务,对应的 json_rpc_service 是该类的子类。默认情况下其 main 进行 URL dispatching,因此前面简单的 hello 只是替换了默认的实现。如果我们希望利用 dispatching 的机制,就需要在对应构造函数调用 application 提供的 mapper() 和 dispatcher() 进行 bind,如通过一些成员函数实现具体的功能,然后对应到具体的 URL 下面。
hello::hello (cppcms::service &srv)
: cppcms::application (srv) {
dispatcher ().assign ("/number/(\\d+)", &hello::number, this, 1) ;
mapper ().assign ("number", "/number/{1}") ;
dispatcher ().assign ("/smile", &hello::smile, this);
mapper ().assign ("smile", "/smile");
dispatcher ().assign ("", &hello::welcome, this);
mapper ().assign ("");
mapper ().root ("/hello") ;
}
void
hello::number (std::string num) {
int no = boost::lexical_cast<int> (num) ;
response ().out () << "The number is " << no << "<br/>\n" ;
response ().out () << "<a href='" << url ("/") << "'>Go back</a>" ;
}
void
hello::smile () {
response ().out () << ":-) <br/>\n" ;
response ().out () << "<a href='" << url ("/") << "'>Go back</a>";
}
为了形成 application 的层次结构,我们可以调用 add()/attach() 将别的 application 添加为子 application 这样进行“复用”。另外提供了 render() 方便将模板产生的结果返回为 response,
void
hello::main (std::string) {
content::message c ;
c.text = ">>>hello<<<" ;
render ("message", c) ;
}
translate 用于支持 i18n,url() 用来产生分配的 URL(前面的代码里面有)。嗯,下面就是关于 templating 的部分了。django 这部分一般用户不需要 coding,也基本上是用 urls.py 文件的配置和几个 views.py 里面的实现获得的。
MVC 与模板
cppcms 提供的模板机制前面的笔记里面列出来了三点:
- model 用来存放数据的一个类;
- view 类 HTML 语法的模板文件,通过 cppcms_tmpl_cc 转换成为实现(cpp)的中间体
- controller 即前面所说的成员函数控制逻辑
我们拿个简单的例子看看,下面是一个简单的 model
#include <cppcms/view.h>
#include <string>
namespace content {
struct message
: public cppcms::base_content {
std::string text ;
} ;
}
我们对应的模板如下
<% c++ #include "content.hpp" %>
<% skin my_skin %>
<% view message uses content::message %>
<% template render() %>
<html>
<body>
<h1><%= text %> World!</h1>
</body>
<html>
<% end template %>
<% end view %>
<% end skin %>
语法和 JSP 很像(好像 wordpress 的 sourcecode plugin 不支持 JSP…)。通过 cppcms_tmpl_cc 编译后获得的 cpp 文件大致如下:
#include "content.hpp"
namespace my_skin {
struct message :public cppcms::base_view {
content::message &content;
message (std::ostream &_s,
content::message &_content)
: cppcms::base_view (_s), content (_content) {}
virtual void
render () {
out () << "\n"
"<html>\n"
" <body>\n"
" <h1>";
out () << cppcms::filters::escape (content.text);
out () << " World!</h1>\n"
" </body>\n"
"<html>\n"
"";
} // end of template render
}; // end of class message
} // end of namespace my_skin
namespace {
cppcms::views::generator my_generator;
struct loader {
loader () {
my_generator.name ("my_skin") ;
my_generator.add_view<my_skin::message,
content::message> ("message", true);
cppcms::views::pool::instance ().add (my_generator) ;
}
~loader() { cppcms::views::pool::instance().remove(my_generator); }
} a_loader;
} // anon
可见第一部分放在 my_skin 这个 namespace 的内容实际是对 template 本身的替换和 C++ 化,而后一部分是将这个 view 注册到 view pool 里面。实际上 django 的模板与此类似,它也提供了一套自己的替换语言。但是 django 似乎将替换工作 delay 到运行时,而 CppCMS 是尽可能将工作提前到了运行时。django 的好处是 server 起来后修改这些东西就能直接在线上看到效果,而 CppCMS 似乎得等编译链接之后重启服务器。不知道能不能通过一些动态链接的方法动态的更新这些东西呢?
有了这些准备知识,后面就可以了解一些更复杂的例子了。
——————-
And he left off talking with him, and God went up from Abraham.
发现个好东西
CppCMS 是一个试图使用 C++ 构建高性能 CMS 的 C++ library。在 mac 上装了一个开始玩玩。很多库我都是用 macports 自带的版本,因此对应 cmake 的指令大致如下:
$ tar xjvf cppcms-1.0.1.tar.bz2
$ cd cppcms-1.0.1 && mkdir build && cd build
$ cmake -DCMAKE_LIBRARY_PATH=/opt/local/lib -DCMAKE_INCLUDE_PATH=/opt/local/include \
-DCMAKE_INSTALL_PREFIX=/some/path/you/want ../
$ make && make install
下面是安装好后的文件,
$ ls bin/ cppcms_config_find_param cppcms_run cppcms_tmpl_cc cppcms_make_key cppcms_scale $ ls lib/ libbooster.0.0.0.dylib libbooster.a libcppcms.1.0.1.dylib libcppcms.a libbooster.0.dylib libbooster.dylib libcppcms.1.dylib libcppcms.dylib $ ls include/ booster cppcms
然后我们看下面的例子,如何利用 CppCMS 构建第一个 web service。我们首先创建一个 hello 的 class,
#ifndef CPPCMS_HELLO_HPP
#define CPPCMS_HELLO_HPP
#include <cppcms/application.h>
#include <cppcms/applications_pool.h>
#include <cppcms/service.h>
#include <cppcms/http_response.h>
#include <iostream>
class hello : public cppcms::application {
public:
hello(cppcms::service &srv) ;
virtual void main(std::string url);
};
#endif
其实现如下,
#include "hello.hpp"
hello::hello(cppcms::service &srv)
: cppcms::application(srv) {}
void
hello::main(std::string) {
response().out() <<
"<html>\n"
"<body>\n"
" <h1>Hello World</h1>\n"
"</body>\n"
"</html>\n";
}
最后写一个 driver,
#include "hello.hpp"
int
main(int argc, char ** argv) {
try {
cppcms::service srv(argc,argv);
srv.applications_pool().mount(
cppcms::applications_factory<hello>()
) ;
srv.run() ;
} catch(std::exception const &e) {
std::cerr << e.what() << std::endl;
}
return 0 ;
}
之后提供一个配置文件,格式是 JSON
{
"service" : {
"api" : "http",
"port" : 52080
},
"http" : {
"script_names" : [ "/hello" ]
}
}
之后我们连接了 hello 之后就能启动我们的 server 了。
./hello -c config.json
之后就能在浏览器里面访问本地的 52080 端口 http://localhost:52080/hello 看到效果了。后面会仔细研究下 CppCMS 的具体细节。
——————
And as for Ishmael, I have heard you: Behold, I have blessed him, and will make him fruitful, and will multiply him exceedingly; twelve princes shall he beget, and I will make him a great nation.
