Ion
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
httpserver.cc
Go to the documentation of this file.
1 
18 #include "ion/remote/httpserver.h"
19 
20 #if !ION_PRODUCTION
21 
22 #include <stdio.h>
23 
24 #include <algorithm>
25 #include <cstring>
26 #include <sstream>
27 
28 #include "ion/base/lockguards.h"
29 #include "ion/base/logging.h"
31 #include "ion/base/stringutils.h"
32 
33 #include "third_party/mongoose/mongoose.h"
34 
35 #endif // !ION_PRODUCTION
36 
37 namespace ion {
38 namespace remote {
39 
40 #if !ION_PRODUCTION
41 
44  public:
45  explicit WebsocketHelper(mg_connection* conn)
46  : connection_(conn), ready_(false), binary_(false) {
47  }
48  ~WebsocketHelper() {
49  websocket_->helper_ = NULL;
50  }
51 
52  void SetWebsocket(const WebsocketPtr& websocket) {
53  websocket_ = websocket;
54  websocket_->SetHelper(this);
55  }
56  void Register(HttpServer* server) {
57  server->RegisterWebsocket(connection_, this);
58  }
59  void Unregister(HttpServer* server) {
60  server->UnregisterWebsocket(connection_);
61  }
62 
63  void ConnectionReady() {
64  ready_ = true;
65  websocket_->ConnectionReady();
66  }
67 
68  enum Opcode {
69  CONTINUATION = 0,
70  TEXT = 1,
71  BINARY = 2,
72  CLOSE = 8,
73  PING = 9,
74  PONG = 10
75  };
76 
77  int ReceiveData(uint8 bits, char* data, size_t data_len);
78  void SendData(Opcode opcode, const char* data, size_t data_len);
79 
83  static WebsocketHelper* FindWebsocket(HttpServer* server,
84  mg_connection* connection) {
85  return server->FindWebsocket(connection);
86  }
87 
88  private:
89  int BeginContinuation(bool is_binary, char* data, size_t data_len);
90  void AppendContinuationData(char data[], size_t data_len);
91 
92  mg_connection* connection_;
93  WebsocketPtr websocket_;
94  bool ready_;
96  std::vector<char> continuation_;
99  bool binary_;
100  port::Mutex mutex_;
101 };
102 
103 int HttpServer::WebsocketHelper::ReceiveData(
104  uint8 bits, char* data, size_t data_len) {
105 
106  Opcode opcode = static_cast<Opcode>(bits & 0xF);
107  bool fin = bits & 0x80;
108 
109  switch (opcode) {
110  case CONTINUATION:
111  if (continuation_.size() == 0) {
114  return 0;
115  }
116  AppendContinuationData(data, data_len);
117  if (fin) {
119  int result = websocket_->ReceiveData(
120  &(continuation_[0]), continuation_.size(), binary_);
121  continuation_.clear();
122  return result;
123  } else {
125  return 1;
126  }
127  case TEXT:
128  if (fin) {
130  return websocket_->ReceiveData(data, data_len, false);
131  } else {
132  return BeginContinuation(false, data, data_len);
133  }
134  case BINARY:
135  if (fin) {
137  return websocket_->ReceiveData(data, data_len, true);
138  } else {
139  return BeginContinuation(true, data, data_len);
140  }
141  case CLOSE:
142  return 0;
143  case PING:
145  SendData(PONG, data, data_len);
146  return 1;
147  case PONG:
150  return 0;
151  default:
152  LOG(WARNING) << "Unrecognized websocket opcode: " << opcode;
153  return 0;
154  }
155 }
156 
157 int HttpServer::WebsocketHelper::BeginContinuation(
158  bool is_binary, char* data, size_t data_len) {
159  if (continuation_.size() > 0) {
161  continuation_.clear();
162  return 0;
163  }
164  binary_ = is_binary;
165  AppendContinuationData(data, data_len);
166  return 1;
167 }
168 
169 void HttpServer::WebsocketHelper::AppendContinuationData(
170  char data[], size_t data_len) {
171  continuation_.insert(continuation_.end(), data, data + data_len);
172 }
173 
174 void HttpServer::WebsocketHelper::SendData(
175  Opcode opcode, const char* data, size_t data_len) {
178  uint8 header[10];
179  size_t header_size;
180 
182  header[0] = static_cast<uint8>(0x80 | (opcode & 0xF));
183 
186  if (data_len < 126) {
188  header[1] = static_cast<uint8>(data_len);
189  header_size = 2;
190  } else if (data_len <= 0xFFFF) {
192  header[1] = 126;
193  header[2] = static_cast<uint8>(data_len >> 8);
194  header[3] = static_cast<uint8>(data_len & 0xFF);
195  header_size = 4;
196  } else {
198  header[1] = 127;
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);
202  shifted_len >>= 8;
203  }
204  header_size = 10;
205  }
206 
208  base::LockGuard lock(&mutex_);
209  mg_write(connection_, header, header_size);
210  mg_write(connection_, data, data_len);
211 }
212 
213 namespace {
214 
217 static int LogCallback(const mg_connection*, const char* message) {
218  LOG(ERROR) << "Mongoose: " << message;
219  return 1;
220 }
221 
223 static HttpServer::QueryMap BuildQueryMap(const char* query_string) {
225  if (query_string) {
226  const std::vector<std::string> queries =
227  base::SplitString(query_string, "&");
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 =
231  base::SplitString(queries[i], "=");
233  DCHECK(pairs.size() == 1 || pairs.size() == 2);
234  if (pairs.size() > 1) {
238  const size_t buffer_length = pairs[1].length() * 5;
239  base::ScopedAllocation<char> buffer(base::kShortTerm, buffer_length);
240  mg_url_decode(pairs[1].c_str(), static_cast<int>(pairs[1].length()),
241  buffer.Get(), static_cast<int>(buffer_length), 1);
242 
243  args[pairs[0]] = buffer.Get();
244  } else {
245  args[pairs[0]] = "";
246  }
247  }
248  }
249  return args;
250 }
251 
254 static const std::string MakeRelativePath(
255  const HttpServer::RequestHandlerPtr& handler, const std::string& full_path) {
258  const std::string& base_path = handler->GetBasePath();
259  const std::string path = full_path.length() < base_path.length()
260  ? std::string()
261  : full_path.substr(base_path.length(),
262  full_path.length() - base_path.length());
263 
264  size_t start_pos = path.find_first_not_of('/');
265  if (start_pos == std::string::npos)
266  start_pos = 0;
267  if (path == "/")
268  start_pos = 1;
269  size_t end_pos = path.find_last_not_of('/');
270  if (end_pos == std::string::npos)
271  end_pos = path.length();
272  else
273  end_pos++;
274 
275  return path.substr(start_pos, end_pos - start_pos);
276 }
277 
280 static const std::string EmbedAllLocalTags(
281  const std::string& source, const HttpServer::HandlerMap& handlers);
282 
283 
284 static HttpServer::RequestHandlerPtr FindHandlerForPath(
285  const std::string& path, const HttpServer::HandlerMap& handlers) {
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;
296  } else {
298  const size_t pos = search_path.rfind("/");
299  if (pos == 0U && search_path.length() > 1) {
301  search_path = "/";
302  } else {
304  search_path = search_path.substr(0, pos);
305  }
306  }
307  }
309  static const HttpServer::RequestHandlerPtr no_handler;
310  return no_handler;
311 }
312 
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,
317  const HttpServer::HandlerMap& handlers,
318  std::string* content_type,
319  bool embed_local_sourced_files) {
320  HttpServer::RequestHandlerPtr handler = FindHandlerForPath(path, handlers);
321  if (!handler.Get()) {
325  return std::string();
326  }
327 
329  const HttpServer::QueryMap args = BuildQueryMap(query_string);
330 
332  std::string response = handler->HandleRequest(
333  MakeRelativePath(handler, path), args, content_type);
334 
337  if (*content_type == "text/html") {
339  if (!header_html.empty())
340  response = base::ReplaceString(response, "<!--HEADER-->", header_html);
341  if (!footer_html.empty())
342  response = base::ReplaceString(response, "<!--FOOTER-->", footer_html);
343  if (embed_local_sourced_files)
344  response = EmbedAllLocalTags(response, handlers);
345  }
346  return response;
347 }
348 
355 static bool FindLocallyReferencedTag(
356  const std::string& html, const HttpServer::HandlerMap& handlers,
357  const std::string& tag_in, const std::string& attribute, size_t* pos,
358  std::string* target, std::string* data, std::string* content_type) {
359  data->clear();
360  target->clear();
361  const std::string tag = "<" + tag_in;
362  *pos = html.find(tag, *pos);
363  if (*pos == std::string::npos) {
364  return false;
365  } else {
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 =
373  base::SplitString(*target, " =>");
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("\"'"));
383  if (base::StartsWith(path, "/")) {
384  *content_type = mg_get_builtin_mime_type(path.c_str());
385  *data = GetFileData(path, "", "", "", handlers, content_type, true);
386  }
387  break;
388  }
389  }
390  }
391  return true;
392  }
393 }
394 
397 static const std::string FormatImgTag(const std::string& content_type,
398  const std::string& data) {
400  return "<img src='data:" + content_type + ";base64," +
401  base::MimeBase64EncodeString(data) + "'>";
402 }
403 static const std::string FormatLinkTag(const std::string& content_type,
404  const std::string& data) {
406  return "<style>\n" + data + "\n</style>\n";
407 }
408 static const std::string FormatScriptTag(const std::string& content_type,
409  const std::string& data) {
411  return "<script>\n" + data + "\n";
412 }
413 
419 static const std::string EmbedLocalTags(
420  const std::string& source,
421  const HttpServer::HandlerMap& handlers,
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;
427  size_t pos = 0U;
428  std::string data;
429  std::string target;
430  std::string content_type;
431  while (FindLocallyReferencedTag(
432  html, handlers, tag, attribute, &pos, &target, &data, &content_type)) {
433  if (!data.empty()) {
435  const std::string replacement = formatter(content_type, data);
436  html = base::ReplaceString(html, target, replacement);
438  pos += replacement.length();
439  } else {
441  pos += target.length();
442  }
443  }
444  return html;
445 }
446 
449 static const std::string EmbedAllLocalTags(
450  const std::string& source, const HttpServer::HandlerMap& handlers) {
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);
455  return html;
456 }
457 
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());
470 }
471 
477 static bool ParseRangeRequest(const char* range_header,
478  int64 content_length,
479  int64* range_start,
480  int64* range_end,
481  std::string* range) {
482  static const size_t kEqualPos = 5U;
483 
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;
497  if (valid_request) {
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();
504  }
505  }
506  return valid_request;
507 }
508 
510 static void SendFileData(mg_connection* connection,
511  const std::string& method, // GET, HEAD, or POST.
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());
522  std::string range;
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";
526 
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";
534 
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);
542  }
543 }
544 
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")) {
555  return 0;
556  }
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);
563  DCHECK(server);
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());
571 
572  if (data.empty()) {
573  SendStatusCode(connection, "404 Not Found",
574  "Error 404: Not Found\nThe requested file was not found.");
575  } else {
576  SendFileData(connection, method, content_type, data);
577  }
578  return 1;
579  } else {
580  return 0;
581  }
582 }
583 
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);
591 
594  HttpServer* server = reinterpret_cast<HttpServer*>(info->user_data);
596  FindHandlerForPath(info->uri, server->GetHandlers());
597  if (!handler.Get()) {
599  return -1;
600  }
601 
603  const HttpServer::QueryMap args = BuildQueryMap(info->query_string);
604  const std::string path = MakeRelativePath(handler, info->uri);
605 
606  HttpServer::WebsocketPtr websocket = handler->ConnectWebsocket(path, args);
607  if (!websocket.Get()) {
609  return -1;
610  }
612  new HttpServer::WebsocketHelper(unconst_connection);
613  helper->SetWebsocket(websocket);
614  helper->Register(server);
616  return 0;
617 }
618 
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)->
627  ConnectionReady();
628 }
629 
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";
641  int result = 0;
642  if (helper) {
643  result = helper->ReceiveData(static_cast<uint8>(bits), data, data_len);
644  if (result == 0) {
645  helper->Unregister(server);
646  delete helper;
647  }
648  }
649  return result;
650 }
651 
652 } // anonymous namespace
653 
654 HttpServer::RequestHandler::RequestHandler(const std::string& base_path)
655  : base_path_(base_path) {}
656 
658 
659 HttpServer::HttpServer(int port, int num_threads)
660  : context_(NULL),
661  embed_local_sourced_files_(false) {
662  if (port) {
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();
671 
672  mg_callbacks callbacks;
673  const char* options[] = { "listening_ports", port_cstr.c_str(),
674  "num_threads", num_threads_cstr.c_str(),
675  NULL };
676 
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);
685  }
686 }
687 
689  if (context_) {
690  mg_stop(context_);
691  context_ = NULL;
692  }
693 }
694 
695 const std::string HttpServer::GetUriData(const std::string& uri) const {
696  std::string content_type = mg_get_builtin_mime_type(uri.c_str());
697 
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);
703 
705  return GetFileData(base::StartsWith(path, "/") ? path : '/' + path,
706  query_string.c_str(), header_, footer_, GetHandlers(),
707  &content_type, embed_local_sourced_files_);
708 }
709 
710 bool HttpServer::IsRunning() const {
711  return context_ != NULL;
712 }
713 
716  DCHECK(handler.Get());
717  std::string chomped_path = handler->GetBasePath();
718  while (chomped_path.length() > 1 && base::RemoveSuffix("/", &chomped_path)) {}
719 
720  ion::base::LockGuard lock(&handlers_mutex_);
721  handlers_[chomped_path] = handler;
722 }
723 
724 void HttpServer::UnregisterHandler(const std::string& path) {
725  ion::base::LockGuard lock(&handlers_mutex_);
726  handlers_.erase(path);
727 }
728 
730  ion::base::LockGuard lock(&handlers_mutex_);
731  return handlers_;
732 }
733 
734 void HttpServer::RegisterWebsocket(void* key, WebsocketHelper* helper) {
735  base::LockGuard lock(&websocket_mutex_);
736  DCHECK(websockets_.find(key) == websockets_.end());
737  websockets_[key] = helper;
738 }
739 
740 HttpServer::WebsocketHelper* HttpServer::FindWebsocket(void* key) {
741  base::LockGuard lock(&websocket_mutex_);
742  WebsocketMap::iterator it = websockets_.find(key);
743  return (it == websockets_.end())
744  ? static_cast<HttpServer::WebsocketHelper*>(NULL)
745  : it->second;
746 }
747 
748 void HttpServer::UnregisterWebsocket(void* key) {
749  base::LockGuard lock(&websocket_mutex_);
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);
754  }
755 }
756 
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);
763 }
764 
766  base::LockGuard lock(&websocket_mutex_);
767  return websockets_.size();
768 }
769 
770 #else
771 
772 HttpServer::HttpServer(int port, int num_threads)
773  : context_(NULL),
774  embed_local_sourced_files_(false) {}
775 
777 
778 #endif // !ION_PRODUCTION
779 
780 } // namespace remote
781 } // namespace ion
std::string buffer
kShortTerm is used for objects that are very transient in nature, such as scratch memory used to comp...
Definition: allocator.h:36
bool StartsWith(const std::string &target, const std::string &start)
Returns whether target begins with start.
Definition: stringutils.h:76
size_t WebsocketCount()
Return the number of currently-connected websockets.
Definition: httpserver.cc:765
HandlerMap GetHandlers() const
Returns the handlers registered with this server.
Definition: httpserver.cc:729
std::string text
#define DCHECK(expr)
Definition: logging.h:331
GenericLockGuard< port::Mutex > LockGuard
Convenient typedefs for ion::port::Mutex.
Definition: lockguards.h:192
#define LOG(severity)
Logs the streamed message unconditionally with a severity of severity.
Definition: logging.h:216
~RequestHandler() override
The destructor is protected since this derived from base::Referent.
Definition: httpserver.cc:657
std::map< std::string, RequestHandlerPtr > HandlerMap
Definition: httpserver.h:116
void SendData(const char *data, size_t data_len, bool is_binary)
Send a TEXT or BINARY frame with the specified data.
Definition: httpserver.cc:757
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.
Definition: httpserver.cc:695
base::ReferentPtr< Websocket >::Type WebsocketPtr
Definition: httpserver.h:65
HttpServer(int port, int num_threads)
Starts a HttpServer on the passed port with the passed number of handler threads. ...
Definition: httpserver.cc:659
friend class WebsocketHelper
These methods and fields are all concerned with keeping track of active websocket connections...
Definition: httpserver.h:166
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...
Definition: stringutils.cc:187
uint32 length
bool RemoveSuffix(const std::string &suffix, std::string *target)
Removes suffix from the end of target if target ends with it.
Definition: stringutils.h:113
T * Get() const
Returns a raw pointer to the instance, which may be NULL.
Definition: sharedptr.h:89
A LockGuard locks a mutex when created, and unlocks it when destroyed.
Definition: lockguards.h:90
base::ReferentPtr< RequestHandler >::Type RequestHandlerPtr
Definition: httpserver.h:115
Copyright 2016 Google Inc.
void RegisterHandler(const RequestHandlerPtr &handler)
Registers the passed handler at the path returned by handler->GetBasePath().
Definition: httpserver.cc:714
std::string ION_API MimeBase64EncodeString(const std::string &str)
Returns a mime base-64 encoded version of the passed string.
Definition: stringutils.cc:57
bool IsRunning() const
Returns whether the server is running.
Definition: httpserver.cc:710
void UnregisterHandler(const std::string &path)
Unregisters the handler at path.
Definition: httpserver.cc:724
std::map< std::string, std::string > QueryMap
Definition: httpserver.h:35
RequestHandler(const std::string &base_path)
The constructor is protected since this is an abstract base class.
Definition: httpserver.cc:654
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.
Definition: stringutils.h:122
A SharedPtr is a smart shared pointer to an instance of some class that implements reference counting...
Definition: sharedptr.h:60