Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

httplite: C++ REST Processing Class Library

5.00/5 (8 votes)
6 Dec 2021CPOL2 min read 9.3K   264  
Interested in easily implementing REST communications in your Windows C++ apps?
This article documents the httplite C++ class library and how it can be integrated into Windows C++ applications for inter-process or network communication.

Introduction

Why httplite?

Because you do not always want to jump through ASP.NET through C++/CLI just to get back into your C++ code... just to add a side communication channel to your Windows C++ app.

You're not trying to create or use an internet-scale web server. You just want to add some request processing to your app for inter-process or LAN networking.

In this article, you will learn about the httplite class library and how you can integrate it into your Windows applications.

This class library makes it trivial to add HTTP request processing to any Windows application, enabling REST communication with any HTTP/1.0 client library, including the one provided by the library.

Article Body

httplite is best understood by looking at the proof of concept httpserver application shipped with the class library.

This app responds to any GET with "reversrever", and to any POST'd string with the string reversed. Simple enough. But how much plumbing would that take in ASP.NET to get to that C++? Your C++, non-trivial stuff.

httpserver - The Proof of Concept httplite HTTP/1.0 Server

C++
// 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;
}

Integrating with httplite

To integrate with httplite to power request processing (serving)...

  1. Build the httplite solution
  2. Link against the httplib.lib static library
  3. Include HttpServer.h in your source
  4. Write your request handler with the signature:
    C++
    Response HandleRequest(const Request& request)
  5. Create your HttpServer object, passing in the TCP port of your choosing and your request handler function
  6. Call StartServing() when your app is ready to handle requests
  7. Call StopServing() for orderly shutdown. Note that you cannot call StartServing again after you have called StopServing. You could create a new HttpServer object to solve that requirement.

httplite Implementation

Most of the library is implemented by an abstract base class MessageBase:

C++
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 and Send are where the bulk of the code in the library resides. The derived types, Request and Response just implement GetTotalHeader() and ReadHeader() to handle how headers are different between Requests and Responses.

GetTotalHeader() must take the member variables and return the complete HTTP header.

ReadHeader() must parse an HTTP header and populate the member variables, returning an error message on failure or "" on success.

The derived types, Request and Response are very simple, only adding type-specific member variables and header generation and parsing.

C++
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 Consumes HTTP Headers

The HeaderReader class is responsible for receiving data until the all-important \r\n\r\n is found. Doing this efficiently and cleanly was a fun exercise in high- and low-level coding.

C++
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;
	}
}

Conclusion

I hope you can integrate httplite into your Windows C++ applications to enable REST communications for your inter-process or network communication needs.

History

  • 5th December, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)