33 #include "third_party/mongoose/mongoose.h"
35 #endif // !ION_PRODUCTION
46 : connection_(conn), ready_(false), binary_(false) {
49 websocket_->helper_ = NULL;
53 websocket_ = websocket;
54 websocket_->SetHelper(
this);
57 server->RegisterWebsocket(connection_,
this);
60 server->UnregisterWebsocket(connection_);
63 void ConnectionReady() {
65 websocket_->ConnectionReady();
77 int ReceiveData(uint8 bits,
char* data,
size_t data_len);
78 void SendData(Opcode opcode,
const char* data,
size_t data_len);
84 mg_connection* connection) {
85 return server->FindWebsocket(connection);
89 int BeginContinuation(
bool is_binary,
char* data,
size_t data_len);
90 void AppendContinuationData(
char data[],
size_t data_len);
92 mg_connection* connection_;
96 std::vector<char> continuation_;
103 int HttpServer::WebsocketHelper::ReceiveData(
104 uint8 bits,
char* data,
size_t data_len) {
106 Opcode opcode =
static_cast<Opcode
>(bits & 0xF);
107 bool fin = bits & 0x80;
111 if (continuation_.size() == 0) {
116 AppendContinuationData(data, data_len);
119 int result = websocket_->ReceiveData(
120 &(continuation_[0]), continuation_.size(), binary_);
121 continuation_.clear();
130 return websocket_->ReceiveData(data, data_len,
false);
132 return BeginContinuation(
false, data, data_len);
137 return websocket_->ReceiveData(data, data_len,
true);
139 return BeginContinuation(
true, data, data_len);
145 SendData(PONG, data, data_len);
152 LOG(
WARNING) <<
"Unrecognized websocket opcode: " << opcode;
157 int HttpServer::WebsocketHelper::BeginContinuation(
158 bool is_binary,
char* data,
size_t data_len) {
159 if (continuation_.size() > 0) {
161 continuation_.clear();
165 AppendContinuationData(data, data_len);
169 void HttpServer::WebsocketHelper::AppendContinuationData(
170 char data[],
size_t data_len) {
171 continuation_.insert(continuation_.end(), data, data + data_len);
174 void HttpServer::WebsocketHelper::SendData(
175 Opcode opcode,
const char* data,
size_t data_len) {
182 header[0] =
static_cast<uint8
>(0x80 | (opcode & 0xF));
186 if (data_len < 126) {
188 header[1] =
static_cast<uint8
>(data_len);
190 }
else if (data_len <= 0xFFFF) {
193 header[2] =
static_cast<uint8
>(data_len >> 8);
194 header[3] =
static_cast<uint8
>(data_len & 0xFF);
199 size_t shifted_len = data_len;
200 for (
int i = 7; i >= 0; i--) {
201 header[2+i] =
static_cast<uint8
>(shifted_len & 0xFF);
209 mg_write(connection_, header, header_size);
210 mg_write(connection_, data, data_len);
217 static int LogCallback(
const mg_connection*,
const char* message) {
218 LOG(
ERROR) <<
"Mongoose: " << message;
226 const std::vector<std::string> queries =
228 const size_t num_queries = queries.size();
229 for (
size_t i = 0; i < num_queries; ++i) {
230 const std::vector<std::string> pairs =
233 DCHECK(pairs.size() == 1 || pairs.size() == 2);
234 if (pairs.size() > 1) {
238 const size_t buffer_length = pairs[1].length() * 5;
240 mg_url_decode(pairs[1].c_str(), static_cast<int>(pairs[1].
length()),
241 buffer.Get(),
static_cast<int>(buffer_length), 1);
243 args[pairs[0]] =
buffer.Get();
254 static const std::string MakeRelativePath(
258 const std::string& base_path = handler->GetBasePath();
259 const std::string path = full_path.length() < base_path.length()
261 : full_path.substr(base_path.
length(),
264 size_t start_pos = path.find_first_not_of(
'/');
265 if (start_pos == std::string::npos)
269 size_t end_pos = path.find_last_not_of(
'/');
270 if (end_pos == std::string::npos)
271 end_pos = path.length();
275 return path.substr(start_pos, end_pos - start_pos);
280 static const std::string EmbedAllLocalTags(
289 std::string search_path = path;
290 while (search_path.length()) {
291 HttpServer::HandlerMap::const_iterator found = handlers.find(search_path);
293 if (found != handlers.end()) {
295 return found->second;
298 const size_t pos = search_path.rfind(
"/");
299 if (pos == 0U && search_path.length() > 1) {
304 search_path = search_path.substr(0, pos);
313 static const std::string GetFileData(
const std::string& path,
314 const char* query_string,
315 const std::string& header_html,
316 const std::string& footer_html,
318 std::string* content_type,
319 bool embed_local_sourced_files) {
321 if (!handler.Get()) {
325 return std::string();
332 std::string response = handler->HandleRequest(
333 MakeRelativePath(handler, path), args, content_type);
337 if (*content_type ==
"text/html") {
339 if (!header_html.empty())
341 if (!footer_html.empty())
343 if (embed_local_sourced_files)
344 response = EmbedAllLocalTags(response, handlers);
355 static bool FindLocallyReferencedTag(
357 const std::string& tag_in,
const std::string& attribute,
size_t* pos,
358 std::string* target, std::string* data, std::string* content_type) {
361 const std::string tag =
"<" + tag_in;
362 *pos = html.find(tag, *pos);
363 if (*pos == std::string::npos) {
367 const size_t end = html.find(
">", *pos);
368 if (end != std::string::npos) {
370 *target = html.substr(*pos, end - *pos + 1U);
372 const std::vector<std::string> attributes =
374 const size_t count = attributes.size();
376 for (
size_t i = 0; i < count; ++i) {
377 if (attributes[i] == attribute && i < count - 1U) {
379 const std::string& attr = attributes[i + 1U];
380 const std::string path = attr.substr(attr.find_first_not_of(
"\"'"),
381 attr.find_last_not_of(
"\"'"));
384 *content_type = mg_get_builtin_mime_type(path.c_str());
385 *data = GetFileData(path,
"",
"",
"", handlers, content_type,
true);
397 static const std::string FormatImgTag(
const std::string& content_type,
398 const std::string& data) {
400 return "<img src='data:" + content_type +
";base64," +
403 static const std::string FormatLinkTag(
const std::string& content_type,
404 const std::string& data) {
406 return "<style>\n" + data +
"\n</style>\n";
408 static const std::string FormatScriptTag(
const std::string& content_type,
409 const std::string& data) {
411 return "<script>\n" + data +
"\n";
419 static const std::string EmbedLocalTags(
420 const std::string& source,
422 const std::string& tag,
423 const std::string& attribute,
424 const std::string (*formatter)(
const std::string& content_type,
425 const std::string& data)) {
426 std::string html = source;
430 std::string content_type;
431 while (FindLocallyReferencedTag(
432 html, handlers, tag, attribute, &pos, &target, &data, &content_type)) {
435 const std::string replacement = formatter(content_type, data);
438 pos += replacement.length();
441 pos += target.length();
449 static const std::string EmbedAllLocalTags(
451 std::string html = source;
452 html = EmbedLocalTags(html, handlers,
"img",
"src", FormatImgTag);
453 html = EmbedLocalTags(html, handlers,
"link",
"href", FormatLinkTag);
454 html = EmbedLocalTags(html, handlers,
"script",
"src", FormatScriptTag);
462 static void SendStatusCode(mg_connection* connection,
const std::string& status,
463 const std::string&
text) {
464 std::stringstream header_stream;
465 header_stream <<
"HTTP/1.1 " << status <<
"\r\n"
466 <<
"Content-Length: " << text.length() <<
"\r\n"
467 <<
"Connection: close\r\n\r\n" <<
text;
468 const std::string headers = header_stream.str();
469 mg_printf(connection,
"%s", headers.c_str());
477 static bool ParseRangeRequest(
const char* range_header,
478 int64 content_length,
481 std::string* range) {
482 static const size_t kEqualPos = 5U;
484 bool valid_request =
false;
490 const std::string header_string(range_header);
491 if (header_string.find(
"=") == kEqualPos &&
492 header_string.substr(0, kEqualPos) ==
"bytes") {
493 std::istringstream header(header_string.substr(kEqualPos + 1U));
494 int64 start = 0, end = 0;
495 header >> start >> end;
496 valid_request = !header.fail() && end < 0;
498 *range_start = start;
499 *range_end = std::min(-end, content_length);
500 std::stringstream range_stream;
501 range_stream <<
"Content-Range: bytes " << *range_start <<
"-"
502 << *range_end <<
"/" << content_length <<
"\r\n";
503 *range = range_stream.str();
506 return valid_request;
510 static void SendFileData(mg_connection* connection,
511 const std::string& method,
512 const std::string& content_type,
513 const std::string& data) {
515 std::string status(
"200 OK");
518 int64 range_start = 0;
519 int64 range_end =
static_cast<int64
>(data.length());
523 if (
const char* header = mg_get_header(connection,
"Range"))
524 if (ParseRangeRequest(header, range_end, &range_start, &range_end, &range))
525 status =
"206 Partial Content";
528 std::stringstream header_stream;
529 header_stream <<
"HTTP/1.1 " << status <<
"\r\n"
530 <<
"Content-Type: " << content_type <<
"\r\n"
531 <<
"Content-Length: " << data.length() <<
"\r\n"
532 <<
"Connection: close\r\n"
533 <<
"Accept-Ranges: bytes\r\n" << range <<
"\r\n";
536 const std::string headers = header_stream.str();
537 mg_printf(connection,
"%s", headers.c_str());
539 if (method !=
"HEAD") {
540 const size_t size =
static_cast<size_t>(range_end - range_start + 1);
541 mg_write(connection, &data[static_cast<size_t>(range_start)], size);
548 static int BeginRequestCallback(mg_connection* connection) {
549 const mg_request_info* info = mg_get_request_info(connection);
554 if (NULL != mg_get_header(connection,
"Sec-WebSocket-Key")) {
558 const std::string method(info->request_method);
559 if (method ==
"GET" || method ==
"POST" || method ==
"HEAD") {
562 HttpServer* server =
reinterpret_cast<HttpServer*
>(info->user_data);
565 std::string content_type = mg_get_builtin_mime_type(info->uri);
567 const std::string data =
568 GetFileData(info->uri, info->query_string, server->GetHeaderHtml(),
569 server->GetFooterHtml(), server->GetHandlers(),
570 &content_type, server->EmbedLocalSourcedFiles());
573 SendStatusCode(connection,
"404 Not Found",
574 "Error 404: Not Found\nThe requested file was not found.");
576 SendFileData(connection, method, content_type, data);
586 static int WebsocketConnect(
const mg_connection* connection) {
587 mg_connection* unconst_connection =
const_cast<mg_connection*
>(connection);
590 const mg_request_info* info = mg_get_request_info(unconst_connection);
594 HttpServer* server =
reinterpret_cast<HttpServer*
>(info->user_data);
596 FindHandlerForPath(info->uri, server->GetHandlers());
597 if (!handler.Get()) {
604 const std::string path = MakeRelativePath(handler, info->uri);
607 if (!websocket.Get()) {
613 helper->SetWebsocket(websocket);
614 helper->Register(server);
621 static void WebsocketReady(mg_connection* connection) {
622 const mg_request_info* info = mg_get_request_info(connection);
625 HttpServer* server =
reinterpret_cast<HttpServer*
>(info->user_data);
626 HttpServer::WebsocketHelper::FindWebsocket(server, connection)->
632 static int WebsocketData(mg_connection* connection,
int bits,
633 char* data,
size_t data_len) {
634 const mg_request_info* info = mg_get_request_info(connection);
637 HttpServer* server =
reinterpret_cast<HttpServer*
>(info->user_data);
639 HttpServer::WebsocketHelper::FindWebsocket(server, connection);
640 DCHECK(helper) <<
"WebsocketData(): failed websocket lookup";
643 result = helper->ReceiveData(static_cast<uint8>(bits), data, data_len);
645 helper->Unregister(server);
655 : base_path_(base_path) {}
661 embed_local_sourced_files_(false) {
663 std::stringstream int_to_string;
664 int_to_string << port;
667 const std::string port_cstr = int_to_string.str();
668 int_to_string.str(
"");
669 int_to_string << num_threads;
670 const std::string num_threads_cstr = int_to_string.str();
672 mg_callbacks callbacks;
673 const char* options[] = {
"listening_ports", port_cstr.c_str(),
674 "num_threads", num_threads_cstr.c_str(),
677 memset(&callbacks, 0,
sizeof(callbacks));
678 callbacks.log_message = LogCallback;
679 callbacks.begin_request = BeginRequestCallback;
680 callbacks.websocket_connect = WebsocketConnect;
681 callbacks.websocket_ready = WebsocketReady;
682 callbacks.websocket_data = WebsocketData;
684 context_ = mg_start(&callbacks,
this, options);
696 std::string content_type = mg_get_builtin_mime_type(uri.c_str());
699 const size_t query_pos = uri.find(
'?');
700 const std::string path = uri.substr(0, query_pos);
701 const std::string query_string = query_pos == std::string::npos ?
702 "" : uri.substr(query_pos + 1U, std::string::npos);
706 query_string.c_str(), header_, footer_,
GetHandlers(),
707 &content_type, embed_local_sourced_files_);
711 return context_ != NULL;
717 std::string chomped_path = handler->GetBasePath();
721 handlers_[chomped_path] = handler;
726 handlers_.erase(path);
734 void HttpServer::RegisterWebsocket(
void* key, WebsocketHelper* helper) {
736 DCHECK(websockets_.find(key) == websockets_.end());
737 websockets_[key] = helper;
742 WebsocketMap::iterator it = websockets_.find(key);
743 return (it == websockets_.end())
744 ? static_cast<HttpServer::WebsocketHelper*>(NULL)
748 void HttpServer::UnregisterWebsocket(
void* key) {
750 WebsocketMap::iterator it = websockets_.find(key);
751 DCHECK(it != websockets_.end()) <<
"could not find websocket to unregister";
752 if (it != websockets_.end()) {
753 websockets_.erase(it);
758 const char* data,
size_t data_len,
bool is_binary) {
759 HttpServer::WebsocketHelper::Opcode opcode = is_binary
760 ? HttpServer::WebsocketHelper::BINARY
761 : HttpServer::WebsocketHelper::TEXT;
762 helper_->SendData(opcode, data, data_len);
767 return websockets_.size();
774 embed_local_sourced_files_(false) {}
778 #endif // !ION_PRODUCTION
kShortTerm is used for objects that are very transient in nature, such as scratch memory used to comp...
bool StartsWith(const std::string &target, const std::string &start)
Returns whether target begins with start.
size_t WebsocketCount()
Return the number of currently-connected websockets.
HandlerMap GetHandlers() const
Returns the handlers registered with this server.
GenericLockGuard< port::Mutex > LockGuard
Convenient typedefs for ion::port::Mutex.
#define LOG(severity)
Logs the streamed message unconditionally with a severity of severity.
~RequestHandler() override
The destructor is protected since this derived from base::Referent.
std::map< std::string, RequestHandlerPtr > HandlerMap
void SendData(const char *data, size_t data_len, bool is_binary)
Send a TEXT or BINARY frame with the specified data.
const std::string GetUriData(const std::string &uri) const
Returns the data of the requested URI, or returns an empty string if it does not exist.
base::ReferentPtr< Websocket >::Type WebsocketPtr
HttpServer(int port, int num_threads)
Starts a HttpServer on the passed port with the passed number of handler threads. ...
friend class WebsocketHelper
These methods and fields are all concerned with keeping track of active websocket connections...
std::vector< std::string > ION_API SplitString(const std::string &str, const std::string &delimiters)
Splits a string into a vector of substrings, given a set of delimiter characters (expressed as a stri...
bool RemoveSuffix(const std::string &suffix, std::string *target)
Removes suffix from the end of target if target ends with it.
T * Get() const
Returns a raw pointer to the instance, which may be NULL.
A LockGuard locks a mutex when created, and unlocks it when destroyed.
base::ReferentPtr< RequestHandler >::Type RequestHandlerPtr
Copyright 2016 Google Inc.
void RegisterHandler(const RequestHandlerPtr &handler)
Registers the passed handler at the path returned by handler->GetBasePath().
std::string ION_API MimeBase64EncodeString(const std::string &str)
Returns a mime base-64 encoded version of the passed string.
bool IsRunning() const
Returns whether the server is running.
void UnregisterHandler(const std::string &path)
Unregisters the handler at path.
std::map< std::string, std::string > QueryMap
RequestHandler(const std::string &base_path)
The constructor is protected since this is an abstract base class.
port::Mutex mutex_
Protects shared access to the Allocator and FT_Library.
std::string ReplaceString(const std::string &search, const std::string &from, const std::string &to)
Returns a string with all instances of from replaced with to.
A SharedPtr is a smart shared pointer to an instance of some class that implements reference counting...