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
#include "HttpServer.h"
#pragma comment(lib, "httplite")
using namespace httplite;
#include <iostream> // getline
static Response HandleRequest(const Request& request)
{
Response response;
if (request.Verb == "GET")
{
response.Payload.emplace(L"reversrever");
}
else if (request.Verb == "POST")
{
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();
printf("Hit [Enter] to stop serving and close the program:\n");
std::string line;
std::getline(std::cin, line); 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 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
:
class MessageBase
{
public:
virtual ~MessageBase() {}
std::unordered_map<std::string, std::string> Headers;
std::optional<Buffer> Payload;
bool IsConnectionClose() const;
int GetContentLength() const;
std::string Recv(SOCKET theSocket);
std::string Send(SOCKET theSocket) const;
virtual std::string GetTotalHeader() const = 0;
virtual std::string ReadHeader(const char* headerStart) = 0;
protected:
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";
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)
{}
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)
{
if (m_headersEnd != nullptr)
{
assert(false);
throw NetworkError("OnMoreData called after headers read");
}
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;
m_headersEnd = const_cast<char*>(strstr((const char*)m_buffer.data(), "\r\n\r\n"));
m_buffer.pop_back(); if (m_headersEnd == nullptr)
{
return false;
}
else
{
m_headersEnd[0] = '\0';
m_remainderStart = reinterpret_cast<uint8_t*>(m_headersEnd) + 4;
m_remainderCount = m_buffer.size() - (m_remainderStart - m_buffer.data());
if (m_remainderCount == 0) 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