<commit_before>// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/browser/web_contents_preferences.h"

#include <algorithm>
#include <string>
#include <vector>

#include "atom/browser/native_window.h"
#include "atom/browser/web_view_manager.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "cc/base/switches.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/web_preferences.h"
#include "native_mate/dictionary.h"
#include "net/base/filename_util.h"

#if defined(OS_WIN)
#include "ui/gfx/switches.h"
#endif

DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences);

namespace atom {

// static
std::vector<WebContentsPreferences*> WebContentsPreferences::instances_;

WebContentsPreferences::WebContentsPreferences(
    content::WebContents* web_contents,
    const mate::Dictionary& web_preferences)
    : web_contents_(web_contents) {
  v8::Isolate* isolate = web_preferences.isolate();
  mate::Dictionary copied(isolate, web_preferences.GetHandle()->Clone());
  // Following fields should not be stored.
  copied.Delete("embedder");
  copied.Delete("isGuest");
  copied.Delete("session");

  mate::ConvertFromV8(isolate, copied.GetHandle(), &web_preferences_);
  web_contents->SetUserData(UserDataKey(), this);

  instances_.push_back(this);
}

WebContentsPreferences::~WebContentsPreferences() {
  instances_.erase(
      std::remove(instances_.begin(), instances_.end(), this),
      instances_.end());
}

void WebContentsPreferences::Merge(const base::DictionaryValue& extend) {
  web_preferences_.MergeDictionary(&extend);
}

// static
content::WebContents* WebContentsPreferences::GetWebContentsFromProcessID(
    int process_id) {
  for (WebContentsPreferences* preferences : instances_) {
    content::WebContents* web_contents = preferences->web_contents_;
    if (web_contents->GetRenderProcessHost()->GetID() == process_id)
      return web_contents;
  }
  return nullptr;
}

// static
void WebContentsPreferences::AppendExtraCommandLineSwitches(
    content::WebContents* web_contents, base::CommandLine* command_line) {
  WebContentsPreferences* self = FromWebContents(web_contents);
  if (!self)
    return;

  base::DictionaryValue& web_preferences = self->web_preferences_;

  bool b;
  // Check if plugins are enabled.
  if (web_preferences.GetBoolean("plugins", &b) && b)
    command_line->AppendSwitch(switches::kEnablePlugins);

  // Experimental flags.
  if (web_preferences.GetBoolean(options::kExperimentalFeatures, &b) && b)
    command_line->AppendSwitch(
        ::switches::kEnableExperimentalWebPlatformFeatures);
  if (web_preferences.GetBoolean(options::kExperimentalCanvasFeatures, &b) && b)
    command_line->AppendSwitch(::switches::kEnableExperimentalCanvasFeatures);

  // Check if we have node integration specified.
  bool node_integration = true;
  web_preferences.GetBoolean(options::kNodeIntegration, &node_integration);
  command_line->AppendSwitchASCII(switches::kNodeIntegration,
                                  node_integration ? "true" : "false");

  // Whether to enable node integration in Worker.
  if (web_preferences.GetBoolean(options::kNodeIntegrationInWorker, &b) && b)
    command_line->AppendSwitch(switches::kNodeIntegrationInWorker);

  // If the `sandbox` option was passed to the BrowserWindow's webPreferences,
  // pass `--enable-sandbox` to the renderer so it won't have any node.js
  // integration.
  if (IsSandboxed(web_contents))
    command_line->AppendSwitch(switches::kEnableSandbox);
  if (UsesNativeWindowOpen(web_contents))
    command_line->AppendSwitch(switches::kNativeWindowOpen);

  // The preload script.
  base::FilePath::StringType preload;
  if (web_preferences.GetString(options::kPreloadScript, &preload)) {
    if (base::FilePath(preload).IsAbsolute())
      command_line->AppendSwitchNative(switches::kPreloadScript, preload);
    else
      LOG(ERROR) << "preload script must have absolute path.";
  } else if (web_preferences.GetString(options::kPreloadURL, &preload)) {
    // Translate to file path if there is "preload-url" option.
    base::FilePath preload_path;
    if (net::FileURLToFilePath(GURL(preload), &preload_path))
      command_line->AppendSwitchPath(switches::kPreloadScript, preload_path);
    else
      LOG(ERROR) << "preload url must be file:// protocol.";
  }

  // Run Electron APIs and preload script in isolated world
  bool isolated;
  if (web_preferences.GetBoolean(options::kContextIsolation, &isolated) &&
      isolated)
    command_line->AppendSwitch(switches::kContextIsolation);

  // --background-color.
  std::string color;
  if (web_preferences.GetString(options::kBackgroundColor, &color))
    command_line->AppendSwitchASCII(switches::kBackgroundColor, color);

  // --guest-instance-id, which is used to identify guest WebContents.
  int guest_instance_id = 0;
  if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id))
    command_line->AppendSwitchASCII(switches::kGuestInstanceID,
                                    base::IntToString(guest_instance_id));

  // Pass the opener's window id.
  int opener_id;
  if (web_preferences.GetInteger(options::kOpenerID, &opener_id))
    command_line->AppendSwitchASCII(switches::kOpenerID,
                                    base::IntToString(opener_id));

#if defined(OS_MACOSX)
  // Enable scroll bounce.
  bool scroll_bounce;
  if (web_preferences.GetBoolean(options::kScrollBounce, &scroll_bounce) &&
      scroll_bounce)
    command_line->AppendSwitch(switches::kScrollBounce);
#endif

  // Custom command line switches.
  const base::ListValue* args;
  if (web_preferences.GetList("commandLineSwitches", &args)) {
    for (size_t i = 0; i < args->GetSize(); ++i) {
      std::string arg;
      if (args->GetString(i, &arg) && !arg.empty())
        command_line->AppendSwitch(arg);
    }
  }

  // Enable blink features.
  std::string blink_features;
  if (web_preferences.GetString(options::kBlinkFeatures, &blink_features))
    command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures,
                                    blink_features);

  // Disable blink features.
  std::string disable_blink_features;
  if (web_preferences.GetString(options::kDisableBlinkFeatures,
                                &disable_blink_features))
    command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
                                    disable_blink_features);

  // The initial visibility state.
  NativeWindow* window = NativeWindow::FromWebContents(web_contents);

  // Use embedder window for webviews
  if (guest_instance_id && !window) {
    auto manager = WebViewManager::GetWebViewManager(web_contents);
    if (manager) {
      auto embedder = manager->GetEmbedder(guest_instance_id);
      if (embedder)
        window = NativeWindow::FromWebContents(embedder);
    }
  }

  if (window) {
    bool visible = window->IsVisible() && !window->IsMinimized();
    if (!visible)  // Default state is visible.
      command_line->AppendSwitch(switches::kHiddenPage);
  }
}

bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
  WebContentsPreferences* self;
  if (!web_contents)
    return false;

  self = FromWebContents(web_contents);
  if (!self)
    return false;

  base::DictionaryValue& web_preferences = self->web_preferences_;
  bool sandboxed = false;
  web_preferences.GetBoolean("sandbox", &sandboxed);
  return sandboxed;
}

bool WebContentsPreferences::UsesNativeWindowOpen(
    content::WebContents* web_contents) {
  WebContentsPreferences* self;
  if (!web_contents)
    return false;

  self = FromWebContents(web_contents);
  if (!self)
    return false;

  base::DictionaryValue& web_preferences = self->web_preferences_;
  bool use = false;
  web_preferences.GetBoolean("nativeWindowOpen", &use);
  return use;
}

// static
void WebContentsPreferences::OverrideWebkitPrefs(
    content::WebContents* web_contents, content::WebPreferences* prefs) {
  WebContentsPreferences* self = FromWebContents(web_contents);
  if (!self)
    return;

  bool b;
  if (self->web_preferences_.GetBoolean("javascript", &b))
    prefs->javascript_enabled = b;
  if (self->web_preferences_.GetBoolean("images", &b))
    prefs->images_enabled = b;
  if (self->web_preferences_.GetBoolean("textAreasAreResizable", &b))
    prefs->text_areas_are_resizable = b;
  if (self->web_preferences_.GetBoolean("webgl", &b))
    prefs->experimental_webgl_enabled = b;
  if (self->web_preferences_.GetBoolean("webSecurity", &b)) {
    prefs->web_security_enabled = b;
    prefs->allow_running_insecure_content = !b;
  }
  if (self->web_preferences_.GetBoolean("allowRunningInsecureContent", &b))
    prefs->allow_running_insecure_content = b;
  const base::DictionaryValue* fonts = nullptr;
  if (self->web_preferences_.GetDictionary("defaultFontFamily", &fonts)) {
    base::string16 font;
    if (fonts->GetString("standard", &font))
      prefs->standard_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("serif", &font))
      prefs->serif_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("sansSerif", &font))
      prefs->sans_serif_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("monospace", &font))
      prefs->fixed_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("cursive", &font))
      prefs->cursive_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("fantasy", &font))
      prefs->fantasy_font_family_map[content::kCommonScript] = font;
  }
  int size;
  if (self->GetInteger("defaultFontSize", &size))
    prefs->default_font_size = size;
  if (self->GetInteger("defaultMonospaceFontSize", &size))
    prefs->default_fixed_font_size = size;
  if (self->GetInteger("minimumFontSize", &size))
    prefs->minimum_font_size = size;
  std::string encoding;
  if (self->web_preferences_.GetString("defaultEncoding", &encoding))
    prefs->default_encoding = encoding;
}

