You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
4.3 KiB
C++
174 lines
4.3 KiB
C++
#include "webcc/request_handler.h"
|
|
|
|
#include <fstream>
|
|
#include <utility> // for move()
|
|
#include <vector>
|
|
|
|
#include "webcc/logger.h"
|
|
#include "webcc/request.h"
|
|
#include "webcc/response.h"
|
|
#include "webcc/url.h"
|
|
|
|
#if WEBCC_ENABLE_GZIP
|
|
#include "webcc/gzip.h"
|
|
#endif
|
|
|
|
namespace webcc {
|
|
|
|
RequestHandler::RequestHandler(const std::string& doc_root)
|
|
: doc_root_(doc_root) {
|
|
}
|
|
|
|
bool RequestHandler::Bind(ServicePtr service, const std::string& url,
|
|
bool is_regex) {
|
|
return service_manager_.Add(service, url, is_regex);
|
|
}
|
|
|
|
void RequestHandler::Enqueue(ConnectionPtr connection) {
|
|
queue_.Push(connection);
|
|
}
|
|
|
|
void RequestHandler::Start(std::size_t count) {
|
|
assert(count > 0 && workers_.size() == 0);
|
|
|
|
for (std::size_t i = 0; i < count; ++i) {
|
|
workers_.emplace_back(std::bind(&RequestHandler::WorkerRoutine, this));
|
|
}
|
|
}
|
|
|
|
void RequestHandler::Stop() {
|
|
LOG_INFO("Stopping workers...");
|
|
|
|
// Clear pending connections.
|
|
// The connections will be closed later (see Server::DoAwaitStop).
|
|
LOG_INFO("Clear pending connections...");
|
|
queue_.Clear();
|
|
|
|
// Enqueue a null connection to trigger the first worker to stop.
|
|
queue_.Push(ConnectionPtr());
|
|
|
|
for (auto& worker : workers_) {
|
|
if (worker.joinable()) {
|
|
worker.join();
|
|
}
|
|
}
|
|
|
|
LOG_INFO("All workers have been stopped.");
|
|
}
|
|
|
|
void RequestHandler::WorkerRoutine() {
|
|
LOG_INFO("Worker is running.");
|
|
|
|
for (;;) {
|
|
auto connection = queue_.PopOrWait();
|
|
|
|
if (!connection) {
|
|
LOG_INFO("Worker is going to stop.");
|
|
|
|
// For stopping next worker.
|
|
queue_.Push(ConnectionPtr());
|
|
|
|
// Stop the worker.
|
|
break;
|
|
}
|
|
|
|
HandleConnection(connection);
|
|
}
|
|
}
|
|
|
|
void RequestHandler::HandleConnection(ConnectionPtr connection) {
|
|
auto request = connection->request();
|
|
|
|
const Url& url = request->url();
|
|
UrlArgs args;
|
|
|
|
LOG_INFO("Request URL path: %s", url.path().c_str());
|
|
|
|
// Get service by URL path.
|
|
auto service = service_manager_.Get(url.path(), &args);
|
|
|
|
if (!service) {
|
|
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
|
|
|
if (!ServeStatic(connection)) {
|
|
connection->SendResponse(Status::kNotFound);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
ResponsePtr response = service->Handle(request, args);
|
|
|
|
// Send response back to client.
|
|
if (response) {
|
|
connection->SendResponse(response);
|
|
} else {
|
|
connection->SendResponse(Status::kNotImplemented);
|
|
}
|
|
}
|
|
|
|
bool RequestHandler::ServeStatic(ConnectionPtr connection) {
|
|
auto request = connection->request();
|
|
std::string path = request->url().path();
|
|
|
|
// If path ends in slash (i.e. is a directory) then add "index.html".
|
|
if (path[path.size() - 1] == '/') {
|
|
path += "index.html";
|
|
}
|
|
|
|
// Determine the file extension.
|
|
std::string extension;
|
|
std::size_t last_slash_pos = path.find_last_of("/");
|
|
std::size_t last_dot_pos = path.find_last_of(".");
|
|
if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos) {
|
|
extension = path.substr(last_dot_pos + 1);
|
|
}
|
|
|
|
// Open the file to send back.
|
|
std::string full_path = doc_root_ + path;
|
|
std::ifstream ifs(full_path.c_str(), std::ios::in | std::ios::binary);
|
|
if (!ifs) {
|
|
// The file doesn't exist.
|
|
connection->SendResponse(Status::kNotFound);
|
|
return false;
|
|
}
|
|
|
|
// Fill out the content to be sent to the client.
|
|
std::string content;
|
|
char buf[512];
|
|
while (ifs.read(buf, sizeof(buf)).gcount() > 0) {
|
|
content.append(buf, ifs.gcount());
|
|
}
|
|
|
|
auto response = std::make_shared<Response>(Status::kOK);
|
|
|
|
if (!content.empty()) {
|
|
response->SetContentType(media_types::FromExtension(extension), "");
|
|
response->SetContent(std::move(content), true);
|
|
}
|
|
|
|
// Send response back to client.
|
|
connection->SendResponse(response);
|
|
|
|
return true;
|
|
}
|
|
|
|
void RequestHandler::SetContent(RequestPtr request, ResponsePtr response,
|
|
std::string&& content) {
|
|
#if WEBCC_ENABLE_GZIP
|
|
// Only support gzip (no deflate) for response compression.
|
|
if (content.size() > kGzipThreshold && request->AcceptEncodingGzip()) {
|
|
std::string compressed;
|
|
if (gzip::Compress(content, &compressed)) {
|
|
response->SetHeader(headers::kContentEncoding, "gzip");
|
|
response->SetContent(std::move(compressed), true);
|
|
return;
|
|
}
|
|
}
|
|
#endif // WEBCC_ENABLE_GZIP
|
|
|
|
response->SetContent(std::move(content), true);
|
|
}
|
|
|
|
} // namespace webcc
|