原文地址:
http://www.ddj.com/cpp/206401952
翻译:宇行
用来写C++ CGI 风格的应用程序
Wt 是一个可以免费获得的类库和应用服务 ,使得C++程序员写现代化的WEB应用程序使其拥有一个熟悉的 C++ GUI 程序风格。Wim领导Sobicom nv,一个软件工程咨询公司专门从事嵌入式系统设计和生物信息学。 Koen 完成了博士学位在医学科学方面。 我们可以分别联系 wdumon@gmail.com 和 koen.deforche@ gmail.com。
Wt 是一个可以免费获得的类库和应用服务 (www.webtoolkit.eu/wt) ,使得C++程序员写现代化的WEB应用程序使其拥有一个熟悉的 C++ GUI 程序风格。Wt 使 C++ 应用通过web浏览器使用。图 1, 实例, 是一个运行着的Wt应用—一个看起来像GMail 撰写, 完全被开启 AJAX ,在 C++中 使用 CSS 标记并书写完成。
从程序员的角度,Wt API 是类似那些被提供的类 ,如: Qt,Gtk,wxWindows等。然而,代替渲染widgets 到 Windows/X11/ windows,Wt增加渲染widgets 在 web 浏览器里。 Wt 完全隐藏下面的Web技术 (HTML, AJAX, XML, CGI, JavaScript, and DHTML), 选择渲染和活动管理策略依靠于浏览器的能力,并处理浏览器的方言。
对于一个本地化的 C++类库,web 应用开发Wt 通常拥有更高的效率和较少的运行信息比 Java 或者 Ruby方案。这样, Wt 适合于对效率要求高的设备,像嵌入式应用程序。
类简介
一个 Wt 会话开始的时候通过输入应用的URL地址, 最后当用户明确的退出应用程序的时候,或含蓄的留下的网页离开, 程序员们,会话管理是友好的类似于其他的GUI应用程序在多用户操作系统上 。在其最朴素的配置,为每一个新的Wt会话,产生一个进程且会话长期的被使用。服务 N 个会话 (例如,注册用户),因此有N 进程在运行。 Wt 能够配置使用 cookies 或者 URL重写绑定请求到正确的会话。 与此架构,全部的会话运行在自己的内存空间(崩溃的一个过程不能影响其他进程),会话不能相互通讯除非有明确的沟通机制被使用,以提供额外的安全保障。 因为这个模块是不恰当的或者期望是公用的 web 应用, Wt 可以配置所有多重会话到数量有限或者仅仅一个进程当中去。这样可以节省服务器资源和更能弹性的抵御拒绝服务攻击( Denial-of-Service attacks)。通过 Wt 提供的这些部署选择不会影响您的程序代码,但重要的是链接时间和运行时间。
Wt Widgets: 与浏览器交互
Figure 2 插图 Wt是 widget 层次。在顶部,WWidget exposes 方法为各个widget组织布局和样式。布局和视觉两者方面由自定义的 CSS。一个缺点是CSS布局模块是特意的标记文档,因此并不是直观的用户界面设计。
Wt的核心widgets,直接映射 创建HTML块,所有继承自 WWebWidget 。WWebWidget 如何详细的安装启用并进行改变 widget 。在HTML文档对象模型 (DOM)显示所有这些HTML功能原理。 见范例, HTML 相当于一个类WPushButton,HTML Text成为 WText , HTML tables成为类 WTable 。属性的HTML块(特性和容量) 转化为方法类, 名称相一致的类。 Widgets 相对应的 HTML元素支持键盘鼠标的相互作用,WInteractWidget 的继承,这里显露出了这些事件的 Wt::Signal<Event> 对象。widgets控件可以接受键盘焦点,能够启用/禁用WFormWidget的 后代,透漏附加的事件。类库的继承信息在浏览 DOM 和服务器 widget 树之间: 当服务器端的应用程序修改控件属性(改变颜色, enable/disabe, 或者变更控件树),这个改变被渲染在web 浏览器中 (通过AJAX, 如果有效的话)。在另一方面,任何改变或者事件的创建都通过 DOM自动反应在widget (例如修改编辑一个 HTML 文本输入那相当于一个WLineEdit widget)。 事件传送使用一个signal/slot 机制 (这是一个特殊的执行模式Observer pattern)。这两者是用户发起的事件动作 如改变文本,鼠标点击,或者改变焦点,除了通过源代码发起的事件之外。
下面是 WWebWidget ,有 WCompositeWidget。 在这里 WWebWidgets 尽可能揭露所有功能,可在他们的浏览器端实现,WCompositeWidgets 先进的实现了widget 功能其实现内容是完全封装。 见例子, WTreeNode , 这里在树的层次上定义了一个节点,是从内部应用的使用WImage 和 WText widgets规定在一个 2×2 的 WTable。 这个内部的机制不暴漏继承自 WCompositeWidget ,因此可以在未来以任何目的来改变。
组织一个新的指定外观的WCompositeWidget 通过构造现存的widgets (这可能是 WWebWidget 或者 WCompositeWidgets), 定义动作行为是通过链接slots 到不同的 signals 显示这些widgets。新 widgets 不局限于服务器端事件处理, 但能够使用动态翻译 C++-到-JavaScript 绕过服务器来回渲染视觉更新 (更多在这个版本)。 范例, WTreeNode使用此功能来执行客户端展开/折叠切换。
部署结构
HTTP通信协议与Web浏览器是抽象的Wt使用连接器执行。 两个连接器可实现在 Wt 2.x. 一个连接器使用的FastCGI协议, 让Wt 应用同时运行在许多服务器上需要通过FastCGI 插件(plug-ins)。第二个连接器包含一个轻巧的内置Web服务器,从而使Wt的应用可以开发和部署,需要有一个第三方Web服务器。
开放的FastCGI协议最初旨在让CGI应用程序是长期生活和处理多个请求。一个插件在Web服务器中转发请求对应的一个特定的Web应用程序通过本地套接字使进程长久运行。这个过程实现了一个 Wt 调度服务器,确定了会话的目的是请求和回复它到进程,实现本次会话。
另一方面,使用内置的Web服务器避免了一个中间协议监听 HTTP(S) 要求。在这种部署的情况下,强大的Web服务器仍然可以被用来作为前端Web服务器,然后通过请求WT应用设定代理。 由于其体积小,内置的Web服务器也是一个非常适合独立使用的嵌入式应用。也许最重要的是内置的Web服务器,简化了WT应用开发,因为你可能会一开始就 很容易地在调试器里调试,他们有一个进程和线程的结构可以配置一个简单的单线程在一个单一进程里,其中包括Web服务器。
世界永不满足
不同于桌面应用,Web应用程序会自动提供给国际观众。借助Wt提供这个功能的机会直接将你的应用程序国际化和本地化。本地化,Wt 提供资源消息束。一个消息资源捆绑定义了一套XML文件,每个地区。 每个XML文件连同本地化文字或XHTML与消息键。
Wt API 使用WString 提供的所有文本。WString 定义了一个隐含的标准字符串构造 (包括狭义和广义变种, C和C++),描述一个文字字符串,范例:
- WText *aText =
- new WText("Hello World!");
创建一个 text widget 简单显示 "Hello World!"
要创建本地化版本的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" 提供同样目录在相应的位置 nl Dutch:
- <messages>
- <message id=
- "hello-world">Dag Wereld!</message>
- </messages>
Internationalization requires support for characters that go beyond the 8-bit western character sets. For this purpose, Wt provides Unicode support for both literal and localized strings. For literal strings, you may specify and access content through narrow, wide, and UTF8 encoded strings, and therefore allow interfacing with legacy code. For localized strings, Wt relies on the extensive support for Unicode and different character encodings available in XML.
Server-Side Event Handling
Wt's signals and slots are implemented on top of the Boost.Signals package, which is part of the C++ Boost template libraries. All classes that inherit from WObject can use signals and slots with automatic care for connections when objects are destructed, because WObject inherits from boost::trackable.
Wt makes all user interaction with HTML DOM elements available by emitting the corresponding Wt::Signal. These events include keyboard events (keyWentDown, keyWentUp, keyPressed), mouse events (clicked, doubleClicked, mouseMoved, and so on) and many others. The event is only propagated if the corresponding signal has any connections, and therefore avoids unnecessary communication. Unfortunately, without availability of JavaScript, the application may only react to click events. Wt is designed to be functional even when the client has no JavaScript support, and since the availability of JavaScript is indicated using WEnvironment::javaScript(), the absence of richer interaction may be worked around if required, by providing alternative methods for essential operations; for example, by adding extra buttons.
Listing One illustrates a common Wt construct that instantiates a widget and reacts to some of its events (for brevity, we mix the implementation in the class declaration). In this example, a button is created as part of a new composite widget. When the button is clicked, the method doFumble() gets called, which in this case, disables the button (to prevent the user from clicking twice), and then performs some business logic.
- 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(...);
- }
- };
In this example, there is an obvious drawback—the button is only disabled after at least a client-server roundtrip, but also after all the fumbling has been done! This can easily be avoided by using client-side event handling.
Client-Side Event Handling
While server-side event handling can in principle do any kind of event handling, there are limitations because of the latency involved with the client-server roundtrip. When JavaScript is available in the browser, this roundtrip would not be necessary provided that the event handling is specified in JavaScript code. Even ignoring the extra complexity of specifying part of the functionality in JavaScript, and the bad reputation of that language (and its implementations), there are more fundamental problems with specifying part of the functionality in JavaScript. First, an alternative must be provided when JavaScript is not available. Second, the server-side application state is no longer synchronized with the browser-side state. Furthermore, with custom JavaScript, Wt cannot guarantee robustness against XSS attacks and cross-browser compatibility.
Wt provides an attractive alternative, which is a dynamic C++-to-JavaScript translation mechanism for so-called stateless slots in Wt. A stateless slot is any slot that adheres to the contract to always have the same visual effect regardless of application state. For example, to unconditionally disable the button as soon as it is clicked, as it is in MyWidget::doFumble(), no application state or event details are required and therefore the stateless slot learning in Wt may be used to implement this event handling in client-side code.
Consider the code changes in Listing Two. The effect of this code is that the JavaScript code for the MyWidget::disableFumbleButton() slot is learned on the first invocation of the event, and cached in the browser. Thus, the first invocation will require a server roundtrip before rendering the visual update, but subsequent invocations will simply execute the JavaScript again. Obviously, this kind of solution is not sufficient if the fumbling is not something that we expect the user to do repeatedly.
- 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(...);
- }
- };
With some extra effort, we may also eliminate the roundtrip for the visual update at the very first invocation. By letting the library invoke a stateless slot internally even before it is actually triggered by client-side code, the library may learn the visual changes that are implied by it. To undo the effect of this spontaneous internal invocation, an undo function must be provided.
Applied to the same example, you would change the call to:
- implementStateless
- (&MyWidget::disableFumbleButton,
- &MyWidget::undoDisableFumbleButton);
and implement this undo method:
- void undoDisableFumbleButton()
- {
- fumbleButton_->enable();
- }
The library provides stateless implementation for many of its built-in widget methods, such as WWidget::hide() and WWidget::show(), but incidentally also for WFormWidget::enable() and WFormWidget::disable(). Because it is convenient to connect these little methods directly to signals, the client-side optimization is automatically provided with the construct in Listing Three.
- 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(...);
- }
- };



