init
This commit is contained in:
commit
70d467d052
97 files changed
+10386
No files matched your search
@@ -0,0 +1,74 @@
|
||||
#PRJ
|
||||
*.db
|
||||
*.crt
|
||||
*.key
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Linker files
|
||||
*.ilk
|
||||
|
||||
# Debugger Files
|
||||
*.pdb
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.so.*
|
||||
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Build directories
|
||||
build/
|
||||
Build/
|
||||
build-*/
|
||||
|
||||
# CMake generated files
|
||||
CMakeFiles/
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
#Makefile
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
*.bak
|
||||
*.swp
|
||||
|
||||
# vcpkg
|
||||
vcpkg_installed/
|
||||
|
||||
# debug information files
|
||||
*.dwo
|
||||
|
||||
# test output & cache
|
||||
Testing/
|
||||
.cache/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 dashun
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,19 @@
|
||||
VERSION := 0.0.1
|
||||
APP_NAME := gateway
|
||||
INSTL_DIR := /usr/local/bin
|
||||
CPP := g++
|
||||
INCS := -I inc/
|
||||
MY_LIBS := -L/usr/lib -lus_http -lus_crypt
|
||||
LIBS := -lcurl -lssl -lsqlite3 -lz -lcrypto -lboost_json -lboost_program_options
|
||||
CPPFLAGS := --std=c++20 -pipe # -Wall -Werror
|
||||
BUILD_DIR = build
|
||||
$(shell mkdir -p $(BUILD_DIR))
|
||||
|
||||
#--------
|
||||
all: $(BUILD_DIR)/$(APP_NAME).out
|
||||
$(BUILD_DIR)/$(APP_NAME).out: $(BUILD_DIR)/main.o
|
||||
$(CPP) $^ $(LIBS) $(MY_LIBS) -o $@
|
||||
$(BUILD_DIR)/main.o: src/main.cpp
|
||||
$(CPP) $(CPPFLAGS) -c $^ $(INCS) -o $@
|
||||
clear:
|
||||
@rm -rvf build/
|
||||
@@ -0,0 +1,7 @@
|
||||
# UniServe_gateway2
|
||||
US Framework gateway C++ version
|
||||
|
||||
# Dependancy
|
||||
|
||||
libboost
|
||||
libcurl
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365 -subj "/CN=localhost"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
exec ./build/gateway.out -i example.ini #1>/dev/null
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
make clear && make $@
|
||||
@@ -0,0 +1,9 @@
|
||||
[Server]
|
||||
address = 0.0.0.0
|
||||
port = 9201
|
||||
|
||||
[Proxy]
|
||||
default = http://127.0.0.1:9200
|
||||
|
||||
[Database]
|
||||
path = sqlite3.db
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
namespace property_tree = boost::property_tree;
|
||||
using ptree = property_tree::ptree;
|
||||
namespace qing {
|
||||
|
||||
class ConfIni {
|
||||
public:
|
||||
ConfIni(const std::string &filename) {
|
||||
try {
|
||||
std::cout << "open and parse ini file: " << filename << std::endl;
|
||||
property_tree::ini_parser::read_ini(filename, pt);
|
||||
}
|
||||
catch (const boost::property_tree::ini_parser_error& e) {
|
||||
std::cerr << "INI parsing error: " << e.what() << std::endl;
|
||||
throw IniConfigureFileParseException("INI parsing error!");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get(const std::string &path, const T &_defaut) {
|
||||
return pt.get<T>(path, _defaut);
|
||||
}
|
||||
|
||||
class IniConfigureFileParseException: public std::exception {
|
||||
public:
|
||||
explicit IniConfigureFileParseException(const std::string &msg)
|
||||
: message(msg) {}
|
||||
const char* what() const noexcept override {
|
||||
return message.c_str();
|
||||
}
|
||||
private:
|
||||
std::string message;
|
||||
};
|
||||
|
||||
private:
|
||||
ptree pt;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
#ifndef EXCEPTION_BASE_H
|
||||
#define EXCEPTION_BASE_H
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <source_location>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
/**
|
||||
* @brief 通用异常基类
|
||||
*
|
||||
* 特性:
|
||||
* 1. 完整的错误信息管理(错误码、消息、位置信息)
|
||||
* 2. 支持异常链(嵌套异常)
|
||||
* 3. 多态支持(clone、自定义消息)
|
||||
* 4. 线程安全的 what() 缓存
|
||||
* 5. 支持 C++20 source_location
|
||||
*/
|
||||
class ExceptionBase : public std::exception {
|
||||
public:
|
||||
// ============ 构造函数 ============
|
||||
|
||||
explicit ExceptionBase(
|
||||
int code = 0,
|
||||
const std::string& message = "",
|
||||
const std::source_location& location = std::source_location::current()
|
||||
) noexcept
|
||||
: m_code(code)
|
||||
, m_message(message)
|
||||
, m_file(location.file_name())
|
||||
, m_line(location.line())
|
||||
, m_function(location.function_name())
|
||||
, m_what_cache()
|
||||
{
|
||||
}
|
||||
|
||||
// 支持从字符串字面量构造
|
||||
explicit ExceptionBase(
|
||||
const std::string& message,
|
||||
const std::source_location& location = std::source_location::current()
|
||||
) noexcept
|
||||
: m_code(0)
|
||||
, m_message(message)
|
||||
, m_file(location.file_name())
|
||||
, m_line(location.line())
|
||||
, m_function(location.function_name())
|
||||
, m_what_cache()
|
||||
{
|
||||
}
|
||||
|
||||
// ============ 拷贝和移动 ============
|
||||
|
||||
ExceptionBase(const ExceptionBase& other) noexcept
|
||||
: std::exception(other)
|
||||
, m_code(other.m_code)
|
||||
, m_message(other.m_message)
|
||||
, m_file(other.m_file)
|
||||
, m_line(other.m_line)
|
||||
, m_function(other.m_function)
|
||||
, m_nested(other.m_nested ? other.m_nested->clone() : nullptr)
|
||||
, m_what_cache()
|
||||
{
|
||||
}
|
||||
|
||||
ExceptionBase(ExceptionBase&& other) noexcept
|
||||
: std::exception(std::move(other))
|
||||
, m_code(other.m_code)
|
||||
, m_message(std::move(other.m_message))
|
||||
, m_file(std::move(other.m_file))
|
||||
, m_line(other.m_line)
|
||||
, m_function(std::move(other.m_function))
|
||||
, m_nested(std::move(other.m_nested))
|
||||
, m_what_cache(std::move(other.m_what_cache))
|
||||
{
|
||||
other.m_code = 0;
|
||||
other.m_line = 0;
|
||||
}
|
||||
|
||||
virtual ~ExceptionBase() noexcept = default;
|
||||
|
||||
// ============ 赋值运算符 ============
|
||||
|
||||
ExceptionBase& operator=(const ExceptionBase& other) noexcept {
|
||||
if (this != &other) {
|
||||
m_code = other.m_code;
|
||||
m_message = other.m_message;
|
||||
m_file = other.m_file;
|
||||
m_line = other.m_line;
|
||||
m_function = other.m_function;
|
||||
m_nested = other.m_nested ? other.m_nested->clone() : nullptr;
|
||||
m_what_cache.clear();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExceptionBase& operator=(ExceptionBase&& other) noexcept {
|
||||
if (this != &other) {
|
||||
m_code = other.m_code;
|
||||
m_message = std::move(other.m_message);
|
||||
m_file = std::move(other.m_file);
|
||||
m_line = other.m_line;
|
||||
m_function = std::move(other.m_function);
|
||||
m_nested = std::move(other.m_nested);
|
||||
m_what_cache = std::move(other.m_what_cache);
|
||||
other.m_code = 0;
|
||||
other.m_line = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// ============ 访问器 ============
|
||||
|
||||
int code() const noexcept { return m_code; }
|
||||
const std::string& message() const noexcept { return m_message; }
|
||||
const std::string& file() const noexcept { return m_file; }
|
||||
int line() const noexcept { return m_line; }
|
||||
const std::string& function() const noexcept { return m_function; }
|
||||
const ExceptionBase* nested() const noexcept { return m_nested.get(); }
|
||||
|
||||
// ============ 异常链操作 ============
|
||||
|
||||
void set_nested(std::unique_ptr<ExceptionBase> nested) noexcept {
|
||||
m_nested = std::move(nested);
|
||||
m_what_cache.clear();
|
||||
}
|
||||
|
||||
template<typename ExType>
|
||||
void set_nested(const ExType& nested_ex) noexcept {
|
||||
static_assert(std::is_base_of<ExceptionBase, ExType>::value,
|
||||
"ExType must inherit from ExceptionBase");
|
||||
m_nested = std::make_unique<ExType>(nested_ex);
|
||||
m_what_cache.clear();
|
||||
}
|
||||
|
||||
// ============ std::exception 接口 ============
|
||||
|
||||
const char* what() const noexcept override {
|
||||
if (m_what_cache.empty()) {
|
||||
build_what_message();
|
||||
}
|
||||
return m_what_cache.c_str();
|
||||
}
|
||||
|
||||
// ============ 多态支持 ============
|
||||
|
||||
virtual std::unique_ptr<ExceptionBase> clone() const {
|
||||
return std::make_unique<ExceptionBase>(*this);
|
||||
}
|
||||
|
||||
// ============ 重新抛出 ============
|
||||
|
||||
[[noreturn]] void rethrow() const {
|
||||
throw *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
// ============ 可重写的消息构建方法 ============
|
||||
|
||||
virtual std::string build_message() const {
|
||||
std::string result;
|
||||
|
||||
// 错误码
|
||||
if (m_code != 0) {
|
||||
result += "[Error " + std::to_string(m_code) + "] ";
|
||||
}
|
||||
|
||||
// 错误消息
|
||||
result += m_message;
|
||||
|
||||
// 位置信息
|
||||
if (!m_file.empty()) {
|
||||
result += " (at " + m_file;
|
||||
if (m_line > 0) {
|
||||
result += ":" + std::to_string(m_line);
|
||||
}
|
||||
if (!m_function.empty()) {
|
||||
result += " in " + m_function;
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
|
||||
// 嵌套异常
|
||||
if (m_nested) {
|
||||
result += "\n Caused by: " + std::string(m_nested->what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
// ============ 内部方法 ============
|
||||
|
||||
void build_what_message() const noexcept {
|
||||
try {
|
||||
m_what_cache = build_message();
|
||||
} catch (...) {
|
||||
// 降级处理
|
||||
m_what_cache = "Exception: " + m_message;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// ============ 成员变量 ============
|
||||
|
||||
int m_code;
|
||||
std::string m_message;
|
||||
std::string m_file;
|
||||
int m_line;
|
||||
std::string m_function;
|
||||
std::unique_ptr<ExceptionBase> m_nested;
|
||||
mutable std::string m_what_cache;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 宏定义 - 方案二完整版
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 抛出指定类型的异常(自动添加 source_location)
|
||||
*
|
||||
* 适用场景:子类构造函数与 ExceptionBase 相同
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_EX(NetworkException, 404, "Server not found")
|
||||
* THROW_EX(DatabaseException, 100, "Connection timeout")
|
||||
*/
|
||||
#define THROW_EX(ExType, code, msg) \
|
||||
throw ExType(code, msg, std::source_location::current())
|
||||
|
||||
/**
|
||||
* @brief 抛出异常(仅消息,不传错误码)
|
||||
*
|
||||
* 适用场景:子类支持只传消息的构造函数
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_EX_MSG(ValidationException, "Invalid input data")
|
||||
*/
|
||||
#define THROW_EX_MSG(ExType, msg) \
|
||||
throw ExType(msg, std::source_location::current())
|
||||
|
||||
/**
|
||||
* @brief 抛出异常(支持任意构造函数参数)
|
||||
*
|
||||
* 适用场景:子类有额外的构造函数参数
|
||||
*
|
||||
* 使用示例:
|
||||
* // 子类定义
|
||||
* class FileException : public ExceptionBase {
|
||||
* public:
|
||||
* FileException(int code, const std::string& msg,
|
||||
* const std::string& filename,
|
||||
* const std::source_location& loc = std::source_location::current())
|
||||
* : ExceptionBase(code, msg, loc), m_filename(filename) {}
|
||||
* };
|
||||
*
|
||||
* // 使用
|
||||
* THROW_EX_CUSTOM(FileException, 404, "File not found", "/etc/config.txt")
|
||||
*/
|
||||
#define THROW_EX_CUSTOM(ExType, ...) \
|
||||
throw ExType(__VA_ARGS__, std::source_location::current())
|
||||
|
||||
/**
|
||||
* @brief 抛出异常并携带嵌套异常
|
||||
*
|
||||
* 适用场景:需要保留原始异常信息
|
||||
*
|
||||
* 使用示例:
|
||||
* try {
|
||||
* connect_to_server();
|
||||
* } catch (const NetworkException& e) {
|
||||
* THROW_EX_NESTED(ServiceException, 500, "Service failed", e)
|
||||
* }
|
||||
*/
|
||||
#define THROW_EX_NESTED(ExType, code, msg, nested_ex) \
|
||||
do { \
|
||||
auto ex = ExType(code, msg, std::source_location::current()); \
|
||||
ex.set_nested(nested_ex); \
|
||||
throw ex; \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 抛出异常并携带嵌套异常(支持自定义参数)
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_EX_NESTED_CUSTOM(FileException, 500, "Read failed",
|
||||
* original_ex, "/data/file.txt")
|
||||
*/
|
||||
#define THROW_EX_NESTED_CUSTOM(ExType, code, msg, nested_ex, ...) \
|
||||
do { \
|
||||
auto ex = ExType(code, msg, __VA_ARGS__, std::source_location::current()); \
|
||||
ex.set_nested(nested_ex); \
|
||||
throw ex; \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 检查条件,失败则抛出异常
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_IF(ptr == nullptr, NullPointerException, 100, "Pointer is null")
|
||||
* THROW_IF(data.empty(), ValidationException, 200, "Data cannot be empty")
|
||||
*/
|
||||
#define THROW_IF(cond, ExType, code, msg) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX(ExType, code, msg); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 检查条件,失败则抛出异常(仅消息)
|
||||
*/
|
||||
#define THROW_IF_MSG(cond, ExType, msg) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_MSG(ExType, msg); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 检查条件,失败则抛出异常(自定义参数)
|
||||
*/
|
||||
#define THROW_IF_CUSTOM(cond, ExType, ...) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_CUSTOM(ExType, __VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 检查条件,失败则抛出异常(带嵌套异常)
|
||||
*/
|
||||
#define THROW_IF_NESTED(cond, ExType, code, msg, nested_ex) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_NESTED(ExType, code, msg, nested_ex); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 检查条件,失败则抛出异常(带嵌套异常和自定义参数)
|
||||
*/
|
||||
#define THROW_IF_NESTED_CUSTOM(cond, ExType, code, msg, nested_ex, ...) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_NESTED_CUSTOM(ExType, code, msg, nested_ex, __VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
// ============================================================================
|
||||
// 辅助宏 - 用于格式化错误消息
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 格式化错误消息(使用流式语法)
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_EX_FMT(NetworkException, 404, "Failed to connect to " << host << ":" << port)
|
||||
*/
|
||||
#define THROW_EX_FMT(ExType, code, msg_expr) \
|
||||
do { \
|
||||
std::ostringstream _oss; \
|
||||
_oss << msg_expr; \
|
||||
THROW_EX(ExType, code, _oss.str()); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 格式化错误消息(使用 printf 风格)
|
||||
*
|
||||
* 使用示例:
|
||||
* THROW_EX_PRINTF(NetworkException, 404, "Failed to connect to %s:%d", host.c_str(), port)
|
||||
*/
|
||||
#define THROW_EX_PRINTF(ExType, code, fmt, ...) \
|
||||
do { \
|
||||
char _buf[1024]; \
|
||||
snprintf(_buf, sizeof(_buf), fmt, __VA_ARGS__); \
|
||||
THROW_EX(ExType, code, std::string(_buf)); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 条件检查 + 格式化消息
|
||||
*/
|
||||
#define THROW_IF_FMT(cond, ExType, code, msg_expr) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_FMT(ExType, code, msg_expr); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief 条件检查 + printf 风格格式化
|
||||
*/
|
||||
#define THROW_IF_PRINTF(cond, ExType, code, fmt, ...) \
|
||||
do { \
|
||||
if (cond) { \
|
||||
THROW_EX_PRINTF(ExType, code, fmt, __VA_ARGS__); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#endif // EXCEPTION_BASE_H
|
||||
@@ -0,0 +1,523 @@
|
||||
#include <us/HttpServer.hpp>
|
||||
#include <us/HttpClient.hpp>
|
||||
#include <us/MyRSA.hpp>
|
||||
#include <us/Base64.hpp>
|
||||
#include <us/SHA256.hpp>
|
||||
#include <us/Sqlite3.hpp>
|
||||
#include <iostream>
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <csignal>
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <openssl/provider.h>
|
||||
#include "ExpBase.hpp"
|
||||
#include "Conf.hpp"
|
||||
|
||||
using namespace pmc::net;
|
||||
using namespace qing;
|
||||
namespace json = boost::json;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
constexpr const char *_VER = "0.2.1";
|
||||
|
||||
|
||||
class DatabaseException: public ExceptionBase {
|
||||
public:
|
||||
using ExceptionBase::ExceptionBase;
|
||||
|
||||
std::string build_message() const override {
|
||||
return "[Database]" + ExceptionBase::build_message();
|
||||
}
|
||||
};
|
||||
|
||||
class HttpException: public ExceptionBase {
|
||||
public:
|
||||
HttpException(int code, const std::string& msg,
|
||||
int http_status, const std::string& url,
|
||||
const std::source_location& loc = std::source_location::current())
|
||||
: ExceptionBase(code, msg, loc)
|
||||
, m_http_status(http_status)
|
||||
, m_url(url) {}
|
||||
std::string build_message() const override {
|
||||
return "[Http]" + ExceptionBase::build_message();
|
||||
}
|
||||
int status() const {
|
||||
return m_http_status;
|
||||
}
|
||||
const std::string& url() const {
|
||||
return m_url;
|
||||
}
|
||||
private:
|
||||
int m_http_status;
|
||||
std::string m_url;
|
||||
};
|
||||
|
||||
void db_init();
|
||||
void registerRoutes(HttpServer &server);
|
||||
|
||||
http::response<http::string_body> wrapperHttp(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params,
|
||||
HttpRequestHandler _main);
|
||||
|
||||
http::response<http::string_body> handleProxy(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params,
|
||||
const char *method
|
||||
);
|
||||
|
||||
http::response<http::string_body> handleLogin(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params);
|
||||
|
||||
http::response<http::string_body> handleRegister(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params);
|
||||
|
||||
|
||||
/* headers can't processed */
|
||||
bool is_special_header(const std::string &header);
|
||||
|
||||
/* configure items */
|
||||
static std::string _ADDR;
|
||||
static int _PORT;
|
||||
static std::string _DEFT_URL;
|
||||
static std::string _DB_PATH;
|
||||
static std::string _DEFT_ADMIN_USER = "admin";
|
||||
static std::string _DEFT_ADMIN_PASS = "44112233";
|
||||
|
||||
/* instance items */
|
||||
static std::unique_ptr<HttpServer> _SERVE;
|
||||
static std::unique_ptr<MyRSA::Generator> _KEYGEN;
|
||||
static std::unique_ptr<Sqlite3> _SQLDB;
|
||||
|
||||
void signalHandler(int signum) {
|
||||
|
||||
std::cout << "Receive close signal, prepare to shutdown...\n";
|
||||
if (_SERVE) {
|
||||
_SERVE->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void set_signal_action() {
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = signalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sigaction(SIGINT, &sa, nullptr);
|
||||
sigaction(SIGTERM, &sa, nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
po::options_description desc("cpp language web gateway");
|
||||
desc.add_options()
|
||||
("help,h", "output help message")
|
||||
("version,v", "output version message")
|
||||
("input,i", po::value<std::string>(), "input a configure text file")
|
||||
;
|
||||
po::variables_map vm;
|
||||
/* parse commandline params */
|
||||
{
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
}
|
||||
|
||||
catch (const std::exception &e) {
|
||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||
std::cerr << "using --help to check options message" << std::endl;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cout << desc << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!vm.count("input")) {
|
||||
std::cerr << "Please input configure file via -i" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse configure file */
|
||||
{
|
||||
try {
|
||||
|
||||
ConfIni cf(vm["input"].as<std::string>());
|
||||
_ADDR = cf.get<std::string>("Server.address", "");
|
||||
_PORT = cf.get<int> ("Server.port", -1);
|
||||
_DEFT_URL = cf.get<std::string>("Proxy.default", "");
|
||||
_DB_PATH = cf.get<std::string>("Database.path", "");
|
||||
}
|
||||
catch (ConfIni::IniConfigureFileParseException &e) {
|
||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||
std::cerr << "ini format input file error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_ADDR == "" || _PORT == -1) {
|
||||
std::cerr << "Please setup addree and port in Serve section in configure file";
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_SERVE = std::make_unique<HttpServer> (_ADDR, _PORT, "full_chain.crt", "server.key", 4);
|
||||
_KEYGEN = std::make_unique<MyRSA::Generator> ();
|
||||
_SQLDB = std::make_unique<Sqlite3> (_DB_PATH);
|
||||
|
||||
db_init();
|
||||
registerRoutes(*_SERVE);
|
||||
set_signal_action();
|
||||
|
||||
_SERVE->start();
|
||||
_SERVE->run();
|
||||
|
||||
}
|
||||
|
||||
void db_init() {
|
||||
|
||||
std::string sql = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Users'";
|
||||
|
||||
auto flag = false;
|
||||
{
|
||||
auto stmt = _SQLDB->stmt(sql);
|
||||
if (stmt.step()) {
|
||||
auto num = stmt.read_int(0);
|
||||
if (num == 0) {
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
std::cout << "Start database init..." << std::endl;
|
||||
std::string sql_crtt = "-- create users table, username as email\n"
|
||||
"CREATE TABLE Users (\n"
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
|
||||
"email TEXT UNIQUE NOT NULL,\n"
|
||||
"password_hash TEXT NOT NULL,\n"
|
||||
"create_at DATETIME DEFAULT CURRENT_TIMESTAMP,\n"
|
||||
"last_login DATETIME,\n"
|
||||
"is_active BOOLEAN DEFAULT 1\n"
|
||||
");\n\n"
|
||||
|
||||
"CREATE INDEX idx_users_email ON users(email);\n";
|
||||
|
||||
_SQLDB->exec(sql_crtt);
|
||||
|
||||
{
|
||||
auto password_hash = SHA256::sha256(_DEFT_ADMIN_PASS);
|
||||
std::cout << password_hash << std::endl;
|
||||
std::string sql_crta = "INSERT INTO Users(email, password_hash, last_login) VALUES (?, ?, datetime('now', 'localtime'))";
|
||||
auto stmt = _SQLDB->stmt(sql_crta);
|
||||
stmt.bind_text(1, _DEFT_ADMIN_USER);
|
||||
stmt.bind_text(2, password_hash);
|
||||
if (stmt.step()) {
|
||||
std::cout << stmt.read_text(1) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "done" << std::endl;
|
||||
}
|
||||
|
||||
else {
|
||||
std::cout << "The database already init" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void registerRoutes(HttpServer &server) {
|
||||
|
||||
server.post("/login", [](
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params
|
||||
) -> http::response<http::string_body> {
|
||||
return wrapperHttp(req, params, [](
|
||||
const auto& req, const auto& params
|
||||
) {
|
||||
return handleLogin(req, params);
|
||||
});
|
||||
});
|
||||
|
||||
server.post("/register", [](const auto& req, const auto& params) {
|
||||
return handleRegister(req, params);
|
||||
});
|
||||
|
||||
server.get("*", [](const auto& req, const auto& params) {
|
||||
return handleProxy(req, params, "get");
|
||||
});
|
||||
|
||||
server.post("*", [](const auto& req, const auto& params) {
|
||||
return handleProxy(req, params, "post");
|
||||
});
|
||||
}
|
||||
|
||||
http::response<http::string_body> wrapperHttp(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params,
|
||||
HttpRequestHandler _main)
|
||||
|
||||
try {
|
||||
|
||||
return _main(req, params);
|
||||
}
|
||||
|
||||
catch (const std::exception& e) {
|
||||
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
const auto& ti = typeid(e);
|
||||
json::value jv_r = {
|
||||
{"success", false},
|
||||
{"message", std::string("<") + ti.name() + "> " + e.what()}
|
||||
};
|
||||
res.set(http::field::content_type, "text/plain");
|
||||
res.result(http::status::internal_server_error);
|
||||
res.body() = json::serialize(jv_r);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* curl -X POST http://127.0.0.1:9201/login -d '{"username": "qing", "password": "12345678"}' */
|
||||
http::response<http::string_body> handleLogin(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params)
|
||||
{
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
|
||||
try {
|
||||
//std::cout << req.body() << std::endl;
|
||||
json::value jv;
|
||||
try {
|
||||
jv = json::parse(req.body());
|
||||
}
|
||||
catch (const boost::wrapexcept<boost::system::system_error>& e) {
|
||||
THROW_EX_CUSTOM(HttpException, 2001, "Invalid_json_Argument",
|
||||
400, "/login");
|
||||
}
|
||||
|
||||
|
||||
if (!jv.is_object()) {
|
||||
THROW_EX_CUSTOM(HttpException, 2002, "Invalid_json_object_Argument",
|
||||
400, "/login");
|
||||
}
|
||||
auto& obj = jv.as_object();
|
||||
auto username = std::string(obj.at("username").as_string());
|
||||
auto password = std::string(obj.at("password").as_string());
|
||||
auto password_hash = SHA256::sha256(password);
|
||||
std::cout << username << " " << password_hash << std::endl;
|
||||
|
||||
std::string sql = "SELECT COUNT(*) FROM Users WHERE email=? AND password_hash=?;";
|
||||
auto stmt = _SQLDB->stmt(sql);
|
||||
stmt.bind_text(1, username);
|
||||
stmt.bind_text(2, password_hash);
|
||||
if (stmt.step() && stmt.read_int(0) == 1) {
|
||||
//update
|
||||
}
|
||||
|
||||
else {
|
||||
THROW_EX_CUSTOM(HttpException, 2101, "Invalid Username or Password",
|
||||
401, "/login");
|
||||
}
|
||||
|
||||
auto _PRIV = MyRSA::Private_Key::from_pem(_KEYGEN->get_private_key_pem());
|
||||
|
||||
|
||||
//int created_at = 0;
|
||||
int keep_time = 3600; // second
|
||||
|
||||
json::value jv_r = {
|
||||
{"user", username},
|
||||
//{"created_at", created_at},
|
||||
{"keep_time", keep_time}
|
||||
};
|
||||
|
||||
std::string data = json::serialize(jv_r);
|
||||
std::string token = Base64::base64_encode(data);
|
||||
std::string sign = _PRIV.Sign(data);
|
||||
|
||||
res.set("Connection", "close");
|
||||
res.result(http::status::ok);
|
||||
res.body() = token + "_" + sign;
|
||||
}
|
||||
|
||||
catch (const HttpException& e) {
|
||||
|
||||
json::value jv_r = {
|
||||
{"success", false},
|
||||
{"message", e.what()}
|
||||
};
|
||||
res.set(http::field::content_type, "application/json");
|
||||
res.result(e.status());
|
||||
res.body() = json::serialize(jv_r);
|
||||
}
|
||||
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* curl -X POST http://127.0.0.1:9201/register -d '{"username": "qing", "password": "12345678"}' */
|
||||
/* FIXME: strong password check */
|
||||
http::response<http::string_body> handleRegister(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params)
|
||||
{
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
|
||||
try {
|
||||
|
||||
auto jv = json::parse(req.body());
|
||||
if (!jv.is_object()) {
|
||||
throw std::runtime_error("400");
|
||||
}
|
||||
auto& obj = jv.as_object();
|
||||
auto username = std::string(obj.at("username").as_string());
|
||||
auto password = std::string(obj.at("password").as_string());
|
||||
|
||||
auto password_hash = SHA256::sha256(password);
|
||||
|
||||
std::string sql = "INSERT INTO Users (email, password_hash, last_login) VALUES (?, ?, datetime('now', 'localtime'));";
|
||||
auto stmt = _SQLDB->stmt(sql);
|
||||
stmt.bind_text(1, username);
|
||||
stmt.bind_text(2, password_hash);
|
||||
stmt.step();
|
||||
|
||||
json::value jv_r = {{"success", true}};
|
||||
res.result(http::status::ok);
|
||||
res.body() = json::serialize(jv_r);
|
||||
}
|
||||
//catch
|
||||
|
||||
catch (const std::exception& e) {
|
||||
const auto& ti = typeid(e);
|
||||
res.set(http::field::content_type, "text/plain");
|
||||
res.result(http::status::internal_server_error);
|
||||
res.body() = std::string("<") + ti.name() + "> " + e.what();
|
||||
}
|
||||
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* curl -H "Target-Url: https://www.baidu.com" http://127.0.0.1:9201 -X GET */
|
||||
http::response<http::string_body> handleProxy(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params,
|
||||
const char *method)
|
||||
{
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
|
||||
try {
|
||||
auto _PUBL = MyRSA::Public_Key::from_pem(_KEYGEN->get_public_key_pem());
|
||||
|
||||
std::string url = "";
|
||||
auto flag = false;
|
||||
{
|
||||
auto it = req.base().find("Target-Url");
|
||||
if (it != req.base().end()) {
|
||||
url = req["Target-Url"];
|
||||
}
|
||||
|
||||
it = req.base().find("target-url");
|
||||
if (it != req.base().end()) {
|
||||
url = req["target-url"];
|
||||
}
|
||||
|
||||
if (url == "") {
|
||||
url = _DEFT_URL;
|
||||
flag = true;
|
||||
}
|
||||
|
||||
url += "/";
|
||||
url += req.target();
|
||||
}
|
||||
|
||||
/* check auth */
|
||||
std::string auth = "";
|
||||
{
|
||||
auto it = req.base().find("Authorization");
|
||||
if (it == req.base().end() && !flag) {
|
||||
throw std::runtime_error("401");
|
||||
}
|
||||
std::string raw = req["Authorization"];
|
||||
if (raw.substr(0, 7) == "Bearer ") {
|
||||
raw = raw.substr(7, raw.size() - 7);
|
||||
size_t idx = raw.find("_");
|
||||
if (idx == std::string::npos) {
|
||||
throw std::runtime_error("Header 400 Bad Request");
|
||||
}
|
||||
std::string data = raw.substr(0, idx);
|
||||
std::string sign = raw.substr(idx+1, raw.size()-idx-1);
|
||||
auth = Base64::base64_decode_to_string(data);
|
||||
if (!_PUBL.Verify(auth, sign)) {
|
||||
throw std::runtime_error("Header 401 Unauthorized");
|
||||
}
|
||||
std::cout << "auth data: " << auth << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto callback = [&res](const auto& ret) {
|
||||
res.body() += ret; // TODO: too big file
|
||||
};
|
||||
|
||||
|
||||
auto client = HttpClient();
|
||||
for (auto const& field: req) {
|
||||
if (is_special_header(field.name_string()))
|
||||
continue;
|
||||
auto h = std::string();
|
||||
h += field.name_string();
|
||||
h += ": ";
|
||||
h += field.value();
|
||||
client.set_header(h);
|
||||
}
|
||||
|
||||
|
||||
if (method == "get")
|
||||
client.get_stream_sync(url, callback);
|
||||
|
||||
else if (method == "post") {
|
||||
std::string payload = req.body();
|
||||
client.post_stream_sync(url, payload, callback);
|
||||
}
|
||||
|
||||
|
||||
for (const auto& pair: client.get_response_headers() ) {
|
||||
res.set(pair.first, pair.second);
|
||||
}
|
||||
res.set("Connection", "close");
|
||||
res.result(client.get_response_code());
|
||||
}
|
||||
//catch
|
||||
|
||||
catch (const std::exception& e) {
|
||||
const auto& ti = typeid(e);
|
||||
res.set(http::field::content_type, "text/plain");
|
||||
res.result(http::status::internal_server_error);
|
||||
json::value jv_r = {
|
||||
{"success", false},
|
||||
{"err_name", ti.name()},
|
||||
{"err_message", e.what()}
|
||||
};
|
||||
res.body() = json::serialize(jv_r);
|
||||
}
|
||||
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* they are special because they point to raw request */
|
||||
bool is_special_header(const std::string &header) {
|
||||
|
||||
return header == "Host" || header == "connection"
|
||||
|| header == "Accept-Encoding";
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user