HTTP通信におけるデータ処理は、シリアライズとデシリアライズのプロセスが不可欠です。本記事では、TCPを基盤とするバイトストリームを解析し、HTTPリクエストとレスポンスを構造化して処理する実装方法を詳細に解説します。実践的なコード例を通じて、リクエスト構文の分解、ヘッダー解析、静的ファイル返却の実現手順を示します。
リクエスト解析の実装
HTTPリクエストの解析処理では、バッファから構造化データを抽出する必要があります。以下に、リクエスト行、ヘッダー、ボディの解析を担当するクラスを実装します。
class HttpRequest {
public:
HttpRequest() = default;
bool parseFromBuffer(std::string& buffer);
void logParsedData() const;
private:
std::string requestLine;
std::unordered_map<std::string, std::string> headers;
std::string bodyContent;
};バッファからのデータ抽出は、改行文字を基準に処理します。以下は行単位の抽出関数です。
bool extractLine(std::string& buffer, std::string& line) {
const size_t pos = buffer.find("\r\n");
if (pos == std::string::npos) return false;
line = buffer.substr(0, pos);
buffer.erase(0, pos + 2);
return true;
}リクエスト行の解析は、スペース区切りでメソッド、URI、HTTPバージョンを分離します。
bool HttpRequest::parseFromBuffer(std::string& buffer) {
std::string line;
if (!extractLine(buffer, line)) return false;
requestLine = line;
// メソッド/URI/バージョンの分離
std::istringstream iss(line);
iss >> method >> uri >> httpVersion;
// ヘッダー解析
while (extractLine(buffer, line)) {
if (line.empty()) break;
const size_t colonPos = line.find(':');
if (colonPos == std::string::npos) continue;
const std::string key = line.substr(0, colonPos);
const std::string value = line.substr(colonPos + 1);
headers[key] = value;
}
bodyContent = buffer;
return true;
}レスポンス構築処理
レスポンス生成では、リクエストに基づいて適切な状態コードとコンテンツを返却します。ファイル存在確認のため、ウェブルートディレクトリを基準にパスを補正します。
class HttpResponse {
public:
HttpResponse() : httpVersion("HTTP/1.1") {}
void buildResponse(const HttpRequest& req);
bool serialize(std::string& output) const;
private:
std::string httpVersion;
int statusCode;
std::string statusDesc;
std::unordered_map<std::string, std::string> responseHeaders;
std::string responseBody;
};ファイル存在確認と状態コード設定の処理を実装します。
std::string loadFileContent(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) return "";
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void HttpResponse::buildResponse(const HttpRequest& req) {
// ウェブルートパスの補正
std::string fullPath = "wwwroot/" + req.uri;
if (fullPath.back() == '/') fullPath += "index.html";
responseBody = loadFileContent(fullPath);
statusCode = responseBody.empty() ? 404 : 200;
statusDesc = (statusCode == 200) ? "OK" : "Not Found";
responseHeaders["Content-Type"] = "text/html";
responseHeaders["Server"] = "CustomWebServer/1.0";
}シリアライズ処理では、HTTP構文規則に従ってデータを整形します。
bool HttpResponse::serialize(std::string& output) const {
output = httpVersion + " " + std::to_string(statusCode) + " " + statusDesc + "\r\n";
for (const auto& [key, value] : responseHeaders) {
output += key + ": " + value + "\r\n";
}
output += "\r\n";
output += responseBody;
return true;
}実行時の動作確認
サーバー実装では、クライアント接続時にリクエストを解析し、適切なレスポンスを生成します。以下は主要処理フローです。
void handleConnection(int socket) {
std::string buffer;
recv(socket, buffer.data(), buffer.size(), 0);
HttpRequest request;
if (!request.parseFromBuffer(buffer)) {
// エラーレスポンス生成
return;
}
HttpResponse response;
response.buildResponse(request);
std::string output;
response.serialize(output);
send(socket, output.data(), output.size(), 0);
}実際の動作では、ルートパスへのアクセス時にindex.htmlを返却し、存在しないリソースには404エラーページを返します。この実装により、基本的なWebサーバー機能を実現できます。