bool WebContentsPreferences::GetInteger(const std::string& attributeName,
                                        int* intValue) {
  // if it is already an integer, no conversion needed
  if (web_preferences_.GetInteger(attributeName, intValue))
    return true;

  base::string16 stringValue;
  if (web_preferences_.GetString(attributeName, &stringValue))
    return base::StringToInt(stringValue, intValue);

  return false;
}

}  // namespace atom
<commit_msg>Use GetBoolean directly<commit_after>// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/browser/web_contents_preferences.h"

#include <algorithm>
#include <string>
#include <vector>

#include "atom/browser/native_window.h"
#include "atom/browser/web_view_manager.h"
#include "atom/common/native_mate_converters/value_converter.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "cc/base/switches.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/web_preferences.h"
#include "native_mate/dictionary.h"
#include "net/base/filename_util.h"

#if defined(OS_WIN)
#include "ui/gfx/switches.h"
#endif

DEFINE_WEB_CONTENTS_USER_DATA_KEY(atom::WebContentsPreferences);

namespace atom {

// static
std::vector<WebContentsPreferences*> WebContentsPreferences::instances_;

WebContentsPreferences::WebContentsPreferences(
    content::WebContents* web_contents,
    const mate::Dictionary& web_preferences)
    : web_contents_(web_contents) {
  v8::Isolate* isolate = web_preferences.isolate();
  mate::Dictionary copied(isolate, web_preferences.GetHandle()->Clone());
  // Following fields should not be stored.
  copied.Delete("embedder");
  copied.Delete("isGuest");
  copied.Delete("session");

  mate::ConvertFromV8(isolate, copied.GetHandle(), &web_preferences_);
  web_contents->SetUserData(UserDataKey(), this);

  instances_.push_back(this);
}

WebContentsPreferences::~WebContentsPreferences() {
  instances_.erase(
      std::remove(instances_.begin(), instances_.end(), this),
      instances_.end());
}

void WebContentsPreferences::Merge(const base::DictionaryValue& extend) {
  web_preferences_.MergeDictionary(&extend);
}

// static
content::WebContents* WebContentsPreferences::GetWebContentsFromProcessID(
    int process_id) {
  for (WebContentsPreferences* preferences : instances_) {
    content::WebContents* web_contents = preferences->web_contents_;
    if (web_contents->GetRenderProcessHost()->GetID() == process_id)
      return web_contents;
  }
  return nullptr;
}

// static
void WebContentsPreferences::AppendExtraCommandLineSwitches(
    content::WebContents* web_contents, base::CommandLine* command_line) {
  WebContentsPreferences* self = FromWebContents(web_contents);
  if (!self)
    return;

  base::DictionaryValue& web_preferences = self->web_preferences_;

  bool b;
  // Check if plugins are enabled.
  if (web_preferences.GetBoolean("plugins", &b) && b)
    command_line->AppendSwitch(switches::kEnablePlugins);

  // Experimental flags.
  if (web_preferences.GetBoolean(options::kExperimentalFeatures, &b) && b)
    command_line->AppendSwitch(
        ::switches::kEnableExperimentalWebPlatformFeatures);
  if (web_preferences.GetBoolean(options::kExperimentalCanvasFeatures, &b) && b)
    command_line->AppendSwitch(::switches::kEnableExperimentalCanvasFeatures);

  // Check if we have node integration specified.
  bool node_integration = true;
  web_preferences.GetBoolean(options::kNodeIntegration, &node_integration);
  command_line->AppendSwitchASCII(switches::kNodeIntegration,
                                  node_integration ? "true" : "false");

  // Whether to enable node integration in Worker.
  if (web_preferences.GetBoolean(options::kNodeIntegrationInWorker, &b) && b)
    command_line->AppendSwitch(switches::kNodeIntegrationInWorker);

  // If the `sandbox` option was passed to the BrowserWindow's webPreferences,
  // pass `--enable-sandbox` to the renderer so it won't have any node.js
  // integration.
  if (IsSandboxed(web_contents))
    command_line->AppendSwitch(switches::kEnableSandbox);
  if (web_preferences.GetBoolean("nativeWindowOpen", &b) && b)
    command_line->AppendSwitch(switches::kNativeWindowOpen);

  // The preload script.
  base::FilePath::StringType preload;
  if (web_preferences.GetString(options::kPreloadScript, &preload)) {
    if (base::FilePath(preload).IsAbsolute())
      command_line->AppendSwitchNative(switches::kPreloadScript, preload);
    else
      LOG(ERROR) << "preload script must have absolute path.";
  } else if (web_preferences.GetString(options::kPreloadURL, &preload)) {
    // Translate to file path if there is "preload-url" option.
    base::FilePath preload_path;
    if (net::FileURLToFilePath(GURL(preload), &preload_path))
      command_line->AppendSwitchPath(switches::kPreloadScript, preload_path);
    else
      LOG(ERROR) << "preload url must be file:// protocol.";
  }

  // Run Electron APIs and preload script in isolated world
  bool isolated;
  if (web_preferences.GetBoolean(options::kContextIsolation, &isolated) &&
      isolated)
    command_line->AppendSwitch(switches::kContextIsolation);

  // --background-color.
  std::string color;
  if (web_preferences.GetString(options::kBackgroundColor, &color))
    command_line->AppendSwitchASCII(switches::kBackgroundColor, color);

  // --guest-instance-id, which is used to identify guest WebContents.
  int guest_instance_id = 0;
  if (web_preferences.GetInteger(options::kGuestInstanceID, &guest_instance_id))
    command_line->AppendSwitchASCII(switches::kGuestInstanceID,
                                    base::IntToString(guest_instance_id));

  // Pass the opener's window id.
  int opener_id;
  if (web_preferences.GetInteger(options::kOpenerID, &opener_id))
    command_line->AppendSwitchASCII(switches::kOpenerID,
                                    base::IntToString(opener_id));

#if defined(OS_MACOSX)
  // Enable scroll bounce.
  bool scroll_bounce;
  if (web_preferences.GetBoolean(options::kScrollBounce, &scroll_bounce) &&
      scroll_bounce)
    command_line->AppendSwitch(switches::kScrollBounce);
#endif

  // Custom command line switches.
  const base::ListValue* args;
  if (web_preferences.GetList("commandLineSwitches", &args)) {
    for (size_t i = 0; i < args->GetSize(); ++i) {
      std::string arg;
      if (args->GetString(i, &arg) && !arg.empty())
        command_line->AppendSwitch(arg);
    }
  }

  // Enable blink features.
  std::string blink_features;
  if (web_preferences.GetString(options::kBlinkFeatures, &blink_features))
    command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures,
                                    blink_features);

  // Disable blink features.
  std::string disable_blink_features;
  if (web_preferences.GetString(options::kDisableBlinkFeatures,
                                &disable_blink_features))
    command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
                                    disable_blink_features);

  // The initial visibility state.
  NativeWindow* window = NativeWindow::FromWebContents(web_contents);

  // Use embedder window for webviews
  if (guest_instance_id && !window) {
    auto manager = WebViewManager::GetWebViewManager(web_contents);
    if (manager) {
      auto embedder = manager->GetEmbedder(guest_instance_id);
      if (embedder)
        window = NativeWindow::FromWebContents(embedder);
    }
  }

  if (window) {
    bool visible = window->IsVisible() && !window->IsMinimized();
    if (!visible)  // Default state is visible.
      command_line->AppendSwitch(switches::kHiddenPage);
  }
}

bool WebContentsPreferences::IsSandboxed(content::WebContents* web_contents) {
  WebContentsPreferences* self;
  if (!web_contents)
    return false;

  self = FromWebContents(web_contents);
  if (!self)
    return false;

  base::DictionaryValue& web_preferences = self->web_preferences_;
  bool sandboxed = false;
  web_preferences.GetBoolean("sandbox", &sandboxed);
  return sandboxed;
}

bool WebContentsPreferences::UsesNativeWindowOpen(
    content::WebContents* web_contents) {
  WebContentsPreferences* self;
  if (!web_contents)
    return false;

  self = FromWebContents(web_contents);
  if (!self)
    return false;

  base::DictionaryValue& web_preferences = self->web_preferences_;
  bool use = false;
  web_preferences.GetBoolean("nativeWindowOpen", &use);
  return use;
}

// static
void WebContentsPreferences::OverrideWebkitPrefs(
    content::WebContents* web_contents, content::WebPreferences* prefs) {
  WebContentsPreferences* self = FromWebContents(web_contents);
  if (!self)
    return;

  bool b;
  if (self->web_preferences_.GetBoolean("javascript", &b))
    prefs->javascript_enabled = b;
  if (self->web_preferences_.GetBoolean("images", &b))
    prefs->images_enabled = b;
  if (self->web_preferences_.GetBoolean("textAreasAreResizable", &b))
    prefs->text_areas_are_resizable = b;
  if (self->web_preferences_.GetBoolean("webgl", &b))
    prefs->experimental_webgl_enabled = b;
  if (self->web_preferences_.GetBoolean("webSecurity", &b)) {
    prefs->web_security_enabled = b;
    prefs->allow_running_insecure_content = !b;
  }
  if (self->web_preferences_.GetBoolean("allowRunningInsecureContent", &b))
    prefs->allow_running_insecure_content = b;
  const base::DictionaryValue* fonts = nullptr;
  if (self->web_preferences_.GetDictionary("defaultFontFamily", &fonts)) {
    base::string16 font;
    if (fonts->GetString("standard", &font))
      prefs->standard_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("serif", &font))
      prefs->serif_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("sansSerif", &font))
      prefs->sans_serif_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("monospace", &font))
      prefs->fixed_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("cursive", &font))
      prefs->cursive_font_family_map[content::kCommonScript] = font;
    if (fonts->GetString("fantasy", &font))
      prefs->fantasy_font_family_map[content::kCommonScript] = font;
  }
  int size;
  if (self->GetInteger("defaultFontSize", &size))
    prefs->default_font_size = size;
  if (self->GetInteger("defaultMonospaceFontSize", &size))
    prefs->default_fixed_font_size = size;
  if (self->GetInteger("minimumFontSize", &size))
    prefs->minimum_font_size = size;
  std::string encoding;
  if (self->web_preferences_.GetString("defaultEncoding", &encoding))
    prefs->default_encoding = encoding;
}

bool WebContentsPreferences::GetInteger(const std::string& attributeName,
                                        int* intValue) {
  // if it is already an integer, no conversion needed
  if (web_preferences_.GetInteger(attributeName, intValue))
    return true;

  base::string16 stringValue;
  if (web_preferences_.GetString(attributeName, &stringValue))
    return base::StringToInt(stringValue, intValue);

  return false;
}

}  // namespace atom
<|endoftext|>


<commit_before>/*
**  Copyright (C) 2012 Aldebaran Robotics
**  See COPYING for the license
*/

// Disable "'this': used in base member initializer list"
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning(disable: 4355)
#endif

#ifdef ANDROID
#include <linux/in.h> // for  IPPROTO_TCP
#endif

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/make_shared.hpp>

#include "tcptransportsocket.hpp"

#include <qi/log.hpp>

qiLogCategory("qimessaging.transportsocket");

namespace qi
{
  TcpTransportSocket::TcpTransportSocket(EventLoop* eventLoop, bool ssl, void* s)
    : TransportSocket()
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
#endif
    , _abort(false)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
    _eventLoop = eventLoop;
    _err = 0;
    _status = qi::TransportSocket::Status_Disconnected;

    if (s != 0)
    {
#ifdef WITH_SSL
      _socket = SocketPtr((boost::asio::ssl::stream<boost::asio::ip::tcp::socket>*) s);
#else
      _socket = SocketPtr((boost::asio::ip::tcp::socket*) s);
#endif
      _status = qi::TransportSocket::Status_Connected;

      // Transmit each Message without delay
      const boost::asio::ip::tcp::no_delay option( true );
      _socket->lowest_layer().set_option(option);
    }
    else
    {
      _disconnectPromise.setValue(0); // not connected, so we are finished disconnecting
    }
  }

  TcpTransportSocket::~TcpTransportSocket()
  {
    qiLogDebug() << this;

    disconnect();
    delete _msg;
    qiLogVerbose() << "deleted " << this;
  }

  void TcpTransportSocket::startReading()
  {
    qiLogDebug() << this;

    _msg = new qi::Message();

    boost::mutex::scoped_lock(_closingMutex);

    if (_abort)
    {
      boost::system::error_code erc;
      error(erc);
      return;
    }

#ifdef WITH_SSL
    if (_ssl)
    {
      if (!_sslHandshake)
      {
        _socket->async_handshake(boost::asio::ssl::stream_base::server,
          boost::bind(&TcpTransportSocket::handshake, shared_from_this(), _1));
        return;
      }

      boost::asio::async_read(*_socket,
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
    }
    else
    {
      boost::asio::async_read(_socket->next_layer(),
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
    }
#else
    boost::asio::async_read(*_socket,
      boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
      boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
#endif
  }

  void TcpTransportSocket::onReadHeader(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
    size_t payload = _msg->_p->header.size;
    if (payload)
    {
      void* ptr = _msg->_p->buffer.reserve(payload);

      boost::mutex::scoped_lock(_closingMutex);

      if (_abort)
      {
        boost::system::error_code erc;
        error(erc);
        return;
      }

#ifdef WITH_SSL
      if (_ssl)
      {
        boost::asio::async_read(*_socket,
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
      }
      else
      {
        boost::asio::async_read(_socket->next_layer(),
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
      }
#else
      boost::asio::async_read(*_socket,
        boost::asio::buffer(ptr, payload),
        boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
#endif
    }
    else
      onReadData(boost::system::error_code(), 0);
  }

  void TcpTransportSocket::onReadData(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
      qiLogDebug() << this << " Recv (" << _msg->type() << "):" << _msg->address();
    static int usWarnThreshold = os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").empty()?0:strtol(os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").c_str(),0,0);
    qi::int64_t start = 0;
    if (usWarnThreshold)
      start = os::ustime(); // call might be not that cheap
    messageReady(*_msg);
    _dispatcher.dispatch(*_msg);
    if (usWarnThreshold)
    {
      qi::int64_t duration = os::ustime() - start;
      if (duration > usWarnThreshold)
        qiLogWarning() << "Dispatch to user took " << duration << "us";
    }
    delete _msg;
    startReading();
  }

  void TcpTransportSocket::error(const boost::system::error_code& erc)
  {
    boost::mutex::scoped_lock lock(_closingMutex);
    _abort = true;
    _status = qi::TransportSocket::Status_Disconnected;
    disconnected(erc.value());

    if (_connecting)
    {
      _connecting = false;
      _connectPromise.setError(std::string("Connection error: ") + erc.message());
    }

    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      boost::system::error_code er;
      if (_socket->lowest_layer().is_open())
        _socket->lowest_layer().close(er);
    }
    _socket.reset();
    _disconnectPromise.setValue(0);
  }

  qi::FutureSync<void> TcpTransportSocket::connect(const qi::Url &url)
  {
    boost::mutex::scoped_lock l(_closingMutex);

    if (_status == qi::TransportSocket::Status_Connected || _connecting)
    {
      const char* s = "connection already in progress";
      qiLogError() << s;
      return makeFutureError<void>(s);
    }
#ifdef WITH_SSL
    if (_ssl)
    {
      _sslContext.set_verify_mode(boost::asio::ssl::verify_none);
    }
    _socket = SocketPtr(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>((*(boost::asio::io_service*)_eventLoop->nativeHandle()), _sslContext));
#else
    _socket = SocketPtr(new boost::asio::ip::tcp::socket(*(boost::asio::io_service*)_eventLoop->nativeHandle()));
#endif
    _url = url;
    _connectPromise.reset();
    _disconnectPromise.reset();
    _status = qi::TransportSocket::Status_Connecting;
    _connecting = true;
    _err = 0;
    if (_url.port() == 0) {
      qiLogError() << "Error try to connect to a bad address: " << _url.str();
      _connectPromise.setError("Bad address " + _url.str());
      _status = qi::TransportSocket::Status_Disconnected;
      _connecting = false;
      _disconnectPromise.setValue(0);
      return _connectPromise.future();
    }
    qiLogVerbose() << "Trying to connect to " << _url.host() << ":" << _url.port();
    using namespace boost::asio;
    // Resolve url
    ip::tcp::resolver r(_socket->get_io_service());
    ip::tcp::resolver::query q(_url.host(), boost::lexical_cast<std::string>(_url.port()));
    // Synchronous resolution

    try
    {
      ip::tcp::resolver::iterator it = r.resolve(q);
      // asynchronous connect
      _socket->lowest_layer().async_connect(*it,
        boost::bind(&TcpTransportSocket::onConnected, shared_from_this(), _1));
      return _connectPromise.future();
    }
    catch (const std::exception& e)
    {
      const char* s = e.what();
      qiLogError() << s;
      _connectPromise.setError(s);
      return _connectPromise.future();
    }
  }

  void TcpTransportSocket::handshake(const boost::system::error_code& erc)
  {
    if (erc)
    {
      qiLogWarning() << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
      //FIXME: ?? _connectPromise.setError(erc.message());
    }
    else
    {
      _status = qi::TransportSocket::Status_Connected;
      _connectPromise.setValue(0);
      connected();
      _sslHandshake = true;

      {
        boost::mutex::scoped_lock l(_closingMutex);

        if (_abort)
        {
          return;
        }
        // Transmit each Message without delay
        const boost::asio::ip::tcp::no_delay option( true );
        _socket->lowest_layer().set_option(option);
      }

      startReading();
    }
  }

  void TcpTransportSocket::onConnected(const boost::system::error_code& erc)
  {
    _connecting = false;
    if (erc)
    {
      qiLogWarning("qimessaging.TransportSocketLibEvent") << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
    }
    else
    {
      if (_ssl)
      {
#ifdef WITH_SSL
        _socket->async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&TcpTransportSocket::handshake, shared_from_this(), _1));
#endif
      }
      else
      {
        _status = qi::TransportSocket::Status_Connected;
        _connectPromise.setValue(0);
        connected();

        {
          boost::mutex::scoped_lock l(_closingMutex);

          if (_abort)
          {
            return;
          }
          // Transmit each Message without delay
          const boost::asio::ip::tcp::no_delay option( true );
          _socket->lowest_layer().set_option(option);
        }

        startReading();
      }
    }
  }

  qi::FutureSync<void> TcpTransportSocket::disconnect()
  {
    {
      boost::mutex::scoped_lock(_closingMutex);
      _abort = true; // Notify send callback sendCont that it must silently terminate
    }
    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      if (_socket && _socket->lowest_layer().is_open())
      {
        boost::system::error_code erc;
        boost::mutex::scoped_lock(_closingMutex);

        _socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, erc);
        _socket->lowest_layer().close(erc); // will invoke read callback with error set
      }
      // Do not set disconnectPromise here, it will/has been set
      // by error(), called by read callback, and we must wait for it
      // to terminate.
    }
    return _disconnectPromise.future();
  }

  bool TcpTransportSocket::send(const qi::Message &msg)
  {
    // Check that once before locking in case some idiot tries to send
    // from a disconnect notification.
    if (_status != qi::TransportSocket::Status_Connected)
      return false;
    boost::mutex::scoped_lock lockc(_closingMutex);

    if (!_socket || _status != qi::TransportSocket::Status_Connected)
    {
      qiLogDebug() << this << "Send on closed socket";
      return false;
    }

    qiLogDebug() << this << " Send (" << msg.type() << "):" << msg.address();
    boost::mutex::scoped_lock lock(_sendQueueMutex);

    if (!_sending)
    {
      _sending = true;
      send_(msg);
    }
    else
      _sendQueue.push_back(msg);
    return true;
  }

  void TcpTransportSocket::send_(qi::Message msg)
  {
    using boost::asio::buffer;
    std::vector<boost::asio::const_buffer> b;
    msg._p->complete();
    // Send header
    b.push_back(buffer(msg._p->getHeader(), sizeof(qi::MessagePrivate::MessageHeader)));
    const qi::Buffer& buf = msg.buffer();
    size_t sz = buf.size();
    const std::vector<std::pair<size_t, Buffer> >& subs = buf.subBuffers();
    size_t pos = 0;
    // Handle subbuffers
    for (unsigned i=0; i< subs.size(); ++i)
    {
      // Send parent buffer between pos and start of sub
      size_t end = subs[i].first+4;
      if (end != pos)
        b.push_back(buffer((const char*)buf.data() + pos, end-pos));
      pos = end;
      // Send subbuffer
      b.push_back(buffer(subs[i].second.data(), subs[i].second.size()));
    }
    b.push_back(buffer((const char*)buf.data() + pos, sz - pos));

    boost::mutex::scoped_lock(_closingMutex);

    if (_abort)
    {
      qiLogWarning() << "send aborted";
      return;
    }

    _dispatcher.sent(msg);

#ifdef WITH_SSL
    if (_ssl)
    {
      boost::asio::async_write(*_socket, b,
        boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
    }
    else
    {
      boost::asio::async_write(_socket->next_layer(), b,
        boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
    }
#else
    boost::asio::async_write(*_socket, b,
      boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
#endif
  }

  /*
   * warning: msg is given to the callback so as not to drop buffers refcount
   */
  void TcpTransportSocket::sendCont(const boost::system::error_code& erc, qi::Message msg)
  {
    // The class does not wait for us to terminate, but it will set abort to true.
    // So do not use this before checking abort.
    if (erc || _abort)
      return; // read-callback will also get the error, avoid dup and ignore it
    boost::mutex::scoped_lock lock(_sendQueueMutex);
    if (_sendQueue.empty())
      _sending = false;
    else
    {
      qi::Message msg = _sendQueue.front();
      _sendQueue.pop_front();
      send_(msg);
    }
  }

}

<commit_msg>Force setting disconnection promise value upon disconnect()<commit_after>/*
**  Copyright (C) 2012 Aldebaran Robotics
**  See COPYING for the license
*/

// Disable "'this': used in base member initializer list"
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning(disable: 4355)
#endif

#ifdef ANDROID
#include <linux/in.h> // for  IPPROTO_TCP
#endif

#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/make_shared.hpp>

#include "tcptransportsocket.hpp"

#include <qi/log.hpp>

qiLogCategory("qimessaging.transportsocket");

namespace qi
{
  TcpTransportSocket::TcpTransportSocket(EventLoop* eventLoop, bool ssl, void* s)
    : TransportSocket()
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
#endif
    , _abort(false)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
    _eventLoop = eventLoop;
    _err = 0;
    _status = qi::TransportSocket::Status_Disconnected;

    if (s != 0)
    {
#ifdef WITH_SSL
      _socket = SocketPtr((boost::asio::ssl::stream<boost::asio::ip::tcp::socket>*) s);
#else
      _socket = SocketPtr((boost::asio::ip::tcp::socket*) s);
#endif
      _status = qi::TransportSocket::Status_Connected;

      // Transmit each Message without delay
      const boost::asio::ip::tcp::no_delay option( true );
      _socket->lowest_layer().set_option(option);
    }
    else
    {
      _disconnectPromise.setValue(0); // not connected, so we are finished disconnecting
    }
  }

  TcpTransportSocket::~TcpTransportSocket()
  {
    qiLogDebug() << this;

    disconnect();
    delete _msg;
    qiLogVerbose() << "deleted " << this;
  }

  void TcpTransportSocket::startReading()
  {
    qiLogDebug() << this;

    _msg = new qi::Message();

    boost::mutex::scoped_lock(_closingMutex);

    if (_abort)
    {
      boost::system::error_code erc;
      error(erc);
      return;
    }

#ifdef WITH_SSL
    if (_ssl)
    {
      if (!_sslHandshake)
      {
        _socket->async_handshake(boost::asio::ssl::stream_base::server,
          boost::bind(&TcpTransportSocket::handshake, shared_from_this(), _1));
        return;
      }

      boost::asio::async_read(*_socket,
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
    }
    else
    {
      boost::asio::async_read(_socket->next_layer(),
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
    }
#else
    boost::asio::async_read(*_socket,
      boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
      boost::bind(&TcpTransportSocket::onReadHeader, shared_from_this(), _1, _2));
#endif
  }

  void TcpTransportSocket::onReadHeader(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
    size_t payload = _msg->_p->header.size;
    if (payload)
    {
      void* ptr = _msg->_p->buffer.reserve(payload);

      boost::mutex::scoped_lock(_closingMutex);

      if (_abort)
      {
        boost::system::error_code erc;
        error(erc);
        return;
      }

#ifdef WITH_SSL
      if (_ssl)
      {
        boost::asio::async_read(*_socket,
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
      }
      else
      {
        boost::asio::async_read(_socket->next_layer(),
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
      }
#else
      boost::asio::async_read(*_socket,
        boost::asio::buffer(ptr, payload),
        boost::bind(&TcpTransportSocket::onReadData, shared_from_this(), _1, _2));
#endif
    }
    else
      onReadData(boost::system::error_code(), 0);
  }

  void TcpTransportSocket::onReadData(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
      qiLogDebug() << this << " Recv (" << _msg->type() << "):" << _msg->address();
    static int usWarnThreshold = os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").empty()?0:strtol(os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").c_str(),0,0);
    qi::int64_t start = 0;
    if (usWarnThreshold)
      start = os::ustime(); // call might be not that cheap
    messageReady(*_msg);
    _dispatcher.dispatch(*_msg);
    if (usWarnThreshold)
    {
      qi::int64_t duration = os::ustime() - start;
      if (duration > usWarnThreshold)
        qiLogWarning() << "Dispatch to user took " << duration << "us";
    }
    delete _msg;
    startReading();
  }

  void TcpTransportSocket::error(const boost::system::error_code& erc)
  {
    boost::mutex::scoped_lock lock(_closingMutex);
    _abort = true;
    _status = qi::TransportSocket::Status_Disconnected;
    disconnected(erc.value());

    if (_connecting)
    {
      _connecting = false;
      _connectPromise.setError(std::string("Connection error: ") + erc.message());
    }

    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      boost::system::error_code er;
      if (_socket && _socket->lowest_layer().is_open())
      {
        _socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, er);
        _socket->lowest_layer().close(er);

      }
    }
    _socket.reset();

    if (_disconnectPromise.future().isRunning())
    {
      _disconnectPromise.setValue(0);
    }
  }

  qi::FutureSync<void> TcpTransportSocket::connect(const qi::Url &url)
  {
    boost::mutex::scoped_lock l(_closingMutex);

    if (_status == qi::TransportSocket::Status_Connected || _connecting)
    {
      const char* s = "connection already in progress";
      qiLogError() << s;
      return makeFutureError<void>(s);
    }
#ifdef WITH_SSL
    if (_ssl)
    {
      _sslContext.set_verify_mode(boost::asio::ssl::verify_none);
    }
    _socket = SocketPtr(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>((*(boost::asio::io_service*)_eventLoop->nativeHandle()), _sslContext));
#else
    _socket = SocketPtr(new boost::asio::ip::tcp::socket(*(boost::asio::io_service*)_eventLoop->nativeHandle()));
#endif
    _url = url;
    _connectPromise.reset();
    _disconnectPromise.reset();
    _status = qi::TransportSocket::Status_Connecting;
    _connecting = true;
    _err = 0;
    if (_url.port() == 0) {
      qiLogError() << "Error try to connect to a bad address: " << _url.str();
      _connectPromise.setError("Bad address " + _url.str());
      _status = qi::TransportSocket::Status_Disconnected;
      _connecting = false;
      _disconnectPromise.setValue(0);
      return _connectPromise.future();
    }
    qiLogVerbose() << "Trying to connect to " << _url.host() << ":" << _url.port();
    using namespace boost::asio;
    // Resolve url
    ip::tcp::resolver r(_socket->get_io_service());
    ip::tcp::resolver::query q(_url.host(), boost::lexical_cast<std::string>(_url.port()));
    // Synchronous resolution

    try
    {
      ip::tcp::resolver::iterator it = r.resolve(q);
      // asynchronous connect
      _socket->lowest_layer().async_connect(*it,
        boost::bind(&TcpTransportSocket::onConnected, shared_from_this(), _1));
      return _connectPromise.future();
    }
    catch (const std::exception& e)
    {
      const char* s = e.what();
      qiLogError() << s;
      _connectPromise.setError(s);
      return _connectPromise.future();
    }
  }

  void TcpTransportSocket::handshake(const boost::system::error_code& erc)
  {
    if (erc)
    {
      qiLogWarning() << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
      //FIXME: ?? _connectPromise.setError(erc.message());
    }
    else
    {
      _status = qi::TransportSocket::Status_Connected;
      _connectPromise.setValue(0);
      connected();
      _sslHandshake = true;

      {
        boost::mutex::scoped_lock l(_closingMutex);

        if (_abort)
        {
          return;
        }
        // Transmit each Message without delay
        const boost::asio::ip::tcp::no_delay option( true );
        _socket->lowest_layer().set_option(option);
      }

      startReading();
    }
  }

  void TcpTransportSocket::onConnected(const boost::system::error_code& erc)
  {
    _connecting = false;
    if (erc)
    {
      qiLogWarning("qimessaging.TransportSocketLibEvent") << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
    }
    else
    {
      if (_ssl)
      {
#ifdef WITH_SSL
        _socket->async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&TcpTransportSocket::handshake, shared_from_this(), _1));
#endif
      }
      else
      {
        _status = qi::TransportSocket::Status_Connected;
        _connectPromise.setValue(0);
        connected();

        {
          boost::mutex::scoped_lock l(_closingMutex);

          if (_abort)
          {
            return;
          }
          // Transmit each Message without delay
          const boost::asio::ip::tcp::no_delay option( true );
          _socket->lowest_layer().set_option(option);
        }

        startReading();
      }
    }
  }

  qi::FutureSync<void> TcpTransportSocket::disconnect()
  {
    boost::system::error_code erc;
    error(erc);

    return _disconnectPromise.future();
  }

  bool TcpTransportSocket::send(const qi::Message &msg)
  {
    // Check that once before locking in case some idiot tries to send
    // from a disconnect notification.
    if (_status != qi::TransportSocket::Status_Connected)
      return false;
    boost::mutex::scoped_lock lockc(_closingMutex);

    if (!_socket || _status != qi::TransportSocket::Status_Connected)
    {
      qiLogDebug() << this << "Send on closed socket";
      return false;
    }

    qiLogDebug() << this << " Send (" << msg.type() << "):" << msg.address();
    boost::mutex::scoped_lock lock(_sendQueueMutex);

    if (!_sending)
    {
      _sending = true;
      send_(msg);
    }
    else
      _sendQueue.push_back(msg);
    return true;
  }

  void TcpTransportSocket::send_(qi::Message msg)
  {
    using boost::asio::buffer;
    std::vector<boost::asio::const_buffer> b;
    msg._p->complete();
    // Send header
    b.push_back(buffer(msg._p->getHeader(), sizeof(qi::MessagePrivate::MessageHeader)));
    const qi::Buffer& buf = msg.buffer();
    size_t sz = buf.size();
    const std::vector<std::pair<size_t, Buffer> >& subs = buf.subBuffers();
    size_t pos = 0;
    // Handle subbuffers
    for (unsigned i=0; i< subs.size(); ++i)
    {
      // Send parent buffer between pos and start of sub
      size_t end = subs[i].first+4;
      if (end != pos)
        b.push_back(buffer((const char*)buf.data() + pos, end-pos));
      pos = end;
      // Send subbuffer
      b.push_back(buffer(subs[i].second.data(), subs[i].second.size()));
    }
    b.push_back(buffer((const char*)buf.data() + pos, sz - pos));

    boost::mutex::scoped_lock(_closingMutex);

    if (_abort)
    {
      qiLogWarning() << "send aborted";
      return;
    }

    _dispatcher.sent(msg);

#ifdef WITH_SSL
    if (_ssl)
    {
      boost::asio::async_write(*_socket, b,
        boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
    }
    else
    {
      boost::asio::async_write(_socket->next_layer(), b,
        boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
    }
#else
    boost::asio::async_write(*_socket, b,
      boost::bind(&TcpTransportSocket::sendCont, shared_from_this(), _1, msg));
#endif
  }

  /*
   * warning: msg is given to the callback so as not to drop buffers refcount
   */
  void TcpTransportSocket::sendCont(const boost::system::error_code& erc, qi::Message msg)
  {
    // The class does not wait for us to terminate, but it will set abort to true.
    // So do not use this before checking abort.
    if (erc || _abort)
      return; // read-callback will also get the error, avoid dup and ignore it
    boost::mutex::scoped_lock lock(_sendQueueMutex);
    if (_sendQueue.empty())
      _sending = false;
    else
    {
      qi::Message msg = _sendQueue.front();
      _sendQueue.pop_front();
      send_(msg);
    }
  }

}

<|endoftext|>


<commit_before>/*
**  Copyright (C) 2012 Aldebaran Robotics
**  See COPYING for the license
*/

// Disable "'this': used in base member initializer list"
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning(disable: 4355)
#endif

#include <iostream>
#include <cstring>
#include <map>

#ifdef ANDROID
#include <linux/in.h> // for  IPPROTO_TCP
#endif

#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "tcptransportsocket.hpp"
#include "transportsocket_p.hpp"
#include "message_p.hpp"

#include <qi/log.hpp>
#include <qi/types.hpp>
#include <qimessaging/session.hpp>
#include <qimessaging/message.hpp>
#include <qi/buffer.hpp>
#include <qi/eventloop.hpp>

#define MAX_LINE 16384

qiLogCategory("qimessaging.transportsocket");

namespace qi
{
  TcpTransportSocket::TcpTransportSocket(EventLoop* eventLoop, bool ssl)
    : TransportSocketPrivate(this, eventLoop)
    , TransportSocket(this)
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
#endif
    , _readHdr(true)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
#ifdef WITH_SSL
    if (_ssl)
    {
      _sslContext.set_verify_mode(boost::asio::ssl::verify_none);
    }

    _socket = (new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>((*(boost::asio::io_service*)eventLoop->nativeHandle()), _sslContext));
#else
    _socket = new boost::asio::ip::tcp::socket(*(boost::asio::io_service*)eventLoop->nativeHandle());
#endif

    assert(eventLoop);
    _disconnectPromise.setValue(0); // not connected, so we are finished disconnecting
    _abort = boost::shared_ptr<bool>(new bool(false));
  }

  TcpTransportSocket::TcpTransportSocket(void* s, EventLoop* eventLoop, bool ssl)
    : TransportSocketPrivate(this, eventLoop)
    , TransportSocket(this)
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
    , _socket((boost::asio::ssl::stream<boost::asio::ip::tcp::socket>*) s)
#else
    , _socket((boost::asio::ip::tcp::socket*) s)
#endif
    , _readHdr(true)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
    assert(eventLoop);
    _status = qi::TransportSocket::Status_Connected;
    _abort = boost::shared_ptr<bool>(new bool(false));

    // Transmit each Message without delay
    const boost::asio::ip::tcp::no_delay option( true );
    _socket->set_option(option);
  }

  TcpTransportSocket::~TcpTransportSocket()
  {
    disconnect();
    delete _msg;
    delete _socket;
    _p = 0;
    qiLogVerbose() << "deleted " << this;
  }

  void TcpTransportSocket::startReading()
  {
    _msg = new qi::Message();

#ifdef WITH_SSL
    if (_ssl)
    {
      if (!_sslHandshake)
      {
        _socket->async_handshake(boost::asio::ssl::stream_base::server,
          boost::bind(&TcpTransportSocket::handshake, this, _1));
        return;
      }

      boost::asio::async_read(*_socket,
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
    }
    else
    {
      boost::asio::async_read(_socket->next_layer(),
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
    }
#else
    boost::asio::async_read(*_socket,
      boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
      boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
#endif
  }

  void TcpTransportSocket::onReadHeader(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
    size_t payload = _msg->_p->header.size;
    if (payload)
    {
      void* ptr = _msg->_p->buffer.reserve(payload);

#ifdef WITH_SSL
      if (_ssl)
      {
        boost::asio::async_read(*_socket,
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
      }
      else
      {
        boost::asio::async_read(_socket->next_layer(),
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
      }
#else
      boost::asio::async_read(*_socket,
        boost::asio::buffer(ptr, payload),
        boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
#endif
    }
    else
      onReadData(boost::system::error_code(), 0);
  }

  void TcpTransportSocket::onReadData(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
      qiLogDebug() << _self << " Recv (" << _msg->type() << "):" << _msg->address();
    static int usWarnThreshold = os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").empty()?0:strtol(os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").c_str(),0,0);
    qi::int64_t start = 0;
    if (usWarnThreshold)
      start = os::ustime(); // call might be not that cheap
    _self->messageReady(*_msg);
    _dispatcher.dispatch(*_msg);
    if (usWarnThreshold)
    {
      qi::int64_t duration = os::ustime() - start;
      if (duration > usWarnThreshold)
        qiLogWarning() << "Dispatch to user took " << duration << "us";
    }
    delete _msg;
    startReading();
  }

  void TcpTransportSocket::error(const boost::system::error_code& erc)
  {
    _status = qi::TransportSocket::Status_Disconnected;
    _self->disconnected(erc.value());

    if (_connecting)
    {
      _connecting = false;
      _connectPromise.setError(std::string("Connection error: ") + erc.message());
    }

    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      boost::system::error_code er;
      if (_socket->lowest_layer().is_open())
        _socket->lowest_layer().close(er);
    }

    _disconnectPromise.setValue(0);
  }

  qi::FutureSync<void> TcpTransportSocket::connect(const qi::Url &url)
  {
    if (_status == qi::TransportSocket::Status_Connected || _connecting)
    {
      const char* s = "connection already in progress";
      qiLogError() << s;
      return makeFutureError<void>(s);
    }
    _url = url;
    _connectPromise.reset();
    _disconnectPromise.reset();
    _status = qi::TransportSocket::Status_Connecting;
    _connecting = true;
    _err = 0;
    if (_url.port() == 0) {
      qiLogError() << "Error try to connect to a bad address: " << _url.str();
      _connectPromise.setError("Bad address " + _url.str());
      _status = qi::TransportSocket::Status_Disconnected;
      _connecting = false;
      _disconnectPromise.setValue(0);
      return _connectPromise.future();
    }
    qiLogVerbose() << "Trying to connect to " << _url.host() << ":" << _url.port();
    using namespace boost::asio;
    // Resolve url
    ip::tcp::resolver r(_socket->get_io_service());
    ip::tcp::resolver::query q(_url.host(), boost::lexical_cast<std::string>(_url.port()));
    // Synchronous resolution

    try
    {
      ip::tcp::resolver::iterator it = r.resolve(q);
      // asynchronous connect
      _socket->lowest_layer().async_connect(*it,
        boost::bind(&TcpTransportSocket::connected, this, _1));
      return _connectPromise.future();
    }
    catch (const std::exception& e)
    {
      const char* s = e.what();
      qiLogError() << s;
      _connectPromise.setError(s);
      return _connectPromise.future();
    }
  }

  void TcpTransportSocket::handshake(const boost::system::error_code& erc)
  {
    if (erc)
    {
      qiLogWarning() << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
      //FIXME: ?? _connectPromise.setError(erc.message());
    }
    else
    {
      _status = qi::TransportSocket::Status_Connected;
      _connectPromise.setValue(0);
      _self->connected();
      _sslHandshake = true;
      // Transmit each Message without delay
      const boost::asio::ip::tcp::no_delay option( true );
      _socket->set_option(option);
      startReading();
    }
  }

  void TcpTransportSocket::connected(const boost::system::error_code& erc)
  {
    _connecting = false;
    if (erc)
    {
      qiLogWarning("qimessaging.TransportSocketLibEvent") << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
    }
    else
    {
      if (_ssl)
      {
#ifdef WITH_SSL
        _socket->async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&TcpTransportSocket::handshake, this, _1));
#endif
      }
      else
      {
        _status = qi::TransportSocket::Status_Connected;
        _connectPromise.setValue(0);
        _self->connected();
        // Transmit each Message without delay
        const boost::asio::ip::tcp::no_delay option( true );
        _socket->set_option(option);
        startReading();
      }
    }
  }

  qi::FutureSync<void> TcpTransportSocket::disconnect()
  {
    *_abort = true; // Notify send callback sendCont that it must silently terminate
    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      if (_socket->lowest_layer().is_open())
      {
        boost::system::error_code erc;
        _socket->lowest_layer().close(erc); // will invoke read callback with error set
      }
      // Do not set disconnectPromise here, it will/has been set
      // by error(), called by read callback, and we must wait for it
      // to terminate.
    }
    return _disconnectPromise.future();
  }

  bool TcpTransportSocket::send(const qi::Message &msg)
  {
    boost::mutex::scoped_lock lock(_sendQueueMutex);
    if (!_sending)
    {
      _sending = true;
      send_(new Message(msg));
    }
    else
      _sendQueue.push_back(msg);
    return true;
  }

  void TcpTransportSocket::send_(qi::Message* msg)
  {
    using boost::asio::buffer;
    std::vector<boost::asio::const_buffer> b;
    msg->_p->complete();
    // Send header
    b.push_back(buffer(msg->_p->getHeader(), sizeof(qi::MessagePrivate::MessageHeader)));
    const qi::Buffer& buf = msg->buffer();
    size_t sz = buf.size();
    const std::vector<std::pair<size_t, Buffer> >& subs = buf.subBuffers();
    size_t pos = 0;
    // Handle subbuffers
    for (unsigned i=0; i< subs.size(); ++i)
    {
      // Send parent buffer between pos and start of sub
      size_t end = subs[i].first+4;
      if (end != pos)
        b.push_back(buffer((const char*)buf.data() + pos, end-pos));
      pos = end;
      // Send subbuffer
      b.push_back(buffer(subs[i].second.data(), subs[i].second.size()));
    }
    b.push_back(buffer((const char*)buf.data() + pos, sz - pos));
    _dispatcher.sent(*msg);

#ifdef WITH_SSL
    if (_ssl)
    {
      boost::asio::async_write(*_socket, b,
        boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
    }
    else
    {
      boost::asio::async_write(_socket->next_layer(), b,
        boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
    }
#else
    boost::asio::async_write(*_socket, b,
      boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
#endif
  }

  void TcpTransportSocket::sendCont(const boost::system::error_code& erc,
    Message* msg, boost::shared_ptr<bool> abort)
  {
    delete msg;
    // The class does not wait for us to terminate, but it will set abort to true.
    // So do not use this before checking abort.
    if (erc || *abort)
      return; // read-callback will also get the error, avoid dup and ignore it
    boost::mutex::scoped_lock lock(_sendQueueMutex);
    if (_sendQueue.empty())
      _sending = false;
    else
    {
      msg = new Message(_sendQueue.front());
      _sendQueue.pop_front();
      send_(msg);
    }
  }

}

<commit_msg>TransportSocket: debug log for message sent<commit_after>/*
**  Copyright (C) 2012 Aldebaran Robotics
**  See COPYING for the license
*/

// Disable "'this': used in base member initializer list"
#ifdef _MSC_VER
# pragma warning( push )
# pragma warning(disable: 4355)
#endif

#include <iostream>
#include <cstring>
#include <map>

#ifdef ANDROID
#include <linux/in.h> // for  IPPROTO_TCP
#endif

#include <boost/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include "tcptransportsocket.hpp"
#include "transportsocket_p.hpp"
#include "message_p.hpp"

#include <qi/log.hpp>
#include <qi/types.hpp>
#include <qimessaging/session.hpp>
#include <qimessaging/message.hpp>
#include <qi/buffer.hpp>
#include <qi/eventloop.hpp>

#define MAX_LINE 16384

qiLogCategory("qimessaging.transportsocket");

namespace qi
{
  TcpTransportSocket::TcpTransportSocket(EventLoop* eventLoop, bool ssl)
    : TransportSocketPrivate(this, eventLoop)
    , TransportSocket(this)
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
#endif
    , _readHdr(true)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
#ifdef WITH_SSL
    if (_ssl)
    {
      _sslContext.set_verify_mode(boost::asio::ssl::verify_none);
    }

    _socket = (new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>((*(boost::asio::io_service*)eventLoop->nativeHandle()), _sslContext));
#else
    _socket = new boost::asio::ip::tcp::socket(*(boost::asio::io_service*)eventLoop->nativeHandle());
#endif

    assert(eventLoop);
    _disconnectPromise.setValue(0); // not connected, so we are finished disconnecting
    _abort = boost::shared_ptr<bool>(new bool(false));
  }

  TcpTransportSocket::TcpTransportSocket(void* s, EventLoop* eventLoop, bool ssl)
    : TransportSocketPrivate(this, eventLoop)
    , TransportSocket(this)
    , _ssl(ssl)
    , _sslHandshake(false)
#ifdef WITH_SSL
    , _sslContext(boost::asio::ssl::context::sslv23)
    , _socket((boost::asio::ssl::stream<boost::asio::ip::tcp::socket>*) s)
#else
    , _socket((boost::asio::ip::tcp::socket*) s)
#endif
    , _readHdr(true)
    , _msg(0)
    , _connecting(false)
    , _sending(false)
  {
    assert(eventLoop);
    _status = qi::TransportSocket::Status_Connected;
    _abort = boost::shared_ptr<bool>(new bool(false));

    // Transmit each Message without delay
    const boost::asio::ip::tcp::no_delay option( true );
    _socket->set_option(option);
  }

  TcpTransportSocket::~TcpTransportSocket()
  {
    disconnect();
    delete _msg;
    delete _socket;
    _p = 0;
    qiLogVerbose() << "deleted " << this;
  }

  void TcpTransportSocket::startReading()
  {
    _msg = new qi::Message();

#ifdef WITH_SSL
    if (_ssl)
    {
      if (!_sslHandshake)
      {
        _socket->async_handshake(boost::asio::ssl::stream_base::server,
          boost::bind(&TcpTransportSocket::handshake, this, _1));
        return;
      }

      boost::asio::async_read(*_socket,
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
    }
    else
    {
      boost::asio::async_read(_socket->next_layer(),
        boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
        boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
    }
#else
    boost::asio::async_read(*_socket,
      boost::asio::buffer(_msg->_p->getHeader(), sizeof(MessagePrivate::MessageHeader)),
      boost::bind(&TcpTransportSocket::onReadHeader, this, _1, _2));
#endif
  }

  void TcpTransportSocket::onReadHeader(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
    size_t payload = _msg->_p->header.size;
    if (payload)
    {
      void* ptr = _msg->_p->buffer.reserve(payload);

#ifdef WITH_SSL
      if (_ssl)
      {
        boost::asio::async_read(*_socket,
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
      }
      else
      {
        boost::asio::async_read(_socket->next_layer(),
          boost::asio::buffer(ptr, payload),
          boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
      }
#else
      boost::asio::async_read(*_socket,
        boost::asio::buffer(ptr, payload),
        boost::bind(&TcpTransportSocket::onReadData, this, _1, _2));
#endif
    }
    else
      onReadData(boost::system::error_code(), 0);
  }

  void TcpTransportSocket::onReadData(const boost::system::error_code& erc,
    std::size_t len)
  {
    if (erc)
    {
      error(erc);
      return;
    }
      qiLogDebug() << _self << " Recv (" << _msg->type() << "):" << _msg->address();
    static int usWarnThreshold = os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").empty()?0:strtol(os::getenv("QIMESSAGING_SOCKET_DISPATCH_TIME_WARN_THRESHOLD").c_str(),0,0);
    qi::int64_t start = 0;
    if (usWarnThreshold)
      start = os::ustime(); // call might be not that cheap
    _self->messageReady(*_msg);
    _dispatcher.dispatch(*_msg);
    if (usWarnThreshold)
    {
      qi::int64_t duration = os::ustime() - start;
      if (duration > usWarnThreshold)
        qiLogWarning() << "Dispatch to user took " << duration << "us";
    }
    delete _msg;
    startReading();
  }

  void TcpTransportSocket::error(const boost::system::error_code& erc)
  {
    _status = qi::TransportSocket::Status_Disconnected;
    _self->disconnected(erc.value());

    if (_connecting)
    {
      _connecting = false;
      _connectPromise.setError(std::string("Connection error: ") + erc.message());
    }

    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      boost::system::error_code er;
      if (_socket->lowest_layer().is_open())
        _socket->lowest_layer().close(er);
    }

    _disconnectPromise.setValue(0);
  }

  qi::FutureSync<void> TcpTransportSocket::connect(const qi::Url &url)
  {
    if (_status == qi::TransportSocket::Status_Connected || _connecting)
    {
      const char* s = "connection already in progress";
      qiLogError() << s;
      return makeFutureError<void>(s);
    }
    _url = url;
    _connectPromise.reset();
    _disconnectPromise.reset();
    _status = qi::TransportSocket::Status_Connecting;
    _connecting = true;
    _err = 0;
    if (_url.port() == 0) {
      qiLogError() << "Error try to connect to a bad address: " << _url.str();
      _connectPromise.setError("Bad address " + _url.str());
      _status = qi::TransportSocket::Status_Disconnected;
      _connecting = false;
      _disconnectPromise.setValue(0);
      return _connectPromise.future();
    }
    qiLogVerbose() << "Trying to connect to " << _url.host() << ":" << _url.port();
    using namespace boost::asio;
    // Resolve url
    ip::tcp::resolver r(_socket->get_io_service());
    ip::tcp::resolver::query q(_url.host(), boost::lexical_cast<std::string>(_url.port()));
    // Synchronous resolution

    try
    {
      ip::tcp::resolver::iterator it = r.resolve(q);
      // asynchronous connect
      _socket->lowest_layer().async_connect(*it,
        boost::bind(&TcpTransportSocket::connected, this, _1));
      return _connectPromise.future();
    }
    catch (const std::exception& e)
    {
      const char* s = e.what();
      qiLogError() << s;
      _connectPromise.setError(s);
      return _connectPromise.future();
    }
  }

  void TcpTransportSocket::handshake(const boost::system::error_code& erc)
  {
    if (erc)
    {
      qiLogWarning() << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
      //FIXME: ?? _connectPromise.setError(erc.message());
    }
    else
    {
      _status = qi::TransportSocket::Status_Connected;
      _connectPromise.setValue(0);
      _self->connected();
      _sslHandshake = true;
      // Transmit each Message without delay
      const boost::asio::ip::tcp::no_delay option( true );
      _socket->set_option(option);
      startReading();
    }
  }

  void TcpTransportSocket::connected(const boost::system::error_code& erc)
  {
    _connecting = false;
    if (erc)
    {
      qiLogWarning("qimessaging.TransportSocketLibEvent") << "connect: " << erc.message();
      _status = qi::TransportSocket::Status_Disconnected;
      _connectPromise.setError(erc.message());
      _disconnectPromise.setValue(0);
    }
    else
    {
      if (_ssl)
      {
#ifdef WITH_SSL
        _socket->async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&TcpTransportSocket::handshake, this, _1));
#endif
      }
      else
      {
        _status = qi::TransportSocket::Status_Connected;
        _connectPromise.setValue(0);
        _self->connected();
        // Transmit each Message without delay
        const boost::asio::ip::tcp::no_delay option( true );
        _socket->set_option(option);
        startReading();
      }
    }
  }

  qi::FutureSync<void> TcpTransportSocket::disconnect()
  {
    *_abort = true; // Notify send callback sendCont that it must silently terminate
    {
      boost::mutex::scoped_lock l(_sendQueueMutex);
      if (_socket->lowest_layer().is_open())
      {
        boost::system::error_code erc;
        _socket->lowest_layer().close(erc); // will invoke read callback with error set
      }
      // Do not set disconnectPromise here, it will/has been set
      // by error(), called by read callback, and we must wait for it
      // to terminate.
    }
    return _disconnectPromise.future();
  }

  bool TcpTransportSocket::send(const qi::Message &msg)
  {
    qiLogDebug() << _self << " Send (" << msg.type() << "):" << msg.address();
    boost::mutex::scoped_lock lock(_sendQueueMutex);

    if (!_sending)
    {
      _sending = true;
      send_(new Message(msg));
    }
    else
      _sendQueue.push_back(msg);
    return true;
  }

  void TcpTransportSocket::send_(qi::Message* msg)
  {
    using boost::asio::buffer;
    std::vector<boost::asio::const_buffer> b;
    msg->_p->complete();
    // Send header
    b.push_back(buffer(msg->_p->getHeader(), sizeof(qi::MessagePrivate::MessageHeader)));
    const qi::Buffer& buf = msg->buffer();
    size_t sz = buf.size();
    const std::vector<std::pair<size_t, Buffer> >& subs = buf.subBuffers();
    size_t pos = 0;
    // Handle subbuffers
    for (unsigned i=0; i< subs.size(); ++i)
    {
      // Send parent buffer between pos and start of sub
      size_t end = subs[i].first+4;
      if (end != pos)
        b.push_back(buffer((const char*)buf.data() + pos, end-pos));
      pos = end;
      // Send subbuffer
      b.push_back(buffer(subs[i].second.data(), subs[i].second.size()));
    }
    b.push_back(buffer((const char*)buf.data() + pos, sz - pos));
    _dispatcher.sent(*msg);

#ifdef WITH_SSL
    if (_ssl)
    {
      boost::asio::async_write(*_socket, b,
        boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
    }
    else
    {
      boost::asio::async_write(_socket->next_layer(), b,
        boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
    }
#else
    boost::asio::async_write(*_socket, b,
      boost::bind(&TcpTransportSocket::sendCont, this, _1, msg, _abort));
#endif
  }

  void TcpTransportSocket::sendCont(const boost::system::error_code& erc,
    Message* msg, boost::shared_ptr<bool> abort)
  {
    delete msg;
    // The class does not wait for us to terminate, but it will set abort to true.
    // So do not use this before checking abort.
    if (erc || *abort)
      return; // read-callback will also get the error, avoid dup and ignore it
    boost::mutex::scoped_lock lock(_sendQueueMutex);
    if (_sendQueue.empty())
      _sending = false;
    else
    {
      msg = new Message(_sendQueue.front());
      _sendQueue.pop_front();
      send_(msg);
    }
  }

}

<|endoftext|>


<commit_before>// (c) Ivan Gagis
// e-mail: igagis@gmail.com

#include <exception>

#include <ting/debug.hpp>
#include <ting/Thread.hpp>
#include <ting/Socket.hpp>
#include <ting/utils.hpp>

#include "ServerThread.hpp"

using namespace cliser;

ServerThread::ServerThread(
		ting::u16 port,
		unsigned maxClientsPerThread,
		cliser::Listener* listener,
		bool disableNaggle,
		ting::u16 queueLength
	) :
		port(port),
		maxClientsPerThread(maxClientsPerThread),
		listener(listener),
		disableNaggle(disableNaggle),
		queueLength(queueLength)
{
	ASSERT(ting::SocketLib::IsCreated())

	++this->listener->numTimesAdded;
}

ServerThread::~ServerThread(){
	ASSERT(this->clientsThreads.size() == 0)

	--this->listener->numTimesAdded;
}

//override
void ServerThread::Run(){
//	TRACE(<<"Server::Run(): enter thread"<<std::endl)
	this->threadsKillerThread.Start();
//	TRACE(<<"Server::Run(): threads started"<<std::endl)

	//open listening socket
	ting::TCPServerSocket sock;
	sock.Open(this->port, this->disableNaggle, this->queueLength);

	ting::WaitSet waitSet(2);
	waitSet.Add(&sock, ting::Waitable::READ);
	waitSet.Add(&this->queue, ting::Waitable::READ);

	while(!this->quitFlag){
		waitSet.Wait();

		//TRACE(<<"C_TCPAcceptorThread::Run(): going to get message"<<std::endl)
		if(this->queue.CanRead()){
			while(ting::Ptr<ting::Message> m = this->queue.PeekMsg()){
				m->Handle();
			}
		}

		if(sock.CanRead()){
			ting::TCPSocket newSock;
			try{
				while((newSock = sock.Accept()).IsValid()){
					this->HandleNewConnection(newSock);
				}
			}catch(ting::Socket::Exc& e){
				ASSERT_INFO(false, "sock.Accept() failed")
			}
		}
		//TRACE(<<"C_TCPAcceptorThread::Run(): cycle"<<std::endl)
	}

	waitSet.Remove(&this->queue);
	waitSet.Remove(&sock);

//	TRACE(<< "ServerThread::" << __func__ << "(): quiting thread" << std::endl)

	this->threadsKillerThread.PushQuitMessage();

	//kill all client threads
	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
		(*i)->PushQuitMessage();
	}

	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
//		TRACE(<< "ServerThread::" << __func__ << "(): joining thread" << std::endl)
		(*i)->Join();
	}
//	TRACE(<< "ServerThread::" << __func__ << "(): connections threads joined" << std::endl)

	this->clientsThreads.clear();

//	TRACE(<< "ServerThread::" << __func__ << "(): waiting for killer thread to finish" << std::endl)

	this->threadsKillerThread.Join();

//	TRACE(<< "ServerThread::" << __func__ << "(): exit" << std::endl)
}

ServerThread::ServerConnectionsThread* ServerThread::GetNotFullThread(){
	//TODO: adjust threads order for faster search
	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
		if((*i)->numConnections < this->maxClientsPerThread)
			return (*i).operator->();
	}

	this->clientsThreads.push_back(ServerConnectionsThread::New(
			this,
			this->MaxClientsPerThread())
		);

//	TRACE(<< "ServerThread::" << __func__ << "(): num threads = " << this->clientsThreads.size() << std::endl)

	this->clientsThreads.back()->Start();//start new thread
	return this->clientsThreads.back().operator->();
}

void ServerThread::HandleNewConnection(ting::TCPSocket socket){
//	LOG(<< "ServerThread::" << __func__ << "(): enter" << std::endl)
//	TRACE(<< "ServerThread::" << __func__ << "(): enter" << std::endl)

	ASSERT(socket.IsValid())

	ServerConnectionsThread* thr;
	try{
		 thr = this->GetNotFullThread();
	}catch(std::exception& e){
//		TRACE_AND_LOG(<< "ServerThread::" << __func__ << "(): GetNotFullThread() failed: " << e.what() << std::endl)
		//failed getting not full thread, possibly maximum threads limit set by system reached
		//ignore connection
		socket.Close();
		return;
	}

	ASSERT(thr)

	ting::Ref<Connection> conn = ASS(this->listener)->CreateConnectionObject();

	//set client socket
	conn->socket = socket;
	ASSERT(conn->socket.IsValid())

	thr->PushMessage(
			ting::Ptr<ting::Message>(
					new ConnectionsThread::AddConnectionMessage(thr, conn)
				)
		);
	++thr->numConnections;
}

void ServerThread::HandleConnectionRemovedMessage(ServerThread::ServerConnectionsThread* cht){
//	TRACE(<< "ServerThread::" << __func__ << "(): enter" << std::endl)

	ASSERT(cht->numConnections > 0)
	--cht->numConnections;

	if(cht->numConnections > 0)
		return;

	//if we get here then numClients is 0, remove the thread then:
	//find it in the threads list and push to ThreadKillerThread

	for(ServerThread::T_ThrIter i = this->clientsThreads.begin();
			i != this->clientsThreads.end();
			++i
		)
	{
		if((*i) == cht){
			//schedule thread for termination
			this->threadsKillerThread.PushMessage(
					ting::Ptr<ting::Message>(
							new ThreadsKillerThread::KillThreadMessage(
									&this->threadsKillerThread,
									(*i)
								)
						)
				);
			//remove thread from threads list
			this->clientsThreads.erase(i);
			return;
		}//~if
	}//~for
	ASSERT(false)
}

void ServerThread::ThreadsKillerThread::Run(){
	while(!this->quitFlag){
//		TRACE(<< "ThreadsKillerThread::" << __func__ << "(): going to get message" << std::endl)
		this->queue.GetMsg()->Handle();
//		TRACE(<< "ThreadsKillerThread::" << __func__ << "(): message handled, qf = " << this->quitFlag << std::endl)
	}
//	TRACE(<< "ThreadsKillerThread::" << __func__ << "(): exit" << std::endl)
}

//override
void ServerThread::ThreadsKillerThread::KillThreadMessage::Handle(){
//	TRACE(<< "KillThreadMessage::" << __func__ << "(): enter" << std::endl)
	this->thr->Join();//wait for thread finish
//	TRACE(<< "KillThreadMessage::" << __func__ << "(): exit" << std::endl)
}
<commit_msg>do not handle messages in while loop in server thread<commit_after>// (c) Ivan Gagis
// e-mail: igagis@gmail.com

#include <exception>

#include <ting/debug.hpp>
#include <ting/Thread.hpp>
#include <ting/Socket.hpp>
#include <ting/utils.hpp>

#include "ServerThread.hpp"

using namespace cliser;

ServerThread::ServerThread(
		ting::u16 port,
		unsigned maxClientsPerThread,
		cliser::Listener* listener,
		bool disableNaggle,
		ting::u16 queueLength
	) :
		port(port),
		maxClientsPerThread(maxClientsPerThread),
		listener(listener),
		disableNaggle(disableNaggle),
		queueLength(queueLength)
{
	ASSERT(ting::SocketLib::IsCreated())

	++this->listener->numTimesAdded;
}

ServerThread::~ServerThread(){
	ASSERT(this->clientsThreads.size() == 0)

	--this->listener->numTimesAdded;
}

//override
void ServerThread::Run(){
//	TRACE(<<"Server::Run(): enter thread"<<std::endl)
	this->threadsKillerThread.Start();
//	TRACE(<<"Server::Run(): threads started"<<std::endl)

	//open listening socket
	ting::TCPServerSocket sock;
	sock.Open(this->port, this->disableNaggle, this->queueLength);

	ting::WaitSet waitSet(2);
	waitSet.Add(&sock, ting::Waitable::READ);
	waitSet.Add(&this->queue, ting::Waitable::READ);

	while(!this->quitFlag){
		waitSet.Wait();

		//TRACE(<<"C_TCPAcceptorThread::Run(): going to get message"<<std::endl)
		if(this->queue.CanRead()){
			if(ting::Ptr<ting::Message> m = this->queue.PeekMsg()){
				m->Handle();
			}
		}

		if(sock.CanRead()){
			ting::TCPSocket newSock;
			try{
				if((newSock = sock.Accept()).IsValid()){
					this->HandleNewConnection(newSock);
				}
			}catch(ting::Socket::Exc& e){
				ASSERT_INFO(false, "sock.Accept() failed")
			}
		}
		//TRACE(<<"C_TCPAcceptorThread::Run(): cycle"<<std::endl)
	}

	waitSet.Remove(&this->queue);
	waitSet.Remove(&sock);

//	TRACE(<< "ServerThread::" << __func__ << "(): quiting thread" << std::endl)

	this->threadsKillerThread.PushQuitMessage();

	//kill all client threads
	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
		(*i)->PushQuitMessage();
	}

	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
//		TRACE(<< "ServerThread::" << __func__ << "(): joining thread" << std::endl)
		(*i)->Join();
	}
