Skip to content

Instantly share code, notes, and snippets.

@iKunalChhabra
Created August 31, 2025 08:48
Show Gist options
  • Save iKunalChhabra/97952e9cd35ac3e83a86b323d74fd37e to your computer and use it in GitHub Desktop.
Save iKunalChhabra/97952e9cd35ac3e83a86b323d74fd37e to your computer and use it in GitHub Desktop.
C++ server from scratch
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdexcept>
#include <unordered_map>
#include <algorithm>
#include <filesystem>
#include <thread>
#include <fstream>
#include <ranges>
#include <unordered_set>
#include <zlib.h>
void lower(std::string& v)
{
std::ranges::transform(v, v.begin(), ::tolower);
}
void removeWhitespace(std::string& v)
{
std::erase_if(v, ::isspace);
}
struct RequestParts
{
std::string method;
std::string path;
std::string version;
std::unordered_map<std::string, std::string> headers;
std::string body;
};
class FileHandler
{
public:
static bool exists(const std::string& path)
{
const std::filesystem::path p(path);
return std::filesystem::exists(p);
}
static std::string readFile(const std::string& path)
{
std::ifstream file(path);
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return content;
}
static void writeFile(const std::string& path, const std::string& content)
{
std::ofstream file(path);
file << content;
file.close();
}
};
class Response
{
static std::string compressString(const std::string& str) {
const int level = Z_BEST_COMPRESSION;
z_stream zs{};
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
// Use deflateInit2 with 15+16 to produce a gzip-compatible stream
if (deflateInit2(&zs, level, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
throw std::runtime_error("deflateInit2 failed");
zs.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(str.data()));
zs.avail_in = static_cast<uInt>(str.size());
std::string outString;
char outBuffer[32768];
int ret;
// Compress until the end of stream
do {
zs.next_out = reinterpret_cast<Bytef*>(outBuffer);
zs.avail_out = sizeof(outBuffer);
ret = deflate(&zs, zs.avail_in ? Z_NO_FLUSH : Z_FINISH);
if (ret == Z_STREAM_ERROR)
{
deflateEnd(&zs);
throw std::runtime_error("deflate stream error");
}
// Append the bytes that were just produced
size_t have = sizeof(outBuffer) - zs.avail_out;
if (have)
outString.append(outBuffer, have);
} while (ret != Z_STREAM_END);
if (deflateEnd(&zs) != Z_OK)
throw std::runtime_error("deflateEnd failed");
return outString;
}
public:
std::string HTTP_VERSION = "HTTP/1.1";
std::string HTTP_SUCCESS = "200 OK";
std::string HTTP_CREATED = "201 Created";
std::string HTTP_NOT_FOUND = "404 Not Found";
std::unordered_map<std::string, std::string> headers;
std::unordered_set<std::string> encodingSupported = {"gzip"};
std::string http_code;
std::string content;
void selectEncoding(const std::string& acceptEncoding)
{
// parse requested encodings
std::unordered_set<std::string> requestedEncodings;
for (constexpr char delimiter = ','; auto&& part : acceptEncoding | std::views::split(delimiter)) {
if (std::string token(part.begin(), part.end()); !token.empty()) {
requestedEncodings.insert(token);
}
}
// select encoding
for (auto& encoding : requestedEncodings)
{
if (encodingSupported.contains(encoding))
{
headers["Content-Encoding"] = encoding;
}
}
}
void handleConnectionClose(const RequestParts& request)
{
if (request.headers.contains("connection") && request.headers.at("connection") == "close")
{
headers["Connection"] = "close";
}
else
{
headers["Connection"] = "keep-alive";
}
}
explicit Response(const RequestParts& request)
{
if (request.headers.contains("accept-encoding"))
{
selectEncoding(request.headers.at("accept-encoding"));
}
handleConnectionClose(request);
}
Response& addContent(const std::string& v)
{
if (headers.contains("Content-Encoding") && headers.at("Content-Encoding") == "gzip")
{
this->content = compressString(v);
return *this;
}
this->content = v;
return *this;
}
Response& setStatusCode(const std::string& v)
{
this->http_code = v;
return *this;
}
Response& addHeader(const std::string& k, const std::string& v)
{
this->headers[k] = v;
return *this;
}
std::string buildResponse()
{
std::string response = HTTP_VERSION + " " + http_code + "\r\n";
for (auto& [k, v] : headers)
{
response += (k + ": " + v + "\r\n");
}
response += ("Content-Length: " + std::to_string(content.length()) + "\r\n");
response += "\r\n";
response += content;
return response;
}
};
class Server
{
int server_fd = -1;
int port = 4221;
sockaddr_in server_addr{};
sockaddr_in client_addr{};
std::string directory;
public:
void createServerSocket()
{
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0)
{
throw std::runtime_error("Failed to create server socket\n");
}
}
void setSocketOptions() const
{
constexpr int reuse = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
throw std::runtime_error("setsockopt failed\n");
}
}
void BindServerSocket()
{
server_addr.sin_family = AF_INET; // IPV4
server_addr.sin_addr.s_addr = INADDR_ANY; // Any Address
server_addr.sin_port = htons(port); // PORT
if (bind(server_fd, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)) < 0)
{
throw std::runtime_error("Failed to bind to port 4221\n");
}
}
void listenOnSocket() const
{
if (constexpr int connection_backlog = 5; listen(server_fd, connection_backlog) != 0)
{
throw std::runtime_error("listen failed\n");
}
}
int connectToClient()
{
socklen_t client_addr_len = sizeof(client_addr);
const int client_fd = accept(server_fd, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
if (client_fd < 0)
{
throw std::runtime_error("Failed to accept connection\n");
}
std::cout << "Client connected\n";
return client_fd;
}
static std::string receiveFromClient(const int client_fd)
{
char requestBuffer[4096]{};
const ssize_t sz = recv(client_fd, requestBuffer, sizeof(requestBuffer) - 1, 0);
if (sz < 0)
{
throw std::runtime_error("Failed to read from connection\n");
}
if (sz == 0)
{
// Client closed connection gracefully
throw std::runtime_error("Client disconnected\n");
}
std::cout << "Request received from client\n";
requestBuffer[sz] = '\0'; // make it a C-string
std::string request(requestBuffer);
return request;
}
static RequestParts parseRequest(const std::string& request)
{
std::cout << "Raw Request***\n" << request << "\n***\n";
RequestParts parts{};
// process first line
const size_t lineEnd = request.find("\r\n");
std::string firstLine = request.substr(0, lineEnd);
const size_t space1 = firstLine.find(' ');
const size_t space2 = firstLine.find(' ', space1 + 1);
parts.method = firstLine.substr(0, space1);
parts.path = firstLine.substr(space1 + 1, space2 - (space1 + 1));
parts.version = firstLine.substr(space2 + 1);
// process each header
const size_t headerEnd = request.find("\r\n\r\n");
std::string headers = request.substr(lineEnd + 2, headerEnd - (lineEnd + 2));
headers += "\r\n";
std::cout << "Headers***\n" << headers << "\n***\n";
size_t headerEnd2 = headers.find("\r\n");
while (headerEnd2 != std::string::npos)
{
const size_t colon = headers.find(':');
std::string headerName = headers.substr(0, colon);
std::string headerValue = headers.substr(colon + 1, headerEnd2 - (colon + 1));
std::cout << "Parsed Header: " << headerName << " " << headerValue << "\n";
lower(headerName);
removeWhitespace(headerName);
removeWhitespace(headerValue);
parts.headers[headerName] = headerValue;
std::cout << "Saved Header: " << headerName << " " << headerValue << "\n";
headers = headers.substr(headerEnd2 + 2);
headerEnd2 = headers.find("\r\n");
}
parts.body = request.substr(headerEnd + 4);
return parts;
}
void sendResponse(const int client_fd, const RequestParts& requestParts) const
{
std::string response;
std::cout << "path is " << requestParts.path << "\n";
if (requestParts.path.length() == 1)
{
Response r(requestParts);
response = r.setStatusCode(r.HTTP_SUCCESS)
.buildResponse();
}
else if (requestParts.path.starts_with("/echo/"))
{
const std::string echo = requestParts.path.substr(6);
Response r(requestParts);
response = r.setStatusCode(r.HTTP_SUCCESS)
.addContent(echo)
.addHeader("Content-Type", "text/plain")
.buildResponse();
}
else if (requestParts.method=="GET" && requestParts.path.starts_with("/files/"))
{
const std::string filename = requestParts.path.substr(6);
std::string filePath = directory + "/" + filename;
std::cout << "filePath is " << filePath << "\n";
if (!FileHandler::exists(filePath))
{
Response r(requestParts);
response = r.setStatusCode(r.HTTP_NOT_FOUND)
.buildResponse();
}
else
{
Response r(requestParts);
response = r.setStatusCode(r.HTTP_SUCCESS)
.addContent(FileHandler::readFile(filePath))
.addHeader("Content-Type", "application/octet-stream")
.buildResponse();
}
}
else if (requestParts.method=="POST" && requestParts.path.starts_with("/files/"))
{
const std::string filename = requestParts.path.substr(6);
std::string filePath = directory + "/" + filename;
std::cout << "filePath is " << filePath << "\n";
FileHandler::writeFile(filePath, requestParts.body);
Response r(requestParts);
response = r.setStatusCode(r.HTTP_CREATED)
.buildResponse();
}
else if (requestParts.path.starts_with("/user-agent"))
{
std::string userAgent;
// std::println("Available headers: {}", requestParts.headers);
if (requestParts.headers.contains("user-agent"))
{
userAgent = requestParts.headers.at("user-agent");
}
else
{
userAgent = "";
}
Response r(requestParts);
response = r.setStatusCode(r.HTTP_SUCCESS)
.addContent(userAgent)
.addHeader("Content-Type", "text/plain")
.buildResponse();
}
else
{
Response r(requestParts);
response = r.setStatusCode(r.HTTP_NOT_FOUND)
.buildResponse();
}
std::cout << "Sending response to client\n";
send(client_fd, response.c_str(), response.size(), 0);
}
void handleClient(const int client_fd) const
{
std::cout << "Handling client with client fd = " << client_fd << "\n";
while (true)
{
try {
const std::string request = receiveFromClient(client_fd);
const auto requestParts = parseRequest(request);
sendResponse(client_fd, requestParts);
if (requestParts.headers.contains("connection") && requestParts.headers.at("connection") == "close" )
{
close(client_fd);
break;
}
} catch (const std::runtime_error& e) {
std::cout << "Client handling error: " << e.what() << "\n";
// close(client_fd);
}
}
}
[[noreturn]] void startServer()
{
std::cout << "Waiting for a client to connect...\n";
while (true)
{
const int client_fd = connectToClient();
std::thread([this, client_fd]
{
handleClient(client_fd);
}).detach();
}
}
explicit Server(const std::string& d)
{
directory = d;
createServerSocket();
setSocketOptions();
BindServerSocket();
listenOnSocket();
}
~Server()
{
close(server_fd);
std::cout << "Client Disconnected\n";
}
};
// ./your_program.sh --directory /tmp/
int main(const int argc, char** argv)
{
// std::cout << std::unitbuf;
// std::cerr << std::unitbuf;
std::string directory = ".";
if (argc==3 && std::string(argv[1]) == "--directory")
{
directory = argv[2];
if (directory.back() == '/')
{
directory.pop_back();
}
}
std::cout << "Directory is " << directory << "\n";
Server server(directory);
server.startServer();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment