您的位置:首页 > 其它

Wt简介

2010-12-10 10:12 701 查看
作者:Wim Dumon and Koen Deforche
* 关于作者1:本文作者Wim Dumon,Sobicom nv创立者,专注于嵌入式系统设计及生物信息方面的软件工程咨询服务,wdumon@gmail.com
* 关于作者2:Koen Deforche,Wt库作者,医学博士,创立并运行Emweb公司(http://www.emweb.eu/),koen.deforche@gmail.com
翻译:zhmsong

注:本文译自《Wt: A Web Toolkit ---Writing web applications using a C++ GUI programming style》(Dr. Dobb's,2008年11期),全文内容取自http://www.ddj.com/cpp/206401952;该文的翻译工作已经获得了Dr. Dobb's杂志主编Jon Erickson及文章主要作者Koen Deforche的授权。

Wt是一款可以自由获取的软件库,同时也是一款应用服务器软件(www.webtoolkit.eu/wt/cn),Wt实现了依照程序员熟悉的C++ GUI编程风格开发现代web应用的目标;其基本原理是当完成C++开发后,直接将应用渲染到web浏览器上。图1展示了一个运行中的Wt应用,该应用类似于Gmail的邮件编辑器,完全支持AJAX,完全由C++编写,且采用CSS标记。

从程序员的角度观察,Wt的API类似于Qt、Gtk及wxWindows等开发库的API,但不同的是Wt不会把widgets渲染到Windows/X11/windows上,而是增量渲染到web浏览器中。Wt完全隐藏了底层web技术,如HTML、AJAX、XML、CGI、JavaScript、DHTML等,Wt能够根据不同浏览器的不同性能及语法特点区别处理,并选择适当的渲染和会话管理策略。

使用Wt的信号/槽机制,浏览器端的事件,如按钮点击、鼠标移动以及托放等可以自动转换为服务器端事件。对于在简单视图更新过程中出现的服务器往返延迟较长的问题,Wt则采用动态C++-to-JavaScript翻译机制予以避免,同时在代码编写方面Wt坚持C++的事件处理编写规范。 Wt的渲染引擎推荐使用AJAX为widget树的更新进行增量渲染,当然Wt应用仍能保持在不支持AJAX/JavaScript(或被禁用)的情况下正常工作。 Wt库能够保证应用程序免于大多数跨站脚本(XSS)攻击,因为Wt仅提供widget级的API,同时Wt提供内置的自动过滤功能,可以自动过滤掉显示文本中的有害标签。



图 1 一个运行中的Wt应用

作为一种纯C++库,基于Wt开发的web应用与Java或Ruby相比,具有更高的运行效率以及更低的内存使用,因此,Wt非常适用于对性能和内存要求较高的嵌入式应用开发。

Wt库概览

当用户访问应用程序URL时,一个Wt会话即开启;而当用户明确退出应用程序或隐式离开页面时,Wt会话将终止。对于程序员来说,会话管理近乎自动完成,同时也与其他多用户系统上的GUI应用相似。在具有一般配置的Wt应用中,每生成一个新的Wt会话,将产生一个对应的进程,并且该进程拥有与该会话一样的生命周期。因此,一个拥有N个会话的服务器(比如同时有N个用户在线)应该有N个进程在运行中。Wt也可以通过配置来使用cookies或者URL重写以将请求绑定到正确的会话上。通过以上架构,所有的会话都将运行在自己的内存空间中(也就是说,某个进程崩溃永远也不会影像到其他进程),同时各个会话之间将不会互相通讯,除非使用显式的通讯机制并提供额外的安全措施。以上模型可能不太适合于公共web应用,因此Wt也提供另一种方法,即通过相应配置使所有会话限制到一定数量的或者仅有一个的进程中。这种配置会节约服务器资源,同时更易于使服务从DoS攻击中恢复。以上部署方案均是在连接时或运行时实现的,因此不会影响应用程序代码的编写。

Wt Widgets:与浏览器交互

图2展示了Wt的widget继承关系图。位于最顶端的是WWidget类,该类提供了用于组织页面布局及每个widget样式的诸多方法。页面布局和视觉显示均通过CSS来定义,但存在一定的缺憾,这主要是因为CSS采用文档标记方式,所以在设计用户界面时不是很直观。

Wt的核心widgets都继承自WWebWidget类,这些widgets均直接与HTML模块相对应。WWebWidget类指定了如何使用HTML DOM渲染widget的变化,同时提供了这些HTML元素本身所具有的所有功能。例如,HTML按钮通过WPushButton类实现,HTML文本通过WText实现,HTML表格通过WTable类实现。 HTML模块的属性及内容均由相应类的方法控制。HTML元素中支持键盘或鼠标交互的部分在Wt中将成为WInteractWidget类的子类,这些类将事件作为Wt::Signal<Event>对象处理。 WFormWidget类的子类,即表单控制widgets,可以获得键盘焦点,并能够被启用或禁用;同时还提供了一些其他的事件处理。Wt库对于浏览器DOM与服务器端widget树之间的信息传递存在两个方向:当服务器端应用程序修改widget属性时(如颜色变化、启用/禁用或者widget树的修改),该变化即被渲染并反映在浏览器中(如果有支持则通过AJAX);另一个方向,由用户在DOM发起的任何变化或事件都会自动在widget中有所反映,例如对于HTML文本输入框的变化将对应于WLineEdit对象的变化。另外,不管是用户动作发起的或是应用程序自身发起的,Wt中所有事件的传递均采用信号/槽机制处理,该机制是典型的观察者模式的实现。



图 2 Wt widget继承关系图

与WWebWidget类平行的还有一个类叫做WCompositeWidget类,如上所述WWebWidget类尽可能多地提供了浏览器端可以实现的功能,而WCompositeWidget类则以完全封装的方式实现了更为高级的widget功能。例如,WTreeNode类就定义了树形框架中的一个节点,该类是通过使用放置有WImage和WText widgets的2×2的WTable实现的,可见WTreeNode类的内部组织并不是通过继承WCompositeWidget得到的,因此未来还可以根据需要进一步修改。

新构建的WCompositeWidget的样式和结构是通过组合现有widgets得到的,这些现有widgets可以是WWebWidget或WCompositeWidget;其行为则是通过将槽与现有widgets提供的各种信号相连定义的。新Widget不仅仅限于服务器端事件处理,还可以使用动态C++-to-JavaScript翻译机制绕过服务器往返直接渲染视图的更新。例如,WTreeNode即使用这一特点实现客户端的打开/叠起开关动作。
部署架构

Wt采用Connector实现抽象了建立在HTTP协议上的、与web浏览器之间的通讯,Wt2.x以后的版本包含两个Connector实现。一个使用FastCGI协议,另一个是轻量级的内置web服务器。前者允许Wt应用通过多种web服务器的FastCGI插件工作,后者则实现了在不依赖任何第三方web服务器软件的情况下开发和部署web应用。

开源的FastCGI协议在设计之初是被用来允许CGI应用可以长时间驻留并能够处理多个请求的,在此基础上,web服务器中的FastCGI插件即可以通过本地套接字使(对应特定web应用的)请求成为一个常驻进程,这样的进程使服务器具有调度能力,即能够确认请求的目标会话,进而推送该会话到对应的进程并实现之。

另一方面,使用Wt内置服务器可以通过监听HTTP(S)请求免去对中间协议的需要。在这种部署中,强健的web服务器软件仍可作为前端服务器,并能够进一步配置以代理方式将请求传递给Wt应用。因为内置服务器对内存使用的需求很小,所以很适合于独立运行的嵌入式应用。另外,内置服务器最重要的特点是简化了Wt应用的开发,因为你可以轻松地在调试器中启动应用程序,并且这些应用程序可以配置成单线程单进程架构。

The World Is Not Enough.

不像桌面应用,web应用要求自动转换页面语言以能够同时被世界各地的用户访问。为此,Wt提供了诸多特性来让web应用轻松实现本地化及国际化。对于本地化,Wt提供了基于信息资源集的操作;一个信息资源集可以定义一套XML文件,其中每个XML文件会对应一种本地化语言,每个XML文件又都会将本地化文本或XHTML标记与相应的键值(message key)关联。 .

Wt API使用WString类处理所有需渲染的文本。WString类定义了隐式的构造函数,该构造函数可以处理标准字符串(包括C/C++中的半角和全角字符等),并将字符串以字面取值的形式表达,例如:

WText *aText = new WText("Hello World!");

以上代码创建了一个内容为“Hello World”的文本 widget。

若要实现本地化的 hello-world 程序,则应使用静态方法 WString::tr(key),该方法实际是WWidget::tr(key)的别称,之所以使用前者,只是因为实现起来更方便,如下:

WText *text = new Wtext(tr("hello-world"));

应用程序将首先在信息资源集中寻找当前语言种类及该键值,然后决定应显示的本地化文本。比如,如果有一个内容为下面代码的 “hello-world.xml”文件:

<messages>
<message id="hello-world">Hello World!</message>
</messages>

以及一个以荷兰语为本地语言的 “hello-world_nl.xml”文件:

<messages>
<message id="hello-world">Dag Wereld!</message>
</messages>

到底如何决定使用哪种语言呢?默认情况下,Wt将直接采用浏览器报告的本地语种。但是,语言种类也可以通过 WApplication::setLocal(locale)在运行时随时改变。WApplication::setLocale()方法将通过 WApplication::refresh()方法引起整个应用的刷新动作,WApplication::refresh()方法进而触发WWidget::refresh()方法刷新整个widget树。在这个动作的传递过程中,所有的widgets都将采用新指定的语种来显示文本,且每个改变都将被Wt的渲染机制捕捉并在浏览器中得到反映。这种传递不需要程序员做出特别的努力,甚至不需要修改程序中已定义的widgets。

国际化需要支持超过8位西文字符集的其他字符,为达到此目的,Wt为取字面值的文本及本地化字符串提供了Unicode支持。对于字面取值文本,你可以指定并访问从半角、全角及UTF8编码的字符串内容,这样也实现了对已淘汰代码的支持。对于本地化字符串,Wt则依赖XML对Unicode和不同编码字符的广泛支持。

服务器端事件处理

Wt信号和槽机制是建立在Boost.Signals包基础上的,Boost.Signals是C++ Boost模板库的一部分。继承自WObject类的所有子类都能够使用信号和槽,并自动监视连接情况,特别是当对象被删除时,这一切都是因为WObject继承自boost::trackable。

Wt通过释放对应的Wt::Signal实现了用户与HTML DOM元素之间的交互,这些交互事件包括键盘事件(KeyWentDown、KeyWentUp、KeyPressed),鼠标事件(clicked、doubleClicked、mouseMoved等)以及其他类型的事件。这些事件只有在其对应的信号存在某些连接时才会被传递,因此避免了不必要的通讯。不幸的是,在没有JavaScript支持的情况下,一般的应用可能只会对鼠标点击事件做出反映。而Wt却可以实现在任何情况下都能够对事件做出反映,因为Wt可以通过WEnvironment::javaScript()判断是否存在对JavaScript支持,如果不支持,Wt可以通过某些替代方法(如增加额外按钮)来实现较复杂的交互操作。

清单1展示了一个简单的Wt构造函数,该函数初始化了一个widget,并能对某些事件做出反应(简略起见,我们在类声明中加入了实现内容)。在这个例子中,按钮作为复合widget的一部分被创建。当按钮被点击时,方法doFumble()被调用, 其作用效果是首先禁用按钮(阻止用户再次点击),然后再去执行某些业务逻辑。

class MyWidget : public WCompositeWidget
{
public:
MyWidget(WContainerWidget *parent = 0)
: WcompositeWidget(parent),
...
{
...
fumbleButton_ = new WPushButton("Fumble");
fumbleButton_->clicked.connect(SLOT(this, MyWidget::doFumble));
...
}
private:
WpushButton *fumbleButton_;
void doFumble()
{
fumbleButton_->disable();

fumbleSome(...);
}
};
清单 1

这个例子存在一个明显的缺陷,即按钮只能在客户端与服务器端发生至少一次往返之后才能禁用,也会发生在所有的fumbling动作都完成之后!而在Wt中,这个缺陷可以通过客户端事件处理轻松地解决。

客户端事件处理

虽然服务器端能够依规则进行各种事件处理,但存在某些限制,因为客户端和服务器端往返会产生一定的时间延迟。不过幸运的是,当浏览器支持JavaScript,特别是当事件处理直接采用JavaScript时,这种往返延迟带来的影响将显得无足轻重。但是就算我们不去考虑用JavaScript完成事件处理功能的复杂性以及其本身和其实现的缺憾,JavaScript还是存在很多根本性的问题。首先,如果JavaScript不被支持,那么必须提供替代方案。第二,使用JavaScript后,应用程序的服务器端状态将再也不会与客户端保持同步。另外,如果使用自定义的JavaScript,Wt是不能保证程序的健壮性的,特别是在防止XSS攻击和浏览器兼容等方面。

Wt提供了一种极具吸引力的解决方案,这就是针对所谓的无状态槽的动态C++-to-JavaScript翻译机制。无状态槽即不管应用状态如何都能引起相同页面变化的一种槽。例如,上例中只要点击按钮,按钮即无条件地被禁用,我们可以看到该操作完全包含在MyWidget::doFumble()中,可见该操作是不需要任何应用状态或事件的具体细节的,因此Wt中无状态槽实现了客户端代码中的事件处理。

我们再看清单2中的代码,他们运行的效果是第一次调用MyWidget::disableFumbleButton()槽时Wt将其所实现的页面变化用相应的JavaScript代码表示出来并缓存于浏览器中,这样做的效果是第一次调用需要在页面更新被渲染前等待服务器往返延迟,但之后的调用则实际是简单地对JavaScript代码的再次执行。很明显,如果fumbling是我们希望用户重复执行的动作时,这种解决方案便捉襟见肘了。

class MyWidget : public WCompositeWidget
{
public:
MyWidget(WContainerWidget *parent = 0)
: WcompositeWidget(parent),
...
{
...
implementStateless(&MyWidget::disableFumbleButton);
fumbleButton_ = new WPushButton("Fumble");
fumbleButton_
->clicked.connect(SLOT(this, MyWidget::disableFumbleButton));
fumbleButton_->clicked.connect(SLOT(this, MyWidget::doFumble));
...
}
private:
WpushButton *fumbleButton_;
void disableFumbleButton()
{
fumbleButton_->disable();
}
void doFumble()
{
fumbleSome(...);
}
};
清单 2

当然,通过进一步努力,我们也还是可以消除上述第一次调用时发生的服务器往返延迟的,其方法是让Wt库在客户端事件触发之前便在内部调用无状态槽,这样Wt库就可以提前获得其引起的页面变化。另外,如果需要去掉这种自发的内部调用,则应该进一步提供undo函数。

使用同一个例子,我们做如下变动:

implementStateless
(&MyWidget::disableFumbleButton,
&MyWidget::undoDisableFumbleButton);
and implement this undo method:

void undoDisableFumbleButton()
{
fumbleButton_->enable();
}

Wt库已经为自身诸多内置widget方法提供了无状态实现,比如WWidget::hide() 和WWidget::show(),但偶尔也会包括WFormWidget::enable() 和WFormWidget::disable()。因为将这些规模不大的方法连接到信号非常方便,所以,可以在定义中直接给出自动实现客户端优化的方法,如清单3:

class MyWidget : public WCompositeWidget
{
public:
MyWidget(WcontainerWidget *parent = 0)
: WcompositeWidget(parent),

{

fumbleButton_ = new WpushButton(“Fumble”);
fumbleButton_
->clicked.connect(SLOT(fumbleButton_, WpushButton::disable));
fumbleButton_->clicked.connect(SLOT(this, MyWidget::doFumble));

}
private:
WpushButton *fumbleButton_;
void doFumble()
{
fumbleSome(…);
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: