15#include <websocketpp/config/asio_no_tls.hpp>
16#include <websocketpp/server.hpp>
20struct WebSocketServerImpl;
24 static const size_t connection_read_buffer_size = 16384;
26using WppServer = websocketpp::server<CustomServerConfig>;
27using WppConnectionP = WppServer::connection_ptr;
28using WppConnection = WppConnectionP::element_type;
29using WppHdl = websocketpp::connection_hdl;
32operator!= (
const WppHdl &a,
const WppHdl &b)
34 return a.owner_before (b) || b.owner_before (a);
36static inline bool operator== (
const WppHdl &a,
const WppHdl &b) {
return !(a != b); }
44 String server_url_, dir_, token_, see_other_;
51 void setup (
const String &host,
int port);
57 int listen_port ()
const override {
return listen_port_; }
58 String url ()
const override {
return server_url_; }
59 WppConnectionP wppconp (WppHdl hdl) {
return wppserver_.get_con_from_hdl (hdl); }
61 http_dir (
const String &path)
override
71 aseignore.
open (Path::join (dir_,
".aseignore"));
77 http_alias (
const String &webdir,
const String &path)
override
83 std::stable_sort (aliases_.begin(), aliases_.end(), [] (
const auto &a,
const auto &b) {
84 return a.first.size() > b.first.size();
88 map_url (
const String &urlpath)
override
100 for (
const auto &ignorepat : ignores_)
105 for (
const auto &pair : aliases_)
106 if (absurl == pair.first)
108 else if (absurl.
size() > pair.first.size() &&
109 absurl[pair.first.size()] ==
'/' &&
110 strncmp (absurl.
c_str(), pair.first.c_str(), pair.first.size()) == 0)
111 return Path::join (pair.second, absurl.
c_str() + pair.first.size());
114 return Path::join (dir_, absurl);
121 initialized_thread_ =
new std::thread ([
this, ulcb] () {
122 this_thread_set_name (
"AsioWebSocket");
129 see_other (
const String &uri)
override
137 if (initialized_thread_)
140 initialized_thread_->
join();
141 initialized_thread_ =
nullptr;
147 void reset ()
override;
149 token_ (token), make_con_ (make_con), logflags_ (logflags)
154WebSocketServerImpl::setup (
const String &host,
int port)
157 wppserver_.set_user_agent (user_agent());
158 wppserver_.set_validate_handler ([
this] (WppHdl hdl) {
160 WppConnectionP cp = this->wppconp (hdl);
162 const bool is_authenticated = conp->authenticated (token_);
163 if (is_authenticated) {
164 const int index = conp->validate();
168 if (
size_t (index) < subprotocols.size())
170 cp->select_subprotocol (subprotocols[index]);
173 else if (subprotocols.size() == 0 && index == 0)
177 cp->set_status (websocketpp::http::status_code::forbidden);
178 conp->log_status (cp->get_response_code());
181 wppserver_.set_http_handler ([
this] (WppHdl hdl) {
182 WebSocketConnectionP conp = make_connection (hdl);
184 conp->http_request();
186 wppserver_.init_asio();
187 wppserver_.clear_access_channels (websocketpp::log::alevel::all);
188 wppserver_.clear_error_channels (websocketpp::log::elevel::all);
189 wppserver_.set_reuse_addr (
true);
192 namespace IP = boost::asio::ip;
193 IP::tcp::endpoint endpoint_local = IP::tcp::endpoint (IP::address::from_string (host), port);
194 websocketpp::lib::error_code ec;
195 wppserver_.listen (endpoint_local, ec);
197 fatal_error (
"failed to listen on socket: %s:%d: %s", host, port, ec.message());
201 websocketpp::lib::asio::error_code ac;
202 listen_port_ = wppserver_.get_local_endpoint (ac).port();
205 server_url_ = fullurl;
209WebSocketServerImpl::run ()
211 wppserver_.start_accept();
218 WppServer &wppserver;
222 WppConnectionP wppconp() {
return server->wppconp (hdl); }
227WebSocketConnection::log_status (
int intstatus,
const String &filepath)
229 const auto status = websocketpp::http::status_code::value (intstatus);
230 WppConnectionP cp = internals_.wppconp();
231 if (!cp || !(logflags_ & 16))
233 using namespace AnsiColors;
234 const bool rejected = (status >= 400 && status <= 499) || status == 303;
235 const String C1 = rejected ? color (FG_RED) :
"";
236 const String C0 = rejected ? color (RESET) :
"";
237 String resource = cp->get_resource();
238 resource =
Re::sub (
"\\btoken=[^;?]+",
"token=<………>", resource);
241 cp->get_request().get_method(), resource, C0,
242 filepath.
empty() ?
"" :
" [" + filepath +
"]"));
245WebSocketConnection::WebSocketConnection (Internals &internals,
int logflags) :
246 internals_ (*new (internals_mem_) Internals (internals)), logflags_ (logflags)
248 static_assert (
sizeof (Internals) <=
sizeof (internals_mem_));
252WebSocketConnection::~WebSocketConnection()
254 internals_.~Internals();
258WebSocketConnection::is_open ()
const
260 return internals_.opened;
267 WppConnectionP cp = internals_.wppconp();
269 websocketpp::lib::error_code ec;
270 internals_.wppserver.send (internals_.hdl,
message, websocketpp::frame::opcode::text, ec);
274 log (
string_format (
"Error: %s: %s", __func__, ec.message()));
275 websocketpp::lib::error_code ec2;
276 cp->close (websocketpp::close::status::going_away,
"", ec2);
285 WppConnectionP cp = internals_.wppconp();
287 websocketpp::lib::error_code ec;
289 internals_.wppserver.send (internals_.hdl, blob, websocketpp::frame::opcode::binary, ec);
293 log (
string_format (
"Error: %s: %s", __func__, ec.message()));
294 websocketpp::lib::error_code ec2;
295 cp->close (websocketpp::close::status::going_away,
"", ec2);
301 for (
size_t i = 0; i < blob.
size(); i++)
303 if (i && 0 == i % 16)
315WebSocketConnection::get_info ()
317 WppConnectionP cp = internals_.wppconp();
318 const websocketpp::http::parser::request &rq = cp->get_request();
321 info.header = [headers] (
const String &header) {
322 const auto it = headers->find (header);
323 return it == headers->end() ?
"" : it->second;
326 for (
auto it : *headers)
327 printerr (
"%s: %s\n", it.first, it.second);
329 const auto &
socket = cp->get_raw_socket();
330 boost::system::error_code ec;
331 const auto &laddress =
socket.local_endpoint (ec).address();
332 info.local = laddress.to_string();
333 info.lport =
socket.local_endpoint (ec).port();
334 const auto &raddress =
socket.remote_endpoint (ec).address();
335 info.remote = raddress.to_string();
336 info.rport =
socket.remote_endpoint (ec).port();
337 info.subs = cp->get_requested_subprotocols();
342WebSocketConnection::nickname ()
344 if (internals_.nickname_.
empty())
346 Info info = get_info();
347 const String ua = info.header (
"User-Agent");
351 info.header (
"Accept-Encoding") +
"\n" +
352 info.header (
"Accept-Language") +
"\n" +
353 info.header (
"sec-ch-ua") +
"\n" +
354 info.header (
"sec-ch-ua-mobile") +
"\n" +
355 info.header (
"sec-gpc") +
"\n";
360 else if (
Re::search (R
"(\bElectron/)", ua) >= 0)
362 else if (
Re::search (R
"(\bChrome-Lighthouse\b)", ua) >= 0)
364 else if (
Re::search (R
"(\bChrome/)", ua) >= 0)
366 else if (
Re::search (R
"(\bSafari/)", ua) >= 0)
374 else if (
Re::search (R
"(\bPython-urllib/)", ua) >= 0)
378 internals_.nickname_ =
string_format (
"%s-%08x:%x", hh, uint32_t (hash ^ (hash >> 32)), info.rport);
380 return internals_.nickname_;
393 WppConnectionP cp = internals_.wppconp();
398 const String cookie_header = cp->get_request().get_header (
"Cookie");
399 const String cookie_token =
Re::grep (
"\\bsession_auth=([^;?]+)", cookie_header, 1);
400 return cookie_token == token;
413 if (internals_.server->dir_.empty())
415 WppConnectionP cp = internals_.wppconp();
416 const auto &parts =
string_split (cp->get_resource(),
"?");
417 const String query = cp->get_uri()->get_query();
421 auto set_response = [] (WppConnectionP cp, websocketpp::http::status_code::value status,
const String &title,
const String &msg) {
424 "<html><head><title>%u %s</title></head>\n<body>\n"
427 "<hr><address>%s</address>\n"
428 "<hr></body></html>\n", status, title, title,
429 msg.size() ?
"<p>" + msg +
"</p>" :
"",
430 WebSocketServer::user_agent());
432 cp->replace_header (
"Content-Type",
"text/html; charset=utf-8");
433 cp->set_status (status);
437 if (cp->get_request().get_method() ==
"GET" && parts[0] ==
"/~auth" &&
439 Re::search (
"\\btoken=" + internals_.server->token_ +
"\\b", query) >= 0)) {
441 cp->replace_header (
"Set-Cookie",
"session_auth=" + internals_.server->token_ +
"; Path=/; HttpOnly");
442 cp->replace_header (
"Location", target);
443 set_response (cp, websocketpp::http::status_code::found,
444 "Found",
"Redirecting to: <tt>" + target +
"</tt>");
445 log_status (cp->get_response_code());
451 const std::string see_other = internals_.server->see_other_;
452 if (see_other.
empty())
453 set_response (cp, websocketpp::http::status_code::method_not_allowed,
454 "Method Not Allowed",
"Authentication required.");
457 cp->replace_header (
"Location", see_other);
458 set_response (cp, websocketpp::http::status_code::see_other,
459 "See Other",
"<a href=\"" + see_other +
"\">" + see_other +
"</a>");
461 log_status (cp->get_response_code());
466 filepath = internals_.server->map_url (parts[0]);
470 filepath = Path::join (filepath,
"index.html");
473 if (cp->get_request().get_method() ==
"GET") {
475 const String accept_encoding = cp->get_request().get_header (
"Accept-Encoding");
477 bool fp =
false, fz =
false;
481 const String mimetype = WebSocketServer::mime_type (ext ? ext + 1 :
"",
true);
482 cp->append_header (
"Content-Type", mimetype);
485 const CacheType caching = mimetype ==
"text/css" ? CACHE_FOREVER : CACHE_AUTO;
488 cp->append_header (
"Cache-Control",
"no-store");
491 cp->append_header (
"Cache-Control",
"no-cache");
494 cp->append_header (
"Cache-Control",
"max-age=7, stale-while-revalidate=31536000");
497 cp->append_header (
"Cache-Control",
"max-age=31536000, public, immutable");
501 filepath = filepath +
".gz";
502 cp->append_header (
"Content-Encoding",
"gzip");
505 cp->set_body (blob.
string());
506 cp->set_status (websocketpp::http::status_code::ok);
509 set_response (cp, websocketpp::http::status_code::not_found,
511 "The requested URL was not found: <br /> <tt>" + cp->get_uri()->str() +
"</tt>");
514 set_response (cp, websocketpp::http::status_code::method_not_allowed,
515 "Method Not Allowed",
"");
517 log_status (cp->get_response_code());
522WebSocketServerImpl::make_connection (WppHdl hdl)
524 WppConnectionP cp = wppconp (hdl);
530 WebSocketConnectionP conp = make_con_ (internals, logflags_);
532 cp->set_http_handler ([conp] (WppHdl hdl) {
533 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
535 conp->http_request();
538 cp->set_fail_handler ([conp] (WppHdl hdl) {
539 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
543 cp->set_open_handler ([conp] (WppHdl hdl) {
544 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
546 internals_.server->ws_opened (conp);
548 cp->set_message_handler ([conp] (WppHdl hdl, WppServer::message_ptr msg) {
549 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
551 conp->message (msg->get_payload());
553 cp->set_close_handler ([conp] (WppHdl hdl) {
554 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
556 internals_.server->ws_closed (conp);
562WebSocketServerImpl::ws_opened (WebSocketConnectionP conp)
564 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
565 internals_.opened =
true;
566 opencons_.push_back (conp);
571WebSocketServerImpl::ws_closed (WebSocketConnectionP conp)
573 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
574 if (internals_.opened)
576 internals_.opened =
false;
577 auto it =
std::find (opencons_.begin(), opencons_.end(), conp);
578 if (it != opencons_.end())
579 opencons_.erase (it);
585WebSocketServerImpl::reset()
588 for (ssize_t i = ssize_t (opencons_.size()) - 1; i >= 0; i--)
590 WebSocketConnectionP conp = opencons_[i];
591 WebSocketConnection::Internals &internals_ = WebSocketServer::internals (*conp);
592 WppConnectionP cp = internals_.wppconp();
593 websocketpp::lib::error_code ec;
594 cp->close (websocketpp::close::status::going_away,
"", ec);
600WebSocketServer::~WebSocketServer()
604WebSocketServer::create (
const MakeConnection &make,
int logflags,
const std::string &session_token)
610WebSocketServer::user_agent ()
619 return !!websocketpp::utf8_validator::validate (utf8string);
625WebSocketServer::mime_type (
const String &ext,
bool utf8)
628 static MimeMap mime_map = [] () {
634 for (
size_t i = 1; i < w.
size(); i++)
637 if (mime_map.end() != mime_map.find (w[i]))
638 warning (
"mime-types.hh: duplicate extension: %s", w[i]);
639 mime_map[w[i]] = w[0];
644 auto it = mime_map.find (ext);
645 String mimetype = it != mime_map.
end() ? it->second :
"application/octet-stream";
648 if (mimetype ==
"text/html" || mimetype ==
"text/markdown" || mimetype ==
"text/plain")
649 mimetype +=
"; charset=utf-8";
Binary large object storage container.
static Blob from_file(const String &filename)
Create Blob by loading from filename.
String string()
Copy Blob data into a zero terminated string.
static String sub(const String ®ex, const String &subst, const String &input, Flags=DEFAULT)
Substitute regex in input by sbref with backreferences $00…$99 or $&.
static String grep(const String ®ex, const String &input, int group=0, Flags=DEFAULT)
Find regex in input and return matching string.
static ssize_t search(const String ®ex, const String &input, Flags=DEFAULT)
Find regex in input and return match position >= 0 or return < 0 otherwise.
virtual void closed()
Pairs with opened().
virtual int validate()
Return true to allow opened().
virtual void http_request()
Independent of socket ops.
virtual void opened()
Pairs with closed().
bool send_binary(const String &blob)
Returns true if binary blob was sent.
virtual void message(const String &message)
Only if opened.
virtual void failed()
Never folloed by opened().
bool send_text(const String &message)
Returns true if text message was sent.
virtual bool authenticated(const std::string &token)
Return true to allow validate().
static bool utf8_validate(const std::string &utf8string)
Validate UTF-8 string with websocketpp::utf8_validator.
#define ASE_UNLIKELY(expr)
Compiler hint to optimize for expr evaluating to false.
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
bool check(const String &file, const String &mode)
String simplify_abspath(const std::string &abspath_expression)
Remove extra slashes, './' and '../' from abspath_expression.
String abspath(const String &path, const String &incwd)
String normalize(const String &path)
Convert path to normal form.
The Anklang C++ API namespace.
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
StringS string_split(const String &string, const String &splitter, size_t maxn)
String string_from_int(int64 value)
Convert a 64bit signed integer into a string.
const char * string_find_word(const char *haystack, const char *word)
Find occurance of word in haystack.
String string_url_decode(const String &urlstr, const bool form_url_encoded)
Decode URL %-sequences in a string, decode '+' if form_url_encoded.
const char * ase_version()
Provide a string containing the package version.
std::string String
Convenience alias for std::string.
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
T regex_search(T... args)
T shared_from_this(T... args)