TrinityCore
HttpService.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "HttpService.h"
19#include "BaseHttpSocket.h"
20#include "CryptoRandom.h"
21#include "Timezone.h"
22#include <boost/beast/version.hpp>
23#include <boost/uuid/string_generator.hpp>
24#include <fmt/chrono.h>
25
26namespace Trinity::Net::Http
27{
29{
31}
32
34{
36}
37
38RequestHandlerResult DispatcherService::HandleRequest(std::shared_ptr<AbstractSocket> session, RequestContext& context)
39{
40 TC_LOG_DEBUG(_logger, "{} Starting request {} {}", session->GetClientInfo(),
41 ToStdStringView(context.request.method_string()), ToStdStringView(context.request.target()));
42
43 std::string_view path = [&]
44 {
45 std::string_view path = ToStdStringView(context.request.target());
46 size_t queryIndex = path.find('?');
47 if (queryIndex != std::string_view::npos)
48 path = path.substr(0, queryIndex);
49 return path;
50 }();
51
52 context.handler = [&]() -> HttpMethodHandlerMap::mapped_type const*
53 {
54 switch (context.request.method())
55 {
56 case boost::beast::http::verb::get:
57 case boost::beast::http::verb::head:
58 {
59 auto itr = _getHandlers.find(path);
60 return itr != _getHandlers.end() ? &itr->second : nullptr;
61 }
63 {
64 auto itr = _postHandlers.find(path);
65 return itr != _postHandlers.end() ? &itr->second : nullptr;
66 }
67 default:
68 break;
69 }
70 return nullptr;
71 }();
72
73 SystemTimePoint responseDate = SystemTimePoint::clock::now();
74 context.response.set(boost::beast::http::field::date, StringFormat("{:%a, %d %b %Y %T GMT}", responseDate - Timezone::GetSystemZoneOffsetAt(responseDate)));
75 context.response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
76 context.response.keep_alive(context.request.keep_alive());
77
78 if (!context.handler)
79 return HandlePathNotFound(std::move(session), context);
80
81 return context.handler->Func(std::move(session), context);
82}
83
84RequestHandlerResult DispatcherService::HandleBadRequest(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
85{
86 context.response.result(boost::beast::http::status::bad_request);
88}
89
90RequestHandlerResult DispatcherService::HandleUnauthorized(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
91{
92 context.response.result(boost::beast::http::status::unauthorized);
94}
95
96RequestHandlerResult DispatcherService::HandlePathNotFound(std::shared_ptr<AbstractSocket> /*session*/, RequestContext& context)
97{
98 context.response.result(boost::beast::http::status::not_found);
100}
101
102void DispatcherService::RegisterHandler(boost::beast::http::verb method, std::string_view path,
103 std::function<RequestHandlerResult(std::shared_ptr<AbstractSocket> session, RequestContext& context)> handler,
105{
106 HttpMethodHandlerMap& handlerMap = [&]() -> HttpMethodHandlerMap&
107 {
108 switch (method)
109 {
110 case boost::beast::http::verb::get:
111 return _getHandlers;
113 return _postHandlers;
114 default:
115 {
116 std::string_view methodString = ToStdStringView(boost::beast::http::to_string(method));
117 ABORT_MSG("Tried to register a handler for unsupported HTTP method " STRING_VIEW_FMT, STRING_VIEW_FMT_ARG(methodString));
118 }
119 }
120 }();
121
122 handlerMap[std::string(path)] = { .Func = std::move(handler), .Flags = flags };
123 TC_LOG_INFO(_logger, "Registered new handler for {} {}", ToStdStringView(boost::beast::http::to_string(method)), path);
124}
125
126void SessionService::InitAndStoreSessionState(std::shared_ptr<SessionState> state, boost::asio::ip::address const& address)
127{
128 state->RemoteAddress = address;
129
130 // Generate session id
131 {
132 std::unique_lock lock{ _sessionsMutex };
133
134 while (state->Id.is_nil() || _sessions.contains(state->Id))
135 std::copy_n(Trinity::Crypto::GetRandomBytes<16>().begin(), 16, state->Id.begin());
136
137 TC_LOG_DEBUG(_logger, "Client at {} created new session {}", address.to_string(), boost::uuids::to_string(state->Id));
138 _sessions[state->Id] = std::move(state);
139 }
141
143{
144 _inactiveSessionsKillTimer = std::make_unique<Asio::DeadlineTimer>(ioContext);
145 _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
146 _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
147 {
148 if (err)
149 return;
150
152 });
153}
154
156{
158 {
159 std::unique_lock lock{ _sessionsMutex };
160 _sessions.clear();
161 }
162 {
163 std::unique_lock lock{ _inactiveSessionsMutex };
164 _inactiveSessions.clear();
165 }
166}
167
168std::shared_ptr<SessionState> SessionService::FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const& address)
169{
170 std::shared_ptr<SessionState> state;
171
172 {
173 std::shared_lock lock{ _sessionsMutex };
174 auto itr = _sessions.find(boost::uuids::string_generator()(id.begin(), id.end()));
175 if (itr == _sessions.end())
176 {
177 TC_LOG_DEBUG(_logger, "Client at {} attempted to use a session {} that was expired", address.to_string(), id);
178 return nullptr; // no session
179 }
180
181 state = itr->second;
182 }
183
184 if (state->RemoteAddress != address)
185 {
186 TC_LOG_ERROR(_logger, "Client at {} attempted to use a session {} that was last accessed from {}, denied access",
187 address.to_string(), id, state->RemoteAddress.to_string());
188 return nullptr;
189 }
190
191 {
192 std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
193 _inactiveSessions.erase(state->Id);
194 }
195
196 return state;
197}
198
199void SessionService::MarkSessionInactive(boost::uuids::uuid const& id)
200{
201 bool wasActive = true;
202 {
203 std::unique_lock inactiveSessionsLock{ _inactiveSessionsMutex };
204 wasActive = _inactiveSessions.insert(id).second;
205 }
206
207 if (wasActive)
208 {
209 std::shared_lock lock{ _sessionsMutex };
210 auto itr = _sessions.find(id);
211 if (itr != _sessions.end())
212 {
213 itr->second->InactiveTimestamp = TimePoint::clock::now() + Minutes(5);
214 TC_LOG_TRACE(_logger, "Session {} marked as inactive", boost::uuids::to_string(id));
215 }
216 }
217}
218
220{
221 std::set<boost::uuids::uuid> inactiveSessions;
222
223 {
224 std::unique_lock lock{ _inactiveSessionsMutex };
225 std::swap(_inactiveSessions, inactiveSessions);
226 }
227
228 {
229 TimePoint now = TimePoint::clock::now();
230 std::size_t inactiveSessionsCount = inactiveSessions.size();
231
232 std::unique_lock lock{ _sessionsMutex };
233 for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
234 {
235 auto sessionItr = _sessions.find(*itr);
236 if (sessionItr == _sessions.end() || sessionItr->second->InactiveTimestamp < now)
237 {
238 _sessions.erase(sessionItr);
239 itr = inactiveSessions.erase(itr);
240 }
241 else
242 ++itr;
243 }
244
245 TC_LOG_DEBUG(_logger, "Killed {} inactive sessions", inactiveSessionsCount - inactiveSessions.size());
246 }
247
248 {
249 // restore sessions not killed to inactive queue
250 std::unique_lock lock{ _inactiveSessionsMutex };
251 for (auto itr = inactiveSessions.begin(); itr != inactiveSessions.end(); )
252 {
253 auto node = inactiveSessions.extract(itr++);
254 _inactiveSessions.insert(std::move(node));
255 }
256 }
257
258 _inactiveSessionsKillTimer->expires_from_now(boost::posix_time::minutes(1));
259 _inactiveSessionsKillTimer->async_wait([this](boost::system::error_code const& err)
260 {
261 if (err)
262 return;
263
265 });
266}
267}
#define STRING_VIEW_FMT_ARG(str)
Definition: Define.h:135
#define STRING_VIEW_FMT
Definition: Define.h:134
uint16 flags
Definition: DisableMgr.cpp:49
std::chrono::system_clock::time_point SystemTimePoint
Definition: Duration.h:42
std::chrono::steady_clock::time_point TimePoint
time_point shorthand typedefs
Definition: Duration.h:41
std::chrono::minutes Minutes
Minutes shorthand typedef.
Definition: Duration.h:35
#define ABORT_MSG
Definition: Errors.h:75
#define TC_LOG_DEBUG(filterType__,...)
Definition: Log.h:156
#define TC_LOG_TRACE(filterType__,...)
Definition: Log.h:153
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
constexpr bool HasFlag(T flag) const
Definition: EnumFlag.h:106
RequestHandlerResult HandleRequest(std::shared_ptr< AbstractSocket > session, RequestContext &context)
Definition: HttpService.cpp:38
static RequestHandlerResult HandlePathNotFound(std::shared_ptr< AbstractSocket > session, RequestContext &context)
Definition: HttpService.cpp:96
static RequestHandlerResult HandleUnauthorized(std::shared_ptr< AbstractSocket > session, RequestContext &context)
Definition: HttpService.cpp:90
HttpMethodHandlerMap _getHandlers
Definition: HttpService.h:76
HttpMethodHandlerMap _postHandlers
Definition: HttpService.h:77
static RequestHandlerResult HandleBadRequest(std::shared_ptr< AbstractSocket > session, RequestContext &context)
Definition: HttpService.cpp:84
void RegisterHandler(boost::beast::http::verb method, std::string_view path, std::function< RequestHandlerResult(std::shared_ptr< AbstractSocket > session, RequestContext &context)> handler, RequestHandlerFlag flags=RequestHandlerFlag::None)
std::map< std::string, RequestHandler, std::less<> > HttpMethodHandlerMap
Definition: HttpService.h:74
std::map< boost::uuids::uuid, std::shared_ptr< SessionState > > _sessions
Definition: HttpService.h:103
std::unique_ptr< Asio::DeadlineTimer > _inactiveSessionsKillTimer
Definition: HttpService.h:107
std::set< boost::uuids::uuid > _inactiveSessions
Definition: HttpService.h:106
std::shared_ptr< SessionState > FindAndRefreshSessionState(std::string_view id, boost::asio::ip::address const &address)
void Start(Asio::IoContext &ioContext)
void InitAndStoreSessionState(std::shared_ptr< SessionState > state, boost::asio::ip::address const &address)
void MarkSessionInactive(boost::uuids::uuid const &id)
decltype(auto) post(boost::asio::io_context &ioContext, T &&t)
Definition: IoContext.h:54
std::string_view ToStdStringView(boost::beast::string_view bsw)
Definition: HttpCommon.h:43
TC_SHARED_API bool CanLogResponseContent(RequestContext const &context)
Definition: HttpService.cpp:33
TC_SHARED_API bool CanLogRequestContent(RequestContext const &context)
Definition: HttpService.cpp:28
Minutes GetSystemZoneOffsetAt(SystemTimePoint date)
Definition: Timezone.cpp:132
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
Definition: StringFormat.h:38
struct RequestHandler const * handler
Definition: HttpCommon.h:37
std::function< RequestHandlerResult(std::shared_ptr< AbstractSocket > session, RequestContext &context)> Func
Definition: HttpService.h:50
EnumFlag< RequestHandlerFlag > Flags
Definition: HttpService.h:51