httplite: C++ REST Processing Class Library





5.00/5 (8 votes)
Interested in easily implementing REST communications in your Windows C++ apps?
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
// 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)...
- Build the httplite solution
- Link against the
httplib.lib
static library - Include HttpServer.h in your source
- Write your request handler with the signature:
Response HandleRequest(const Request& request)
- Create your
HttpServer
object, passing in the TCP port of your choosing and your request handler function - Call
StartServing()
when your app is ready to handle requests - Call
StopServing()
for orderly shutdown. Note that you cannot callStartServing
again after you have calledStopServing
. You could create a newHttpServer
object to solve that requirement.
httplite Implementation
Most of the library is implemented by an abstract
base class 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
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 Request
s and Response
s.
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.
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.
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