Click here to Skip to main content
15,867,330 members
Articles / Programming Languages / C++
Article

httplite: C++ REST Processing Class Library

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
6 Dec 2021CPOL2 min read 7.7K   244   12   12
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)


Written By
Software Developer
United States United States
Michael Balloni is a manager of software development at a cybersecurity software and services provider.

Check out https://www.michaelballoni.com for all the programming fun he's done over the years.

He has been developing software since 1994, back when Mosaic was the web browser of choice. IE 4.0 changed the world, and Michael rode that wave for five years at a .com that was a cloud storage system before the term "cloud" meant anything. He moved on to a medical imaging gig for seven years, working up and down the architecture of a million-lines-code C++ system.

Michael has been at his current cybersecurity gig since then, making his way into management. He still loves to code, so he sneaks in as much as he can at work and at home.

Comments and Discussions

 
QuestionIs this a bug? Pin
nzmCoder21-Apr-22 12:00
nzmCoder21-Apr-22 12:00 
First, I think this is exactly what I need to add a REST interface to an existing Windows application to allow REST queries to get status, etc.

Second, I am having trouble receiving payload data when sending it using the Postman.exe application. The server will always hang waiting for more data. I discovered this is because the function: MessageBase::GetContentLength() is not getting the correct message length. It doesn't find the "Content-Length" tag in the message.

The server works fine when using curl.exe.

Is this operator error with Postman.exe?
Thoughts?

modified 21-Apr-22 18:13pm.

AnswerRe: Is this a bug? Pin
Michael Sydney Balloni21-Apr-22 13:37
professionalMichael Sydney Balloni21-Apr-22 13:37 
GeneralRe: Is this a bug? Pin
nzmCoder21-Apr-22 15:02
nzmCoder21-Apr-22 15:02 
Questiongreat Pin
Southmountain7-Jan-22 4:49
Southmountain7-Jan-22 4:49 
AnswerRe: great Pin
Michael Sydney Balloni8-Jan-22 5:32
professionalMichael Sydney Balloni8-Jan-22 5:32 
QuestionUseful library Pin
_Flaviu7-Dec-21 21:51
_Flaviu7-Dec-21 21:51 
AnswerRe: Useful library Pin
Michael Sydney Balloni8-Dec-21 3:44
professionalMichael Sydney Balloni8-Dec-21 3:44 
GeneralRe: Useful library Pin
_Flaviu8-Dec-21 23:59
_Flaviu8-Dec-21 23:59 
QuestionDoes it support HTTPS? Pin
Shao Voon Wong6-Dec-21 15:30
mvaShao Voon Wong6-Dec-21 15:30 
AnswerRe: Does it support HTTPS? Pin
Michael Sydney Balloni7-Dec-21 4:30
professionalMichael Sydney Balloni7-Dec-21 4:30 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA6-Dec-21 1:21
professionalȘtefan-Mihai MOGA6-Dec-21 1:21 
GeneralRe: My vote of 5 Pin
Michael Sydney Balloni6-Dec-21 9:15
professionalMichael Sydney Balloni6-Dec-21 9:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.