httplite: C++ REST 处理类库





5.00/5 (8投票s)
有兴趣在您的 Windows C++ 应用程序中轻松实现 REST 通信吗?
引言
为什么选择 httplite
?
因为你并不总是想通过 ASP.NET 和 C++/CLI 来回到你的 C++ 代码中……仅仅是为了给你的 Windows C++ 应用添加一个额外的通信通道。
你不是在尝试创建或使用一个互联网规模的 Web 服务器。你只是想为进程间或局域网通信添加一些请求处理功能到你的应用中。
在本文中,你将了解 httplite
类库以及如何将其集成到你的 Windows 应用程序中。
这个类库使得为任何 Windows 应用程序添加 HTTP 请求处理变得非常简单,从而能够与任何 HTTP/1.0 客户端库进行 REST 通信,包括库本身提供的客户端库。
文章内容
httplite
最好的理解方式是查看与类库一起提供的概念验证应用 httpserver
。
这个应用对任何 GET
请求都响应 "reversrever
",对任何 POST
的 string
都将该 string
反转。 简单吧。 但在 ASP.NET 中实现这一点需要多少代码? 你的 C++ 代码,而且不简单。
httpserver - 概念验证 httplite HTTP/1.0 服务器
// This is all that's needed to integrate with httplite and process requests
#include "HttpServer.h"
#pragma comment(lib, "httplite")
using namespace httplite;
#include <iostream> // getline
// This function defines how the web server will response to requests.
// This is a basic request handler, handling GET with a simple string
// and POSTs by reversing and returning the posted string.
// This type of function can take advantage of the simple
// interface of the Request class to support rich request handling.
static Response HandleRequest(const Request& request)
{
Response response;
if (request.Verb == "GET")
{
// Put the response string into the Payload output object
response.Payload.emplace(L"reversrever");
}
else if (request.Verb == "POST")
{
// Get the POST'd string, reverse it, and set it as the output
std::wstring str = request.Payload->ToString();
std::reverse(str.begin(), str.end());
response.Payload.emplace(str);
}
return response;
}
int main(int argc, char* argv[])
{
uint16_t port = uint16_t(atoi(argv[1]));
printf("Starting serving on port %d...\n", (int)port);
HttpServer server(port, &HandleRequest);
server.StartServing(); // binds to port, accepts connections
printf("Hit [Enter] to stop serving and close the program:\n");
std::string line;
std::getline(std::cin, line); // program spends its life here
return 0;
}
与 httplite 集成
要与 httplite 集成以支持请求处理(服务)…
- 构建 httplite 解决方案
- 链接到
httplib.lib
静态库 - 在你的源代码中包含 HttpServer.h
- 使用以下签名编写你的请求处理程序
Response HandleRequest(const Request& request)
- 创建你的
HttpServer
对象,传入你选择的 TCP 端口和你的请求处理函数 - 当你的应用准备好处理请求时,调用
StartServing()
- 为了正常关闭,调用
StopServing()
。 请注意,在调用StopServing()
后,你不能再次调用StartServing
。 你可以创建一个新的HttpServer
对象来解决这个问题。
httplite 实现
库的大部分实现都由一个 abstract
基类 MessageBase
实现
class MessageBase
{
public:
virtual ~MessageBase() {} // for derived types
// Data structures common to Request and Response
std::unordered_map<std::string, std::string> Headers;
std::optional<Buffer> Payload;
// Header convenience methods
bool IsConnectionClose() const;
int GetContentLength() const;
// Core message networking routines
std::string Recv(SOCKET theSocket);
std::string Send(SOCKET theSocket) const;
// Request and Response need to define header behavior
virtual std::string GetTotalHeader() const = 0;
virtual std::string ReadHeader(const char* headerStart) = 0;
protected:
// Get the parts of the header that are common
// between Request and Response
std::string GetCommonHeader() const;
};
Recv
和 Send
是库中大部分代码所在的地方。 派生类型,Request
和 Response
只是实现 GetTotalHeader()
和 ReadHeader()
来处理 Request
和 Response
之间的头部差异。
GetTotalHeader()
必须获取成员变量并返回完整的 HTTP 头部。
ReadHeader()
必须解析 HTTP 头部并填充成员变量,在失败时返回错误消息,在成功时返回 ""。
派生类型,Request
和 Response
非常简单,仅添加特定于类型的成员变量以及头部生成和解析功能。
class Request : public MessageBase
{
public:
std::string Verb = "GET";
std::vector<std::wstring> Path;
std::unordered_map<std::wstring, std::wstring> Query;
virtual std::string GetTotalHeader() const;
virtual std::string ReadHeader(const char* headerStart);
};
class Response : public MessageBase
{
public:
std::string Status = "200 OK"; // Code + "decimal" part + Description,
// "500.100 Internal ASP Error"
std::uint16_t GetStatusCode() const;
std::wstring GetStatusDescription() const;
static Response CreateErrorResponse(uint16_t code, const std::string& msg);
virtual std::string GetTotalHeader() const;
virtual std::string ReadHeader(const char* headerStart);
};
HeaderReader 消耗 HTTP 头部
HeaderReader
类负责接收数据,直到找到所有重要的 \r\n\r\n
。 高效且干净地做到这一点是一项有趣的高级和低级编码练习。
class HeaderReader
{
public:
HeaderReader()
: m_headersEnd(nullptr)
, m_remainderStart(nullptr)
, m_remainderCount(0)
{}
/// <summary>
/// Call OnMoreData as data comes in, until it returns true,
/// or GetSize exceeds your threshold of pain of buffer memory usage.
/// Then you can call GetHeaders and GetRemainder to tease out the goodies.
/// </summary>
/// <param name="data">pointer to new data</param>
/// <param name="count">amount of new data</param>
/// <returns>true if headers fully read</returns>
bool OnMoreData(const uint8_t* data, const size_t count);
size_t GetSize() const
{
return m_buffer.size();
}
const char* GetHeaders() const
{
return reinterpret_cast<const char*>(m_buffer.data());
}
const uint8_t* GetRemainder(size_t& count) const
{
count = m_remainderCount;
return m_remainderStart;
}
private:
std::vector<uint8_t> m_buffer;
char* m_headersEnd;
uint8_t* m_remainderStart;
size_t m_remainderCount;
};
...
bool HeaderReader::OnMoreData(const uint8_t* data, const size_t count)
{
// You cannot call this function again once headers have been returned
if (m_headersEnd != nullptr)
{
assert(false);
throw NetworkError("OnMoreData called after headers read");
}
// Add the data to our buffer, and add a null-terminator so we can...
const size_t originalSize = m_buffer.size();
m_buffer.resize(m_buffer.size() + count + 1);
memcpy(m_buffer.data() + originalSize, data, count);
m_buffer.back() = 0;
// ...look in our buffer for the all-important \r\n\r\n with trusty strstr...
m_headersEnd = const_cast<char*>(strstr((const char*)m_buffer.data(), "\r\n\r\n"));
m_buffer.pop_back(); // strip our extra null byte now that we're done with it
if (m_headersEnd == nullptr)
{
// ...buffer is incomplete
return false;
}
else
{
// ...buffer is complete...seal it off
m_headersEnd[0] = '\0';
// Capture where the remainder starts and what's left, if any
m_remainderStart = reinterpret_cast<uint8_t*>(m_headersEnd) + 4;
m_remainderCount = m_buffer.size() - (m_remainderStart - m_buffer.data());
if (m_remainderCount == 0) // don't just point to any old place
m_remainderStart = nullptr;
return true;
}
}
结论
我希望你能将 httplite
集成到你的 Windows C++ 应用程序中,以支持进程间或网络通信的需求。
历史
- 2021年12月5日:初始版本