eq2go/old/WorldServer/Web/HTTPSClient.h

211 lines
6.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2005 - 2026 EQ2EMulator Development Team (http://www.eq2emu.com formerly http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef HTTPSCLIENT_H
#define HTTPSCLIENT_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/asio/ssl.hpp>
#include <string>
#include <unordered_map>
#include <deque>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/ssl.hpp>
#include <openssl/ssl.h> // for SSL_set_tlsext_host_name
namespace ssl = boost::asio::ssl;
namespace asio = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
struct PooledStream {
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> stream;
boost::asio::ip::tcp::resolver resolver;
boost::asio::steady_timer connect_timer;
boost::asio::steady_timer handshake_timer;
PooledStream(boost::asio::io_context& ioc,
boost::asio::ssl::context& ssl_ctx)
: stream(ioc, ssl_ctx)
, resolver(ioc)
, connect_timer(ioc)
, handshake_timer(ioc)
{}
void prepare(const std::string& server,
const std::string& port,
std::function<void(boost::system::error_code)> on_ready)
{
auto endpoints = resolver.resolve(server, port);
// — Step 1: async connect + 2s timeout
connect_timer.expires_after(std::chrono::seconds(2));
connect_timer.async_wait([this, on_ready](auto ec){
if (!ec) {
stream.lowest_layer().cancel();
on_ready(boost::asio::error::timed_out);
}
});
boost::asio::async_connect(
stream.lowest_layer(), endpoints,
[this, server, on_ready](auto ec, auto){
connect_timer.cancel();
if (ec) return on_ready(ec);
// ** SNI: must set the host name for TLS before handshake **
if (!SSL_set_tlsext_host_name(stream.native_handle(), server.c_str())) {
// pull the OpenSSL error and report it
auto err = ::ERR_get_error();
return on_ready(
boost::system::error_code(
static_cast<int>(err),
boost::asio::error::get_ssl_category()
)
);
}
// — Step 2: async handshake + 2s timeout
handshake_timer.expires_after(std::chrono::seconds(2));
handshake_timer.async_wait([this](auto ec){
if (!ec) stream.lowest_layer().cancel();
});
stream.async_handshake(
boost::asio::ssl::stream_base::client,
[this, on_ready](auto ec){
handshake_timer.cancel();
on_ready(ec);
}
);
}
);
}
};
// --- ConnectionPool.h ---------------------------------------
class ConnectionPool {
public:
ConnectionPool(boost::asio::io_context& ioc,
boost::asio::ssl::context& ssl_ctx)
: ioc_(ioc), ssl_ctx_(ssl_ctx) {}
// Acquire a ready PooledStream (connected + handshaken),
// or create one and run prepare().
void acquire(const std::string& server,
const std::string& port,
std::function<void(std::shared_ptr<PooledStream>, boost::system::error_code)> cb)
{
std::string key = server + ":" + port;
{
std::lock_guard lk(mutex_);
auto &dq = free_[key];
if (!dq.empty()) {
auto ps = dq.front();
dq.pop_front();
return cb(ps, {});
}
}
// no free stream → make a new one and prepare it
auto ps = std::make_shared<PooledStream>(ioc_, ssl_ctx_);
ps->prepare(server, port, [this, server, port, ps, cb](auto ec) {
if (ec) {
cb(nullptr, ec);
} else {
cb(ps, {});
}
});
}
// Return a stream to the free list
void release(const std::string& server,
const std::string& port,
std::shared_ptr<PooledStream> ps)
{
std::string key = server + ":" + port;
// clear any leftover data in the buffer
// (you might want to reset ps->buffer here if its stored inside)
std::lock_guard lk(mutex_);
free_[key].push_back(ps);
}
private:
boost::asio::io_context& ioc_;
boost::asio::ssl::context& ssl_ctx_;
std::mutex mutex_;
std::unordered_map<std::string, std::deque<std::shared_ptr<PooledStream>>> free_;
};
class HTTPSClient {
public:
HTTPSClient(const std::string& certFile, const std::string& keyFile);
~HTTPSClient();
// — Async overloads —
void sendRequest(
const std::string& server,
const std::string& port,
const std::string& target,
std::function<void(boost::system::error_code, std::string)> done);
void sendPostRequest(
const std::string& server,
const std::string& port,
const std::string& target,
const std::string& jsonPayload,
std::function<void(boost::system::error_code, std::string)> done);
// — Legacy synchronous wrappers —
std::string sendRequest(
const std::string& server,
const std::string& port,
const std::string& target);
std::string sendPostRequest(
const std::string& server,
const std::string& port,
const std::string& target,
const std::string& jsonPayload);
std::string getServer() const { return server; }
std::string getPort() const { return port; }
private:
std::unordered_map<std::string, std::string> cookies;
std::shared_ptr<boost::asio::ssl::context> sslCtx;
boost::asio::io_context ioc_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> workGuard_;
ConnectionPool pool_;
std::thread runner_;
std::string certFile;
std::string keyFile;
std::string server;
std::string port;
void parseAndStoreCookies(const http::response<http::string_body>& res);
std::shared_ptr<boost::asio::ssl::context> createSSLContext(); // New helper function
std::string buildCookieHeader() const;
};
#endif // HTTPSCLIENT_H