C++ 嵌入式 Web 服务器






4.77/5 (46投票s)
为 C++ 应用程序提供自己的网页
引言
您是否有一个或两个网页?也许并不 fancy,但能整洁地展示少量 HTML 标签可以实现什么?您是否有一个复杂的 C++ Windows 桌面应用程序,现在需要远程控制和监控?因此,无需学习全新的技术,让我们让您的应用程序拥有自己的网页!
Webem 是一个您可以嵌入到 C++ 应用程序中的 Web 服务器。它可以轻松地实现一个可从任何地方访问的浏览器 GUI。
Webem 基于一个经过最小修改的 boost::asio
Web 服务器版本,并允许 HTML 代码执行 C++ 方法。虽然您不需要查看服务器代码即可使用 Webem,但您需要在项目中使用 BOOST 库。我建议如果您以前从未使用过 BOOST,那么 Webem 可能不适合您。
背景
现有的嵌入式 C++ Web 服务器使用起来很有挑战性,而且往往不兼容 Windows。它们不是您为了能够从手机监控实验室应用程序而想要涉足的类型。
我尝试过 Wt (http://www.webtoolkit.eu/wt),但被其安装和学习曲线打败了。
我最近开始使用 John Bartas 的 Webio。我喜欢它的概念,而且它运行良好。
然而,我仍然觉得它使用起来过于复杂,而且服务器代码难以理解。我想要一个更易于使用、基于一个众所周知的 Web 服务器、并且仅进行了少量修改的服务器。
Webio 的复杂性很大程度上是由于使用了一个 HML 编译器来隐藏控制 GUI 外观的 HTML 页面,并将它们嵌入到应用程序代码的文件系统中。我更喜欢将 HTML 页面放在外面,一目了然,这样我就可以在不重新编译应用程序的情况下调整 GUI。
我在尝试调整 Webio 以符合我的喜好时学到了很多,最终我准备根据自己的要求完全构建自己的服务器。
Using the Code
您创建应用程序的 GUI 的方式与创建网站的方式完全相同——使用 HTML 创建页面,所有页面都从index.html 开始。
现在您需要让 HTML 调用您的 C++ 方法。您可以执行三件事:
- 创建 include 方法,这些方法生成要包含在网页中的 HTML。
- 创建 action 方法,当用户单击网页中的按钮时,webem 会调用这些方法。这些可以是简单的按钮,也可以是 html 表单。
- 创建“web controls”,这是前面两种方法的组合,其中 include 方法生成一个表单,当用户单击按钮时,该表单会调用 action 方法——“
Calendar
”示例应用程序展示了如何创建一个控件来显示和更新数据库表。
“Hello, World”
步骤 1:创建网页。您可以创建任意复杂的网页,但对于第一个“hello, world”应用程序,我们保持简单。
The Webem Embedded Web server says: <!--#webem hello -->
尖括号中的文本告诉 webem 您想在哪里包含来自应用程序的文本,“hello
”是必须调用的特定应用程序方法的代码,以提供包含的文本。
步骤 2:创建声明 hello
的类。
/// An application class which says hello
class cHello
{
public:
char * DisplayHTML()
{
return "Hello World";
}
};
步骤 3:初始化 Webem,告知它要监听浏览器请求的地址和端口,以及在哪里可以找到开始网页的index.html。
// Initialize web server.
http::server::cWebem theServer(
"0.0.0.0", // address
"1570", // port
".\\"); // document root
步骤 4:将应用程序方法注册到 webem。
cHello hello;
// register application method
// Whenever server sees <!--#webem hello -->
// call cHello::DisplayHTML() and include the HTML returned
theServer.RegisterIncludeCode( "hello",
boost::bind(
&cHello::DisplayHTML, // member function
&hello ) ); // instance of class
步骤 4:最后,您就可以开始运行服务器了。
// run the server
theServer.Run();
正式的 Hello
让我们创建一个更礼貌的程序,它会根据姓名来问候世界(毕竟 CodeProject 是加拿大的)。
步骤 1:创建网站。
What is your name, please?
<form action=name.webem>
<input name=yourname /><input value="Enter" type=submit />
</form>
The Webem Embedded Web server says: <!--#webem hello -->
表单提供了一个输入用户姓名的字段,以及一个提交输入姓名到服务器的按钮。表单属性“action=name.webem
”确保 webem 服务器将调用注册为“name
”的应用程序方法来处理输入。
步骤 2:创建应用程序类。
/// An application class which says hello to the identified user
class cHelloForm
{
string UserName;
http::server::cWebem& myWebem;
public:
cHelloForm( http::server::cWebem& webem ) :
myWebem( webem )
{
myWebem.RegisterIncludeCode( "hello",
boost::bind(
&cHelloForm::DisplayHTML, // member function
this ) ); // instance of class
myWebem.RegisterActionCode( "name",
boost::bind(
&cHelloForm::Action, // member function
this ) ); // instance of class
}
char * DisplayHTML()
{
static char buf[1000];
if( UserName.length() )
sprintf_s( buf, 999,
"Hello, %s", UserName.c_str() );
else
buf[0] = '\0';
return buf;
}
char * Action()
{
UserName = myWebem.FindValue("yourname");
return "/index.html";
}
};
应用程序类存储了对 webem 服务器的引用。这使得它可以在构造时注册自己的方法,并通过调用 cWebem
类 FindValue()
来提取表单中输入的字段的值。
应用程序类必须注册两个方法:一个是在单击提交按钮时保存输入的用户名,另一个是在组装网页并发送到浏览器时显示存储的用户名。
action 方法必须返回网页,该网页应该在响应单击提交按钮时显示。
请注意,所有 action 方法都在 include 方法之前被 Webem 调用,因此网页始终显示更新后的数据。
步骤 3:构造 webem,构造应用程序类并运行服务器。
// Initialize web server
http::server::cWebem theServer(
"0.0.0.0", // address
"1570", // port
".\\"); // document root
// Initialize application code
cHelloForm hello( theServer );
// run the server
theServer.Run();
您可能需要将服务器运行在另一个线程中,也许是为了让您的应用程序能够继续从实验室设备记录数据。要做到这一点,请修改对 server::run
的调用。
boost::thread* pThread = new boost::thread(
boost::bind(
&http::server::server::run, // member function
&theServer ) ); // instance of class
Webem 控件
Webem 控件是负责以标准方式显示和操作应用程序数据的类的集合,这样应用程序程序员就不必关心生成 HTML 文本的所有细节。
演示应用程序使用一个 webem 控件来列出 SQLITE 数据库表的内容,并具有添加或删除记录的功能。本文的顶部有一个截图。
Unicode
Webem 支持 Unicode 应用程序 include 函数,这意味着您的代码可以生成并显示中文、西里尔文,甚至克林贡字符。编写一个返回宽字符字符串(UTF-16 编码)的 include 函数,使用 RegisterIncludeCodeW()
函数(而不是 RegisterIncludeCode()
)进行注册,Webem 将在发送回浏览器之前将其转换为 UTF-8 编码。就像这样:
class cHello
{
public:
/**
Hello to the wide world, returning a wide character UTF-32 encoded string
with chinese characters
*/
wchar_t * DisplayWWHello()
{
return L"Hello Wide World.
Here are some chinese characters: \x751f\x4ea7\x8bbe\x7f6e";
}
};
...
theServer.RegisterIncludeCodeW( "wwwhello",
boost::bind(
&cHello::DisplayWWHello, // member function
&hello ) ); // instance of class
如果您想知道为什么需要 UTF-8 和 UTF-16,请查看我的博客文章 World Wide Characters。
简单的按钮操作
有时您可能需要在用户单击“按钮”时运行一个操作,但不需要传递任何参数。在这种情况下,设置一个 html 表单似乎太麻烦,而且在显示格式方面也很有限。所以我添加了点击操作请求。
如果您将此添加到您的 html 文件中:
<a href="http:/index.html/webem_name">button_label</a>
然后,当用户单击“button_label”时,webem 将调用注册为“name”的函数,然后显示 index.html 页面。
关注点
本节描述了 webem 如何与服务器集成。使用 webem 不需要阅读本节。
boost::asio
HTTP 服务器调用方法
request_handler::handle_request( const request& req, reply& rep)
这是解析浏览器请求并组装新页面以发送回浏览器的地方。我们需要在 request handler 的一个特化版本中重写此方法,以便可以调用已注册的应用程序方法。
void cWebemRequestHandler::handle_request( const request& req, reply& rep)
{
// check for webem action request
request req_modified = req;
myWebem.CheckForAction( req_modified.uri );
// call base method to do normal handling
request_handler::handle_request( req_modified, rep);
// Find and include any special cWebem strings
myWebem.Include( rep.content );
}
不幸的是,boost::asio
服务器虽然设计和实现都很优雅,但并非为继承而设计。为了让服务器调用 webem request handler,我对 boost 代码进行了最小的修改。
- 使
request_handler::handle_request
方法成为虚拟方法,以便调用特化版本。 - 更改服务器构造函数以接受它将使用的
request_handler
的引用。 - 更改服务器属性的顺序,以便在 request handler 初始化之前不会初始化 connection handler。
处理 Post 请求
boost:asio
服务器不处理 post 请求,post 请求通常用于输入密码。添加此功能并使其适用于不同浏览器(Firefox、Internet Explorer 和 Chrome)需要对 asio request handler 进行详细修改,我在这里不赘述。
历史
- 2008 年 9 月 12 日
- 首次发布
- 2008 年 9 月 15 日
- 将服务器构造移入
cWebem
构造函数,从而简化了应用程序代码。 - 提供了两个更简单的示例,“
Hello World
”和“Formal Hello
”。
- 将服务器构造移入
- 2008 年 9 月 29 日
- 修复了代码片段中的引号(希望如此!)。
- 2009 年 3 月 9 日
- 修复了 cWebem.h 中对修改后服务器代码的包含;修复了 release 配置中的构建错误。
- 2009 年 5 月 5 日
- 调整了 HTTP 头部
Content-Length
的值,显然有些浏览器会根据此长度截断显示。此更改由 https://codeproject.org.cn/Members/jaeheung72 提供。
- 调整了 HTTP 头部
- 2010 年 5 月 30 日
- Bug 修复:缺失的 HTML 输入字段值将使用前一个字段的值。
- 功能:处理来自浏览器的 post 请求,适用于输入密码。
- 功能:支持 Unicode 应用程序 include 函数,适用于中文字符。
- 文档:源代码下的文件夹中包含示例,doxygen 生成的源代码文档。
- 2014 年 6 月 23 日
- 记录简单的按钮操作。
- 将 CSS 支持和简单的按钮操作添加到工作区。
- 简单的按钮操作演示应用程序。