使用 boost/asio 的最小 HTTP Web 服务器






4.67/5 (11投票s)
一个使用 C++ 中的 boost::asio 实现的最小 HTTP Web 服务器的示例。
本周我想尝试使用 boost/asio 实现一个 Web 服务 - 我已经成功运行了 boost Web 服务器部分,并且想在你进一步深入之前让你看看。如果你打算构建我要展示的代码,我建议你首先从 https://boost.ac.cn/ 下载 boost。
示例结构
我将要呈现的示例代码分为 4 个部分。第一部分是主部分,负责启动程序、引导 boost/asio 的核心组件并监听每个会话;我将要呈现的第二部分将包含处理每个会话的整体结构的代码;最后,我将向你展示会话和响应的处理细节。为了使 C++ 代码能够编译,某些声明必须按顺序排列,因此我呈现这些部分的顺序将与编译器理解它们所需的顺序不同。不过不用担心,我将附上一些源代码供参考。
boost.asio
boost.asio 遵循一种称为 Proactor 模式的事件驱动编程模型,该模型本质上是一种异步编程模型。Proactor 模式的思想是:你发起一个操作,由其他人(你不需要关心是谁)执行该操作,稍后你会收到一个回调通知,表明操作已完成。此时,你可以发起另一个操作,并挂起另一个回调,当该操作完成时会收到通知。
现代 C++ 有一种简洁地表达回调的机制,称为 lambda 函数。如果你已经编程了一段时间,你可能在其他语言中见过 lambda,但它们对于 C++ 来说是新事物。虽然它不是一个新概念,但 C++ 中的 lambda 函数有一些特有的地方,允许你指定要捕获的变量,以及捕获时是按值还是按引用。这在我们的情况下很重要,因为我们将异步处理回调,可能需要引用 lambda 创建时在堆栈上但已不再作用域内的值。代码中值得注意的一个值是共享指针 'sesh'。我们想要一个对已创建对象的引用,但我们将共享指针按值传递给函数。因为它是共享指针,我们知道当最后一个引用超出作用域时,对象将被销毁。在这个示例中,我将通过 sesh 传递重要变量,当这个指针的最后一个副本超出作用域时,析构函数将为我清理连接。
入口点函数和会话处理
下面代码的具体细节是:入口点 'main' 将创建一个 io_service,设置一个端点和一个 acceptor,监听连接并异步接受它。当连接被接受时,接受 lambda 将会为下一个连接排队另一个接受请求,并与浏览器交互。
void accept_and_run(ip::tcp::acceptor& acceptor, io_service& io_service)
{
std::shared_ptr<session> sesh = std::make_shared<session>(io_service);
acceptor.async_accept(sesh->socket,
[sesh, &acceptor, &io_service](const error_code& accept_error)
{
accept_and_run(acceptor, io_service);
if(!accept_error)
{
session::interact(sesh);
}
});
}
int main(int argc, const char * argv[])
{
io_service io_service;
ip::tcp::endpoint endpoint{ip::tcp::v4(), 8080};
ip::tcp::acceptor acceptor{io_service, endpoint};
acceptor.listen();
accept_and_run(acceptor, io_service);
io_service.run();
return 0;
}
io_service、endpoint、acceptor 和 socket 是 boost 的数据类型,而 session 是一个用于本示例的类,它负责跟踪 HTTP 对话的状态,我将在下面进行描述。
HTTP 会话
我试图将大部分会话的异步行为隔离到它自己的类中。在这个类中,我们逐行读取,然后根据行的顺序、行的长度以及我们期望的内容来处理。由于我们期望的是 HTTP 流量,我们基本上需要一个包含请求行的行,后面跟着几个头,最后是内容体(如果指定了内容长度)。下面的代码逐行读取,并将这些行传递给 http_header 类,我将在稍后展示。最后,如果存在内容,也会读取。
class session
{
asio::streambuf buff;
http_headers headers;
static void read_body(std::shared_ptr<session> pThis)
{
int nbuffer = 1000;
std::shared_ptr<std::vector<char>> bufptr =
std::make_shared<std::vector<char>>(nbuffer);
asio::async_read(pThis->socket, boost::asio::buffer(*bufptr, nbuffer),
[pThis](const error_code& e, std::size_t s)
{
});
}
static void read_next_line(std::shared_ptr<session> pThis)
{
asio::async_read_until(pThis->socket, pThis->buff, '\r',
[pThis](const error_code& e, std::size_t s)
{
std::string line, ignore;
std::istream stream {&pThis->buff};
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
pThis->headers.on_read_header(line);
if(line.length() == 0)
{
if(pThis->headers.content_length() == 0)
{
std::shared_ptr<std::string> str =
std::make_shared<std::string>(pThis->headers.get_response());
asio::async_write(
pThis->socket,
boost::asio::buffer(str->c_str(), str->length()),
[pThis, str](const error_code& e, std::size_t s)
{
std::cout << "done" << std::endl;
});
}
else
{
pThis->read_body(pThis);
}
}
else
{
pThis->read_next_line(pThis);
}
});
}
static void read_first_line(std::shared_ptr<session> pThis)
{
asio::async_read_until(pThis->socket, pThis->buff, '\r',
[pThis](const error_code& e, std::size_t s)
{
std::string line, ignore;
std::istream stream {&pThis->buff};
std::getline(stream, line, '\r');
std::getline(stream, ignore, '\n');
pThis->headers.on_read_request_line(line);
pThis->read_next_line(pThis);
});
}
public:
ip::tcp::socket socket;
session(io_service& io_service)
:socket(io_service)
{
}
static void interact(std::shared_ptr<session> pThis)
{
read_first_line(pThis);
}
};
你会注意到这个类还有一些其他奇怪的地方。首先,所有方法都是静态的,并且我手动传递了 this 参数。我这样做是为了保留会话的引用计数。我知道我不想丢失它,因为当对象超出作用域时,套接字也会超出作用域,如果套接字丢失,对我们的 Web 服务器来说将是灾难性的。其次,由于所有方法都是异步的,每个方法还包含一个 lambda 函数,该函数将在操作完成时执行。这样列出代码使得方法及其结果更易读,同时又不影响其后续执行的能力。
提供页面
好的,这是服务器的最后一部分——提供页面本身。这次我包含了一个最小的服务,但付出了单独序列化每个结果的努力以提高可读性。有根目录,基本上是一个 hello world 页面。一个图标 'favicon.ico',是一个绿红相间的方块,以及一个 404 状态页面。你会注意到这一部分不是异步的,而且它不必是异步的。基本上,在这种情况下,我所做的只是写入一个字符串,而前一部分将发送出去。
unsigned char * get_icon(int* pOutSize);
class session;
class http_headers
{
std::string method;
std::string url;
std::string version;
std::map<std::string, std::string> headers;
public:
std::string get_response()
{
std::stringstream ssOut;
if(url == "/favicon.ico")
{
int nSize = 0;
unsigned char* data = get_icon(&nSize);
ssOut << "HTTP/1.1 200 OK" << std::endl;
ssOut << "content-type: image/vnd.microsoft.icon" << std::endl;
ssOut << "content-length: " << nSize << std::endl;
ssOut << std::endl;
ssOut.write((char*)data, nSize);
}
else if(url == "/")
{
std::string sHTML =
"<html><body><h1>Hello World</h1><p>This is a web server in c++</p></body></html>";
ssOut << "HTTP/1.1 200 OK" << std::endl;
ssOut << "content-type: text/html" << std::endl;
ssOut << "content-length: " << sHTML.length() << std::endl;
ssOut << std::endl;
ssOut << sHTML;
}
else
{
std::string sHTML =
"<html><body><h1>404 Not Found</h1><p>There's nothing here.</p></body></html>";
ssOut << "HTTP/1.1 404 Not Found" << std::endl;
ssOut << "content-type: text/html" << std::endl;
ssOut << "content-length: " << sHTML.length() << std::endl;
ssOut << std::endl;
ssOut << sHTML;
}
return ssOut.str();
}
int content_length()
{
auto request = headers.find("content-length");
if(request != headers.end())
{
std::stringstream ssLength(request->second);
int content_length;
ssLength >> content_length;
return content_length;
}
return 0;
}
void on_read_header(std::string line)
{
std::stringstream ssHeader(line);
std::string headerName;
std::getline(ssHeader, headerName, ':');
std::string value;
std::getline(ssHeader, value);
headers[headerName] = value;
}
void on_read_request_line(std::string line)
{
std::stringstream ssRequestLine(line);
ssRequestLine >> method;
ssRequestLine >> url;
ssRequestLine >> version;
std::cout << "request for resource: " << url << std::endl;
}
};
总结
好的,就是这样了。我今天之前没有用过 boost.asio,但我发现它很容易上手。文档有点晦涩难懂,但我确信大多数这些东西在你使用得越多,就越清楚。我喜欢这个软件的一点是,你可以轻松地实现异步功能,而无需处理线程等底层的东西。
感谢阅读,如果你有任何意见,我很乐意听取你的意见。
本文最初发布于: https://dabblingseriously.wordpress.com/2015/07/06/a-minimal-http-web-server-using-boostasio/
附注:图标数据
如果你真的感兴趣,这是图标代码——它只是一个带红色边框的绿色方块。
unsigned char icon_data[] = {
//reserved
0x00,0x00,
//icon type (1 = icon)
0x01,0x00,
//number of images (1)
0x01,0x00,
//width, height (16x16)
0x10,0x10,
//size of colour palette
0x00,
//reserved
0x00,
//colour planes (1)
0x01,0x00,
//bits per pixel (32)
0x20,0x00,
//size of data in bytes
0x28,0x04,0x00,0x00,
//offset of bitmap data
0x16,0x00,0x00,0x00,
//BEGIN BITMAPINFOHEADER
//bcsize
0x28,0x00,0x00,0x00, //biSize
0x10,0x00,0x00,0x00, //biWidth
0x20,0x00,0x00,0x00, //biHeight (with both AND and XOR mask? wtf?)
0x01,0x00, //biPlanes
0x20,0x00, //biBitCount (32)
0x00,0x00,0x00,0x00, //biCompression
0x00,0x00,0x00,0x00, //biSizeImage
0x00,0x00,0x00,0x00, //biXPelsPerMeter
0x00,0x00,0x00,0x00, //biYPelsPerMeter
0x00,0x00,0x00,0x00, //biClrUsed
0x00,0x00,0x00,0x00, //biClrImportant
//END BITMAPINFOHEADER
//BITMAP DATA (4 bytes per pixel)
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF ,0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF,
0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0x00,0xFF,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF,
0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF, 0x00,0xFF,0x00,0xFF
};
unsigned char* get_icon(int* pOut)
{
*pOut = sizeof(icon_data);
return icon_data;
}