第5章:レスポンス生成とCGI
はじめに
Webサーバーの主要な役割は、リクエストに応じて適切なレスポンスを生成することです。本章では、静的ファイル配信、ディレクトリリスト、エラーページ、そしてCGI(Common Gateway Interface)の実装を学びます。
---
1. レスポンス生成の基礎
1.1 レスポンスの構造
HTTP/1.1 200 OK\r\n ← ステータスライン
Date: Mon, 01 Jan 2024 00:00:00 GMT\r\n
Server: webserv/1.0\r\n
Content-Type: text/html\r\n
Content-Length: 1234\r\n
Connection: keep-alive\r\n
\r\n ← 空行(ヘッダー終了)
<!DOCTYPE html> ← ボディ
<html>
...
</html>
1.2 Responseクラスの設計
class Response {
private:
int _statusCode;
std::string _statusMessage;
std::map<std::string, std::string> _headers;
std::vector<char> _body;
bool _chunked;
public:
Response();
Response(int statusCode);
void setStatus(int code);
void setHeader(const std::string& name, const std::string& value);
void setBody(const std::string& body);
void setBody(const std::vector<char>& body);
void setBody(const char* data, size_t len);
std::string buildResponse() const;
std::vector<char> buildBinaryResponse() const;
private:
std::string getReasonPhrase(int code) const;
std::string getCurrentDate() const;
};
Response::Response() : _statusCode(200), _chunked(false) {
_statusMessage = "OK";
setHeader("Server", "webserv/1.0");
setHeader("Date", getCurrentDate());
}
Response::Response(int statusCode) : _statusCode(statusCode), _chunked(false) {
_statusMessage = getReasonPhrase(statusCode);
setHeader("Server", "webserv/1.0");
setHeader("Date", getCurrentDate());
}
void Response::setStatus(int code) {
_statusCode = code;
_statusMessage = getReasonPhrase(code);
}
std::string Response::buildResponse() const {
std::ostringstream oss;
// ステータスライン
oss << "HTTP/1.1 " << _statusCode << " " << _statusMessage << "\r\n";
// ヘッダー
for (const auto& header : _headers) {
oss << header.first << ": " << header.second << "\r\n";
}
// Content-Length(設定されていない場合)
if (_headers.find("Content-Length") == _headers.end() && !_chunked) {
oss << "Content-Length: " << _body.size() << "\r\n";
}
// 空行
oss << "\r\n";
// ボディ
oss.write(_body.data(), _body.size());
return oss.str();
}
std::string Response::getReasonPhrase(int code) const {
static std::map<int, std::string> phrases = {
{100, "Continue"},
{200, "OK"},
{201, "Created"},
{204, "No Content"},
{301, "Moved Permanently"},
{302, "Found"},
{304, "Not Modified"},
{400, "Bad Request"},
{403, "Forbidden"},
{404, "Not Found"},
{405, "Method Not Allowed"},
{413, "Request Entity Too Large"},
{500, "Internal Server Error"},
{501, "Not Implemented"},
{502, "Bad Gateway"},
{503, "Service Unavailable"},
{504, "Gateway Timeout"}
};
auto it = phrases.find(code);
return (it != phrases.end()) ? it->second : "Unknown";
}
std::string Response::getCurrentDate() const {
time_t now = time(NULL);
struct tm* gmt = gmtime(&now);
char buffer[100];
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", gmt);
return std::string(buffer);
}
---
2. 静的ファイル配信
2.1 ファイル読み込み
#include <fstream>
#include <sys/stat.h>
class StaticFileHandler {
private:
std::string _rootDir;
public:
StaticFileHandler(const std::string& root) : _rootDir(root) {}
Response handleRequest(const Request& req) {
std::string path = resolvePath(req.getUri());
// パストラバーサル対策
if (!isPathSafe(path)) {
return createErrorResponse(403, "Forbidden");
}
// ファイルの存在確認
struct stat st;
if (stat(path.c_str(), &st) < 0) {
return createErrorResponse(404, "Not Found");
}
// ディレクトリの場合
if (S_ISDIR(st.st_mode)) {
return handleDirectory(path, req);
}
// ファイルの場合
return serveFile(path);
}
private:
std::string resolvePath(const std::string& uri) {
std::string path = _rootDir;
// URIの先頭の/を除去して結合
if (!uri.empty() && uri[0] == '/') {
path += uri;
} else {
path += "/" + uri;
}
return path;
}
bool isPathSafe(const std::string& path) {
// 正規化されたパスを取得
char resolved[PATH_MAX];
if (realpath(path.c_str(), resolved) == NULL) {
return false;
}
// rootDir外へのアクセスを禁止
return std::string(resolved).find(_rootDir) == 0;
}
Response serveFile(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file) {
return createErrorResponse(500, "Cannot read file");
}
// ファイルサイズ取得
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
// ファイル読み込み
std::vector<char> content(size);
file.read(content.data(), size);
// レスポンス生成
Response resp(200);
resp.setHeader("Content-Type", getMimeType(path));
resp.setBody(content);
return resp;
}
std::string getMimeType(const std::string& path) {
static std::map<std::string, std::string> mimeTypes = {
{".html", "text/html; charset=utf-8"},
{".htm", "text/html; charset=utf-8"},
{".css", "text/css"},
{".js", "application/javascript"},
{".json", "application/json"},
{".xml", "application/xml"},
{".txt", "text/plain"},
{".png", "image/png"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".gif", "image/gif"},
{".svg", "image/svg+xml"},
{".ico", "image/x-icon"},
{".pdf", "application/pdf"},
{".zip", "application/zip"},
{".mp3", "audio/mpeg"},
{".mp4", "video/mp4"}
};
size_t pos = path.rfind('.');
if (pos != std::string::npos) {
std::string ext = path.substr(pos);
auto it = mimeTypes.find(ext);
if (it != mimeTypes.end()) {
return it->second;
}
}
return "application/octet-stream";
}
};
2.2 ディレクトリリスト(autoindex)
#include <dirent.h>
Response StaticFileHandler::handleDirectory(const std::string& path,
const Request& req) {
// indexファイルを探す
std::vector<std::string> indexFiles = {"index.html", "index.htm"};
for (const auto& indexFile : indexFiles) {
std::string indexPath = path + "/" + indexFile;
struct stat st;
if (stat(indexPath.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
return serveFile(indexPath);
}
}
// autoindexが有効な場合
if (_autoindex) {
return generateDirectoryListing(path, req.getUri());
}
return createErrorResponse(403, "Forbidden");
}
Response StaticFileHandler::generateDirectoryListing(const std::string& path,
const std::string& uri) {
DIR* dir = opendir(path.c_str());
if (!dir) {
return createErrorResponse(500, "Cannot open directory");
}
std::ostringstream html;
html << "<!DOCTYPE html>\n"
<< "<html>\n"
<< "<head>\n"
<< "<meta charset=\"utf-8\">\n"
<< "<title>Index of " << uri << "</title>\n"
<< "<style>\n"
<< "body { font-family: monospace; margin: 20px; }\n"
<< "h1 { border-bottom: 1px solid #ccc; }\n"
<< "table { border-collapse: collapse; }\n"
<< "th, td { text-align: left; padding: 5px 20px; }\n"
<< "a { text-decoration: none; }\n"
<< "a:hover { text-decoration: underline; }\n"
<< "</style>\n"
<< "</head>\n"
<< "<body>\n"
<< "<h1>Index of " << uri << "</h1>\n"
<< "<table>\n"
<< "<tr><th>Name</th><th>Size</th><th>Modified</th></tr>\n";
// 親ディレクトリへのリンク
if (uri != "/") {
html << "<tr><td><a href=\"../\">../</a></td><td>-</td><td>-</td></tr>\n";
}
struct dirent* entry;
std::vector<std::pair<std::string, struct stat>> entries;
while ((entry = readdir(dir)) != NULL) {
std::string name = entry->d_name;
if (name == "." || name == "..") continue;
std::string fullPath = path + "/" + name;
struct stat st;
if (stat(fullPath.c_str(), &st) == 0) {
entries.push_back({name, st});
}
}
closedir(dir);
// ソート(ディレクトリ優先、名前順)
std::sort(entries.begin(), entries.end(),
[](const auto& a, const auto& b) {
bool aIsDir = S_ISDIR(a.second.st_mode);
bool bIsDir = S_ISDIR(b.second.st_mode);
if (aIsDir != bIsDir) return aIsDir > bIsDir;
return a.first < b.first;
});
for (const auto& entry : entries) {
const std::string& name = entry.first;
const struct stat& st = entry.second;
bool isDir = S_ISDIR(st.st_mode);
// サイズ
std::string size = isDir ? "-" : formatSize(st.st_size);
// 更新日時
char dateStr[50];
struct tm* tm = localtime(&st.st_mtime);
strftime(dateStr, sizeof(dateStr), "%Y-%m-%d %H:%M", tm);
// リンク
std::string displayName = name + (isDir ? "/" : "");
std::string href = name + (isDir ? "/" : "");
html << "<tr>"
<< "<td><a href=\"" << href << "\">" << displayName << "</a></td>"
<< "<td>" << size << "</td>"
<< "<td>" << dateStr << "</td>"
<< "</tr>\n";
}
html << "</table>\n"
<< "</body>\n"
<< "</html>\n";
Response resp(200);
resp.setHeader("Content-Type", "text/html; charset=utf-8");
resp.setBody(html.str());
return resp;
}
std::string StaticFileHandler::formatSize(off_t size) {
const char* units[] = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
double dsize = size;
while (dsize >= 1024 && unitIndex < 3) {
dsize /= 1024;
unitIndex++;
}
std::ostringstream oss;
if (unitIndex == 0) {
oss << size << " " << units[unitIndex];
} else {
oss << std::fixed << std::setprecision(1) << dsize << " " << units[unitIndex];
}
return oss.str();
}
---
3. エラーページ
3.1 デフォルトエラーページ
class ErrorPageHandler {
private:
std::map<int, std::string> _customPages; // ステータス → ファイルパス
public:
void setCustomPage(int status, const std::string& path) {
_customPages[status] = path;
}
Response createErrorResponse(int status, const std::string& message = "") {
// カスタムページがあれば使用
auto it = _customPages.find(status);
if (it != _customPages.end()) {
std::ifstream file(it->second);
if (file) {
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
Response resp(status);
resp.setHeader("Content-Type", "text/html; charset=utf-8");
resp.setBody(content);
return resp;
}
}
// デフォルトページ
return createDefaultErrorPage(status, message);
}
private:
Response createDefaultErrorPage(int status, const std::string& message) {
std::string reasonPhrase = getReasonPhrase(status);
std::string msg = message.empty() ? reasonPhrase : message;
std::ostringstream html;
html << "<!DOCTYPE html>\n"
<< "<html>\n"
<< "<head>\n"
<< "<meta charset=\"utf-8\">\n"
<< "<title>" << status << " " << reasonPhrase << "</title>\n"
<< "<style>\n"
<< "body { font-family: sans-serif; text-align: center; padding: 50px; }\n"
<< "h1 { font-size: 72px; margin: 0; color: #333; }\n"
<< "p { font-size: 24px; color: #666; }\n"
<< "</style>\n"
<< "</head>\n"
<< "<body>\n"
<< "<h1>" << status << "</h1>\n"
<< "<p>" << msg << "</p>\n"
<< "<hr>\n"
<< "<p><small>webserv/1.0</small></p>\n"
<< "</body>\n"
<< "</html>\n";
Response resp(status);
resp.setHeader("Content-Type", "text/html; charset=utf-8");
resp.setBody(html.str());
return resp;
}
};
---
4. ファイルアップロード
4.1 multipart/form-dataのパース
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk
Content-Length: 1234
------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
ファイルの内容...
------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="description"
説明文
------WebKitFormBoundary7MA4YWxk--
struct MultipartPart {
std::map<std::string, std::string> headers;
std::string name;
std::string filename;
std::string contentType;
std::vector<char> data;
};
class MultipartParser {
private:
std::string _boundary;
public:
MultipartParser(const std::string& contentType) {
// boundary を抽出
size_t pos = contentType.find("boundary=");
if (pos != std::string::npos) {
_boundary = "--" + contentType.substr(pos + 9);
// 引用符を除去
if (_boundary.front() == '"') {
_boundary = _boundary.substr(1, _boundary.size() - 2);
}
}
}
std::vector<MultipartPart> parse(const std::string& body) {
std::vector<MultipartPart> parts;
size_t pos = 0;
while (true) {
// 境界を探す
size_t start = body.find(_boundary, pos);
if (start == std::string::npos) break;
start += _boundary.length();
// 終了境界かチェック
if (body.substr(start, 2) == "--") break;
// CRLFをスキップ
start += 2;
// 次の境界を探す
size_t end = body.find(_boundary, start);
if (end == std::string::npos) break;
// パート解析
std::string partData = body.substr(start, end - start - 2);
MultipartPart part = parsePart(partData);
parts.push_back(part);
pos = end;
}
return parts;
}
private:
MultipartPart parsePart(const std::string& data) {
MultipartPart part;
// ヘッダーとボディの境界
size_t headerEnd = data.find("\r\n\r\n");
if (headerEnd == std::string::npos) return part;
// ヘッダー解析
std::string headers = data.substr(0, headerEnd);
std::istringstream iss(headers);
std::string line;
while (std::getline(iss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
size_t colonPos = line.find(':');
if (colonPos != std::string::npos) {
std::string name = line.substr(0, colonPos);
std::string value = trim(line.substr(colonPos + 1));
part.headers[name] = value;
// Content-Dispositionの解析
if (name == "Content-Disposition") {
parseContentDisposition(value, part);
}
}
}
// ボディ
std::string bodyStr = data.substr(headerEnd + 4);
part.data.assign(bodyStr.begin(), bodyStr.end());
return part;
}
void parseContentDisposition(const std::string& value, MultipartPart& part) {
// name="..." を抽出
size_t namePos = value.find("name=\"");
if (namePos != std::string::npos) {
size_t start = namePos + 6;
size_t end = value.find('"', start);
part.name = value.substr(start, end - start);
}
// filename="..." を抽出
size_t filenamePos = value.find("filename=\"");
if (filenamePos != std::string::npos) {
size_t start = filenamePos + 10;
size_t end = value.find('"', start);
part.filename = value.substr(start, end - start);
}
}
};
4.2 ファイル保存
class UploadHandler {
private:
std::string _uploadDir;
size_t _maxFileSize;
public:
UploadHandler(const std::string& dir, size_t maxSize)
: _uploadDir(dir), _maxFileSize(maxSize) {}
Response handleUpload(const Request& req) {
// Content-Typeを確認
std::string contentType = req.getHeader("content-type");
if (contentType.find("multipart/form-data") == std::string::npos) {
return ErrorPageHandler().createErrorResponse(400, "Bad Request");
}
// マルチパート解析
MultipartParser parser(contentType);
std::vector<MultipartPart> parts = parser.parse(req.getBody());
// ファイルを保存
std::vector<std::string> savedFiles;
for (const auto& part : parts) {
if (part.filename.empty()) continue;
// サイズチェック
if (part.data.size() > _maxFileSize) {
return ErrorPageHandler().createErrorResponse(
413, "File too large");
}
// ファイル名のサニタイズ
std::string safeName = sanitizeFilename(part.filename);
std::string path = _uploadDir + "/" + safeName;
// 重複回避
path = getUniquePath(path);
// 保存
std::ofstream file(path, std::ios::binary);
if (!file) {
return ErrorPageHandler().createErrorResponse(
500, "Cannot save file");
}
file.write(part.data.data(), part.data.size());
savedFiles.push_back(path);
}
// 成功レスポンス
Response resp(201);
resp.setHeader("Content-Type", "application/json");
std::ostringstream json;
json << "{\"uploaded\":[";
for (size_t i = 0; i < savedFiles.size(); ++i) {
if (i > 0) json << ",";
json << "\"" << savedFiles[i] << "\"";
}
json << "]}";
resp.setBody(json.str());
return resp;
}
private:
std::string sanitizeFilename(const std::string& filename) {
std::string result;
for (char c : filename) {
// 安全な文字のみ許可
if (std::isalnum(c) || c == '.' || c == '-' || c == '_') {
result += c;
}
}
// パストラバーサル対策
while (result.find("..") != std::string::npos) {
size_t pos = result.find("..");
result.erase(pos, 2);
}
return result.empty() ? "unnamed" : result;
}
std::string getUniquePath(const std::string& path) {
if (!fileExists(path)) return path;
// ファイル名と拡張子を分離
size_t dotPos = path.rfind('.');
std::string base = (dotPos != std::string::npos) ?
path.substr(0, dotPos) : path;
std::string ext = (dotPos != std::string::npos) ?
path.substr(dotPos) : "";
// 番号を付けて重複回避
int counter = 1;
std::string newPath;
do {
std::ostringstream oss;
oss << base << "_" << counter << ext;
newPath = oss.str();
counter++;
} while (fileExists(newPath));
return newPath;
}
};
---
5. CGI(Common Gateway Interface)
5.1 CGIの概要
CGIの仕組み:
1. サーバーがリクエストを受信
2. CGIスクリプトを子プロセスで実行
3. 環境変数でリクエスト情報を渡す
4. 標準入力でボディを渡す
5. 標準出力からレスポンスを受け取る
+--------+ +--------+ +--------+
| Client | -------> | Server | -------> | CGI |
| | <------- | | <------- | Script |
+--------+ +--------+ +--------+
fork/exec stdin/stdout
環境変数 レスポンス
5.2 CGI環境変数
std::map<std::string, std::string> CGIHandler::buildEnvVars(
const Request& req, const std::string& scriptPath) {
std::map<std::string, std::string> env;
// 必須変数
env["REQUEST_METHOD"] = methodToString(req.getMethod());
env["QUERY_STRING"] = req.getQueryString();
env["CONTENT_TYPE"] = req.getHeader("content-type");
env["CONTENT_LENGTH"] = std::to_string(req.getBody().size());
// サーバー情報
env["SERVER_NAME"] = "localhost";
env["SERVER_PORT"] = "8080";
env["SERVER_PROTOCOL"] = "HTTP/1.1";
env["SERVER_SOFTWARE"] = "webserv/1.0";
env["GATEWAY_INTERFACE"] = "CGI/1.1";
// スクリプト情報
env["SCRIPT_NAME"] = req.getUri();
env["SCRIPT_FILENAME"] = scriptPath;
env["PATH_INFO"] = "";
env["PATH_TRANSLATED"] = "";
// クライアント情報
env["REMOTE_ADDR"] = "127.0.0.1";
env["REMOTE_HOST"] = "";
// HTTPヘッダー(HTTP_*形式)
for (const auto& header : req.getHeaders()) {
std::string name = "HTTP_" + toUpperSnakeCase(header.first);
env[name] = header.second;
}
return env;
}
std::string CGIHandler::toUpperSnakeCase(const std::string& str) {
std::string result;
for (char c : str) {
if (c == '-') {
result += '_';
} else {
result += std::toupper(c);
}
}
return result;
}
5.3 CGI実行
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
class CGIHandler {
private:
std::string _cgiPath; // CGIインタープリタのパス
std::string _cgiExtension; // 拡張子(.py, .php等)
int _timeout; // タイムアウト秒数
public:
CGIHandler(const std::string& path, const std::string& ext, int timeout = 30)
: _cgiPath(path), _cgiExtension(ext), _timeout(timeout) {}
Response execute(const Request& req, const std::string& scriptPath) {
// パイプ作成
int pipeIn[2]; // 親→子(リクエストボディ)
int pipeOut[2]; // 子→親(レスポンス)
if (pipe(pipeIn) < 0 || pipe(pipeOut) < 0) {
return ErrorPageHandler().createErrorResponse(500, "Pipe failed");
}
// フォーク
pid_t pid = fork();
if (pid < 0) {
close(pipeIn[0]); close(pipeIn[1]);
close(pipeOut[0]); close(pipeOut[1]);
return ErrorPageHandler().createErrorResponse(500, "Fork failed");
}
if (pid == 0) {
// 子プロセス
executeChild(req, scriptPath, pipeIn, pipeOut);
exit(1); // execveが失敗した場合
}
// 親プロセス
close(pipeIn[0]); // 読み込み側を閉じる
close(pipeOut[1]); // 書き込み側を閉じる
// リクエストボディを送信
const std::string& body = req.getBody();
if (!body.empty()) {
write(pipeIn[1], body.data(), body.size());
}
close(pipeIn[1]);
// 子プロセスの出力を読み込み
std::string output = readWithTimeout(pipeOut[0], pid);
close(pipeOut[0]);
// 子プロセスの終了を待つ
int status;
waitpid(pid, &status, 0);
if (output.empty()) {
return ErrorPageHandler().createErrorResponse(502, "Bad Gateway");
}
return parseCGIOutput(output);
}
private:
void executeChild(const Request& req, const std::string& scriptPath,
int pipeIn[2], int pipeOut[2]) {
// 標準入出力をパイプにリダイレクト
dup2(pipeIn[0], STDIN_FILENO);
dup2(pipeOut[1], STDOUT_FILENO);
close(pipeIn[0]); close(pipeIn[1]);
close(pipeOut[0]); close(pipeOut[1]);
// 環境変数の設定
std::map<std::string, std::string> envMap = buildEnvVars(req, scriptPath);
std::vector<char*> envp;
for (const auto& pair : envMap) {
std::string envStr = pair.first + "=" + pair.second;
char* env = strdup(envStr.c_str());
envp.push_back(env);
}
envp.push_back(NULL);
// 作業ディレクトリを変更
std::string dir = scriptPath.substr(0, scriptPath.rfind('/'));
chdir(dir.c_str());
// 実行
char* argv[] = {
const_cast<char*>(_cgiPath.c_str()),
const_cast<char*>(scriptPath.c_str()),
NULL
};
execve(_cgiPath.c_str(), argv, envp.data());
// execveが失敗した場合
perror("execve");
exit(1);
}
std::string readWithTimeout(int fd, pid_t pid) {
std::string output;
char buffer[4096];
time_t startTime = time(NULL);
// 非ブロッキングに設定
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
while (true) {
// タイムアウトチェック
if (time(NULL) - startTime > _timeout) {
kill(pid, SIGKILL);
return "";
}
ssize_t n = read(fd, buffer, sizeof(buffer));
if (n > 0) {
output.append(buffer, n);
} else if (n == 0) {
// EOF
break;
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// データがまだない
usleep(10000); // 10ms待機
continue;
}
break;
}
}
return output;
}
Response parseCGIOutput(const std::string& output) {
// CGI出力形式:
// Content-Type: text/html\r\n
// Status: 200 OK\r\n(オプション)
// \r\n
// ボディ
size_t headerEnd = output.find("\r\n\r\n");
if (headerEnd == std::string::npos) {
// ヘッダーがない場合
Response resp(200);
resp.setHeader("Content-Type", "text/html");
resp.setBody(output);
return resp;
}
// ヘッダー解析
std::string headers = output.substr(0, headerEnd);
std::string body = output.substr(headerEnd + 4);
Response resp(200);
std::istringstream iss(headers);
std::string line;
while (std::getline(iss, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
size_t colonPos = line.find(':');
if (colonPos != std::string::npos) {
std::string name = line.substr(0, colonPos);
std::string value = trim(line.substr(colonPos + 1));
if (name == "Status") {
// Status: 302 Found
int code = std::stoi(value);
resp.setStatus(code);
} else {
resp.setHeader(name, value);
}
}
}
resp.setBody(body);
return resp;
}
};
5.4 サンプルCGIスクリプト
#!/usr/bin/env python3
# hello.py
import os
import sys
# CGIヘッダー
print("Content-Type: text/html")
print("") # 空行(ヘッダー終了)
# HTML出力
print("<!DOCTYPE html>")
print("<html>")
print("<head><title>CGI Test</title></head>")
print("<body>")
print("<h1>Hello from CGI!</h1>")
# 環境変数を表示
print("<h2>Environment Variables:</h2>")
print("<ul>")
for key, value in os.environ.items():
if key.startswith("HTTP_") or key in ["REQUEST_METHOD", "QUERY_STRING",
"CONTENT_TYPE", "CONTENT_LENGTH"]:
print(f"<li><b>{key}</b>: {value}</li>")
print("</ul>")
# POSTデータを表示
if os.environ.get("REQUEST_METHOD") == "POST":
print("<h2>POST Data:</h2>")
print("<pre>")
print(sys.stdin.read())
print("</pre>")
print("</body>")
print("</html>")
---
6. リダイレクト
6.1 HTTPリダイレクト
Response createRedirectResponse(int status, const std::string& location) {
Response resp(status);
resp.setHeader("Location", location);
std::string body = "Redirecting to " + location;
resp.setHeader("Content-Type", "text/plain");
resp.setBody(body);
return resp;
}
// 使用例
// 301: 恒久的リダイレクト
Response permanentRedirect = createRedirectResponse(301, "/new-location");
// 302: 一時的リダイレクト
Response tempRedirect = createRedirectResponse(302, "/temp-location");
// 307: 一時的リダイレクト(メソッド保持)
Response tempRedirectPreserveMethod = createRedirectResponse(307, "/api/v2");
---
7. DELETE処理
7.1 ファイル削除
Response DeleteHandler::handleDelete(const Request& req) {
std::string path = resolvePath(req.getUri());
// パストラバーサル対策
if (!isPathSafe(path)) {
return ErrorPageHandler().createErrorResponse(403, "Forbidden");
}
// ファイルの存在確認
struct stat st;
if (stat(path.c_str(), &st) < 0) {
return ErrorPageHandler().createErrorResponse(404, "Not Found");
}
// ディレクトリは削除不可
if (S_ISDIR(st.st_mode)) {
return ErrorPageHandler().createErrorResponse(403, "Cannot delete directory");
}
// 削除実行
if (unlink(path.c_str()) < 0) {
return ErrorPageHandler().createErrorResponse(500, "Delete failed");
}
// 成功レスポンス
Response resp(204); // No Content
return resp;
}
---
まとめ
本章で学んだこと:
- レスポンス生成: ステータスライン、ヘッダー、ボディ
- 静的ファイル配信: MIMEタイプ、パストラバーサル対策
- ディレクトリリスト: autoindex機能
- エラーページ: デフォルトとカスタム
- ファイルアップロード: multipart/form-data
- CGI: fork/exec、環境変数、パイプ通信
- リダイレクト: 301, 302, 307
- DELETE処理: ファイル削除
次章では、設定ファイルとテストを学びます。