65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 嵌入式 Web 服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (46投票s)

2008年9月12日

BSD

7分钟阅读

viewsIcon

489226

downloadIcon

8520

为 C++ 应用程序提供自己的网页

webem/webem.gif

引言

您是否有一个或两个网页?也许并不 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 日
  • 2010 年 5 月 30 日
    • Bug 修复:缺失的 HTML 输入字段值将使用前一个字段的值。
    • 功能:处理来自浏览器的 post 请求,适用于输入密码。
    • 功能:支持 Unicode 应用程序 include 函数,适用于中文字符。
    • 文档:源代码下的文件夹中包含示例,doxygen 生成的源代码文档。
  • 2014 年 6 月 23 日
    • 记录简单的按钮操作。
    • 将 CSS 支持和简单的按钮操作添加到工作区。
    • 简单的按钮操作演示应用程序。
© . All rights reserved.