//	TRACE(<< "ServerThread::" << __func__ << "(): connections threads joined" << std::endl)

	this->clientsThreads.clear();

//	TRACE(<< "ServerThread::" << __func__ << "(): waiting for killer thread to finish" << std::endl)

	this->threadsKillerThread.Join();

//	TRACE(<< "ServerThread::" << __func__ << "(): exit" << std::endl)
}

ServerThread::ServerConnectionsThread* ServerThread::GetNotFullThread(){
	//TODO: adjust threads order for faster search
	for(T_ThrIter i = this->clientsThreads.begin(); i != this->clientsThreads.end(); ++i){
		if((*i)->numConnections < this->maxClientsPerThread)
			return (*i).operator->();
	}

	this->clientsThreads.push_back(ServerConnectionsThread::New(
			this,
			this->MaxClientsPerThread())
		);

//	TRACE(<< "ServerThread::" << __func__ << "(): num threads = " << this->clientsThreads.size() << std::endl)

	this->clientsThreads.back()->Start();//start new thread
	return this->clientsThreads.back().operator->();
}

void ServerThread::HandleNewConnection(ting::TCPSocket socket){
//	LOG(<< "ServerThread::" << __func__ << "(): enter" << std::endl)
//	TRACE(<< "ServerThread::" << __func__ << "(): enter" << std::endl)

	ASSERT(socket.IsValid())

	ServerConnectionsThread* thr;
	try{
		 thr = this->GetNotFullThread();
	}catch(std::exception& e){
//		TRACE_AND_LOG(<< "ServerThread::" << __func__ << "(): GetNotFullThread() failed: " << e.what() << std::endl)
		//failed getting not full thread, possibly maximum threads limit set by system reached
		//ignore connection
		socket.Close();
		return;
	}

	ASSERT(thr)

	ting::Ref<Connection> conn = ASS(this->listener)->CreateConnectionObject();

	//set client socket
	conn->socket = socket;
	ASSERT(conn->socket.IsValid())

	thr->PushMessage(
			ting::Ptr<ting::Message>(
					new ConnectionsThread::AddConnectionMessage(thr, conn)
				)
		);
	++thr->numConnections;
}

