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

httplite: C++ REST 处理类库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2021年12月6日

CPOL

2分钟阅读

viewsIcon

9800

downloadIcon

294

有兴趣在您的 Windows C++ 应用程序中轻松实现 REST 通信吗?

引言

为什么选择 httplite

因为你并不总是想通过 ASP.NET 和 C++/CLI 来回到你的 C++ 代码中……仅仅是为了给你的 Windows C++ 应用添加一个额外的通信通道。

你不是在尝试创建或使用一个互联网规模的 Web 服务器。你只是想为进程间或局域网通信添加一些请求处理功能到你的应用中。

在本文中,你将了解 httplite 类库以及如何将其集成到你的 Windows 应用程序中。

这个类库使得为任何 Windows 应用程序添加 HTTP 请求处理变得非常简单,从而能够与任何 HTTP/1.0 客户端库进行 REST 通信,包括库本身提供的客户端库。

文章内容

httplite 最好的理解方式是查看与类库一起提供的概念验证应用 httpserver

这个应用对任何 GET 请求都响应 "reversrever",对任何 POSTstring 都将该 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 集成以支持请求处理(服务)…

  1. 构建 httplite 解决方案
  2. 链接到 httplib.lib 静态库
  3. 在你的源代码中包含 HttpServer.h
  4. 使用以下签名编写你的请求处理程序
    Response HandleRequest(const Request& request)
  5. 创建你的 HttpServer 对象,传入你选择的 TCP 端口和你的请求处理函数
  6. 当你的应用准备好处理请求时,调用 StartServing()
  7. 为了正常关闭,调用 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;
};

RecvSend 是库中大部分代码所在的地方。 派生类型,RequestResponse 只是实现 GetTotalHeader()ReadHeader() 来处理 RequestResponse 之间的头部差异。

GetTotalHeader() 必须获取成员变量并返回完整的 HTTP 头部。

ReadHeader() 必须解析 HTTP 头部并填充成员变量,在失败时返回错误消息,在成功时返回 ""。

派生类型,RequestResponse 非常简单,仅添加特定于类型的成员变量以及头部生成和解析功能。

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日:初始版本
© . All rights reserved.