170 lines
5.6 KiB
C++
170 lines
5.6 KiB
C++
#pragma once
|
|
#include <charconv>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace drogon {
|
|
enum HttpStatusCode {
|
|
k200OK = 200,
|
|
k400BadRequest = 400,
|
|
k404NotFound = 404,
|
|
k422UnprocessableEntity = 422,
|
|
k500InternalServerError = 500
|
|
};
|
|
enum ContentType {
|
|
CT_APPLICATION_JSON
|
|
};
|
|
enum HttpMethod {
|
|
Get,
|
|
Post,
|
|
Put,
|
|
Patch,
|
|
Delete
|
|
};
|
|
namespace internal {
|
|
class HttpConstraint {
|
|
public:
|
|
HttpConstraint(HttpMethod method) : method_(method), is_method_(true) {}
|
|
HttpConstraint(std::string filter) : filter_(std::move(filter)) {}
|
|
bool is_method() const noexcept {
|
|
return is_method_;
|
|
}
|
|
HttpMethod method() const noexcept {
|
|
return method_;
|
|
}
|
|
const std::string& filter() const noexcept {
|
|
return filter_;
|
|
}
|
|
private:
|
|
HttpMethod method_{Get};
|
|
bool is_method_{};
|
|
std::string filter_;
|
|
};
|
|
}
|
|
class HttpResponse {
|
|
public:
|
|
static std::shared_ptr<HttpResponse> newHttpResponse() {
|
|
return std::make_shared<HttpResponse>();
|
|
}
|
|
void setStatusCode(HttpStatusCode value) {
|
|
status = value;
|
|
}
|
|
void setContentTypeCode(ContentType value) {
|
|
content_type = value;
|
|
}
|
|
void setBody(std::string value) {
|
|
body = std::move(value);
|
|
}
|
|
HttpStatusCode status{k200OK};
|
|
ContentType content_type{CT_APPLICATION_JSON};
|
|
std::string body;
|
|
};
|
|
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
|
|
class HttpRequest {
|
|
public:
|
|
std::string_view getBody() const noexcept {
|
|
return body;
|
|
}
|
|
const std::map<std::string, std::string>& getParameters() const noexcept {
|
|
return parameters;
|
|
}
|
|
const std::string& getParameter(const std::string& name) const {
|
|
const auto iterator = parameters.find(name);
|
|
return iterator == parameters.end() ? empty_parameter_ : iterator->second;
|
|
}
|
|
std::string body;
|
|
std::string user;
|
|
std::map<std::string, std::string> parameters;
|
|
private:
|
|
inline static const std::string empty_parameter_;
|
|
};
|
|
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
|
|
class HttpAppFramework {
|
|
public:
|
|
using Callback = std::function<void(const HttpResponsePtr&)>;
|
|
using Handler = std::function<void(const HttpRequestPtr&, Callback&&)>;
|
|
using Parameter_Handler = std::function<void(const HttpRequestPtr&, Callback&&, std::uint64_t)>;
|
|
template <class Function>
|
|
void registerHandler(const std::string& path, Function&& function, const std::vector<internal::HttpConstraint>& constraints) {
|
|
HttpMethod method = Get;
|
|
std::vector<std::string> filters;
|
|
for(const auto& constraint : constraints) {
|
|
if(constraint.is_method()) {
|
|
method = constraint.method();
|
|
} else {
|
|
filters.push_back(constraint.filter());
|
|
}
|
|
}
|
|
Route route;
|
|
route.path = path;
|
|
route.method = method;
|
|
route.filters = std::move(filters);
|
|
if constexpr(std::is_invocable_v<Function, const HttpRequestPtr&, Callback&&, std::uint64_t>) {
|
|
route.parameter_handler = Parameter_Handler(std::forward<Function>(function));
|
|
} else {
|
|
route.handler = Handler(std::forward<Function>(function));
|
|
}
|
|
routes_.push_back(std::move(route));
|
|
}
|
|
void handle(const std::string& path, HttpMethod method, const HttpRequestPtr& request, Callback callback) {
|
|
for(auto& route : routes_) {
|
|
if(route.method != method) {
|
|
continue;
|
|
}
|
|
if(route.handler && route.path == path) {
|
|
route.handler(request, std::move(callback));
|
|
return;
|
|
}
|
|
std::uint64_t parameter{};
|
|
if(route.parameter_handler && match_parameter_route(route.path, path, parameter)) {
|
|
route.parameter_handler(request, std::move(callback), parameter);
|
|
return;
|
|
}
|
|
}
|
|
throw std::runtime_error("route is not registered");
|
|
}
|
|
const std::vector<std::string>& filters(const std::string& path, HttpMethod method) const {
|
|
for(const auto& route : routes_) {
|
|
if(route.path == path && route.method == method) {
|
|
return route.filters;
|
|
}
|
|
}
|
|
throw std::runtime_error("route is not registered");
|
|
}
|
|
private:
|
|
struct Route {
|
|
std::string path;
|
|
HttpMethod method{Get};
|
|
Handler handler;
|
|
Parameter_Handler parameter_handler;
|
|
std::vector<std::string> filters;
|
|
};
|
|
static bool match_parameter_route(const std::string& pattern, const std::string& path, std::uint64_t& parameter) {
|
|
const auto begin = pattern.find('{');
|
|
if(begin == std::string::npos) {
|
|
return false;
|
|
}
|
|
const auto end = pattern.find('}', begin);
|
|
if(end == std::string::npos) {
|
|
return false;
|
|
}
|
|
const std::string_view prefix(pattern.data(), begin);
|
|
const std::string_view suffix(pattern.data() + end + 1, pattern.size() - end - 1);
|
|
if(path.size() < prefix.size() + suffix.size() || !std::string_view(path).starts_with(prefix) || !std::string_view(path).ends_with(suffix)) {
|
|
return false;
|
|
}
|
|
const std::string_view value(path.data() + prefix.size(), path.size() - prefix.size() - suffix.size());
|
|
const auto [parsed_end, error] = std::from_chars(value.data(), value.data() + value.size(), parameter);
|
|
return error == std::errc{} && parsed_end == value.data() + value.size();
|
|
}
|
|
std::vector<Route> routes_;
|
|
};
|
|
}
|