void ServerThread::HandleConnectionRemovedMessage(ServerThread::ServerConnectionsThread* cht){
//	TRACE(<< "ServerThread::" << __func__ << "(): enter" << std::endl)

	ASSERT(cht->numConnections > 0)
	--cht->numConnections;

	if(cht->numConnections > 0)
		return;

	//if we get here then numClients is 0, remove the thread then:
	//find it in the threads list and push to ThreadKillerThread

	for(ServerThread::T_ThrIter i = this->clientsThreads.begin();
			i != this->clientsThreads.end();
			++i
		)
	{
		if((*i) == cht){
			//schedule thread for termination
			this->threadsKillerThread.PushMessage(
					ting::Ptr<ting::Message>(
							new ThreadsKillerThread::KillThreadMessage(
									&this->threadsKillerThread,
									(*i)
								)
						)
				);
			//remove thread from threads list
			this->clientsThreads.erase(i);
			return;
		}//~if
	}//~for
	ASSERT(false)
}

void ServerThread::ThreadsKillerThread::Run(){
	while(!this->quitFlag){
//		TRACE(<< "ThreadsKillerThread::" << __func__ << "(): going to get message" << std::endl)
		this->queue.GetMsg()->Handle();
//		TRACE(<< "ThreadsKillerThread::" << __func__ << "(): message handled, qf = " << this->quitFlag << std::endl)
	}
//	TRACE(<< "ThreadsKillerThread::" << __func__ << "(): exit" << std::endl)
}

//override
void ServerThread::ThreadsKillerThread::KillThreadMessage::Handle(){
//	TRACE(<< "KillThreadMessage::" << __func__ << "(): enter" << std::endl)
	this->thr->Join();//wait for thread finish
//	TRACE(<< "KillThreadMessage::" << __func__ << "(): exit" << std::endl)
}
<|endoftext|>