Поделиться через


C++

Применение C++ REST SDK в приложениях Windows Store

Сридхар Подури

Исходный код можно скачать по ссылке.

Продукты и технологии:
C++ REST SDK, OAuth
В статье рассматриваются:

  • предыдущий пример Win32-приложения;
  • варианты интеграции с Windows Runtime;
  • использование класса WebAuthenticationBroker;
  • создание цепочки асинхронных веб-запросов.

В предыдущей статье (msdn.microsoft.com/magazine/dn342869) я познакомил вас с C++ REST SDK и тем, как его можно использовать в Win32/MFC-приложениях. В этой статье мы обсудим, как C++ REST SDK можно интегрировать в приложения Windows Store. Одной из моих исходных целей в применении C++ REST SDK и класса аутентификации на основе OAuth было максимально полное использование стандартного C++ и лишь при необходимости взаимодействие со специфичными для платформы API. Кратко напомню суть предыдущей статьи.

  1. Код в классе аутентификации на основе OAuth использует только стандартные C++-типы — без специфичных для Windows типов.
  2. Код для выдачи веб-запросов к REST-сервису Dropbox использует типы из C++ REST SDK.
  3. Единственный специфичный для платформы код — это функция, которая запускает Internet Explorer, выполняет аутентификацию приложения и получает одобрение от портала Dropbox.

Я поставил те же цели в своем приложении Windows Store для поддержки аутентификации и загрузки файла в Dropbox. Я стремился написать максимально больше портируемого кода на C++ и взаимодействовать с Windows Runtime (WinRT) только при необходимости.

Проблемы с Win32-решением

Одним из крупных недостатков в предыдущем Win32-приложении была необходимость запуска внешнего приложения для выполнения процесса авторизации на основе OAuth. Это означало, что я должен был запускать Internet Explorer (можно было бы запускать и любой другой браузер), входить в Dropbox по своим удостоверениям, а затем выполнять необходимый рабочий процесс. Он проиллюстрирован на рис. 1 и 2.

Вход в Dropbox по моим удостоверениям до авторизации доступа приложения
Рис. 1. Вход в Dropbox по моим удостоверениям до авторизации доступа приложения

Успешная авторизация моего приложения на портале Dropbox
Рис. 2. Успешная авторизация моего приложения на портале Dropbox

Как видите, запуск внешнего приложения и предложение пользователям пройти рабочий процесс через внешнее приложение уводит фокус ввода из окна моего приложения. Как разработчик я также лишен стандартного механизма, через который мое приложение могло бы уведомляться о завершении этого рабочего процесса. При концентрации на асинхронном программировании и использовании C++ REST SDK, предназначенного для поддержки программирования на основе асинхронных задач, вынужденный запуск внешнего приложения со всей очевидностью является для меня неприятной ситуацией. Я исследовал подходы с применением именованных каналов (named pipes), проецируемых в память файлов (memory-mapped files) и т. д., но все эти подходы требуют создания другого приложения для хостинга экземпляра элемента управления «веб-браузер» и последующей записи значения, свидетельствующего об успехе, обратно через именованный канал, общую память или проецируемый в память файл. В итоге я остановился на использовании браузера для выполнения этой задачи, так как не хотел писать другую программу, которая обертывала бы элемент управления «веб-браузер».

Одним из крупных недостатков в предыдущем Win32-приложении была необходимость запуска внешнего приложения для выполнения процесса авторизации на основе OAuth.

Интеграция с Windows Runtime

Начав проектировать свое приложение под Windows Runtime, я рассмотрел несколько вариантов. Я вкратце расскажу о них здесь, а потом мы подробно обсудим выбранный мной подход.

  1. Используем активацию протокола и даем системе запустить подходящий процесс для обработки этого протокола, вызвав функцию Windows::System::Launcher::LaunchUriAsync. Это означает, что для URI на основе HTTPS операционная система запустит браузер по умолчанию. Это аналогично запуску Internet Explorer из Win32-примера, но с «двойной комиссией»: мое приложение Windows Store станет фоновым процессом, браузер по умолчанию запустится в полноэкранном режиме, и в самом худшем случае мое приложение будет приостановлено на время выполнения пользователем рабочего процесса. Совершенно не годится!
  2. Интегрируем в приложение элемент управления WebView. Использование XAML-элемента WebView позволяет встроить всю навигацию по рабочему процессу в контекст моего приложения. Теоретически, я также могу получать уведомления о завершении процесса, слушая событие window.external.notify, генерируемое элементом управления WebView. Однако на практике это событие генерируется, только если веб-страница генерирует событие уведомления. В моем случае страница Dropbox, где выполняется процесс авторизации, такое событие не генерирует. Неприятная ситуация!
  3. Используем WebAuthenticationBroker в моем приложении. Продолжая копаться в Windows Runtime, я случайно наткнулся на класс WebAuthenticationBroker. Судя по всему, он мог бы помочь мне в выполнении процесса авторизации, и, действительно, я сумел создать всю необходимую функциональность на его основе. Прежде чем перейти к рассмотрению кода, позвольте мне пояснить некоторые детали, касающиеся WebAuthenticationBroker.

WebAuthenticationBroker

В мире подключенных приложений, чтобы получить согласие и одобрение пользователя, важно запрашивать его удостоверения через безопасный и доверяемый механизм. Никто не хочет быть разработчиком, чьи приложения допускают утечку удостоверений пользователя или оказываются уязвимыми к скрытым атакам с целью похищения информации о пользователе. Windows Runtime включает ряд API и необходимые технологии, позволяющие разработчику безопасно передавать удостоверения пользователя. WebAuthenticationBroker — одно из таких средств, которое дает возможность приложениям Windows Store использовать протоколы аутентификации и авторизации через Интернет, такие как OAuth и OpenID. Как же это работает в моем приложении-примере для Dropbox?

  1. Я выдаю начальный асинхронный запрос к Dropbox, который возвращает маркер и секрет для моего приложения. Этот начальный запрос передается через функцию oAuthLoginAsync.
  2. Как только функция oAuthLoginAsync возвращает управление, я конструирую в продолжении последовательности URI, где должен начаться процесс авторизации. В своем примере я определил начальный URI как строковую константу:
const std::wstring DropBoxAuthorizeURI = 
  L"https://www.dropbox.com/1/oauth/authorize?oauth_token=";
  1. Затем я формирую URI HTTP-запроса, дописывая маркер, возвращенный Dropbox.
  2. В качестве дополнительного шага я конструирую параметр с URI обратного вызова, обращаясь к функции WebAuthenticationBroker::GetCurrentApplicationCallbackUri. Заметьте, что я не использовал URI обратного вызова в своем настольном приложении, так как этот параметр не обязателен и я полагался на Internet Explorer в выполнении задачи авторизации.
  3. Теперь строка запроса готова, и я могу выдать запрос. Вместо использования класса http_client или интерфейса IHttpWebRequest2 из C++ REST SDK для вызовов веб-сервиса я вызываю функцию WebAuthenticationBroker::AuthenticateAsync.
  4. Функция WebAuthenticationBroker::AuthenticateAsync принимает два параметра: перечисление WebAuthenticationOptions и URI. Перегруженный экземпляр той же функции принимает перечисление WebAuthenticationOptions и два URI, по одному из которых начинается процесс аутентификации, а по другому — заканчивается.
  5. Я использую первую версию функции AuthenticateAsync и передаю значение None для перечисления WebAuthenticationOptions, а также URI, сформированный для моего веб-запроса.
  6. WebAuthenticationBroker размещается между моим приложением и системой. В точке, где я вызываю AuthenticateAsync, он создает системный модальный диалог, который является модальным для моего приложения.
  7. Брокер подключает окно веб-хоста к созданному им модальному диалоговому окну.
  8. Затем брокер выбирает выделенный процесс контейнера приложений, отделенный от контейнера, в котором выполняется мое приложение. Это также приводит к очистке любых сохраненных данных в моем приложении.
  9. Далее брокер начинает процесс аутентификации в этом только что выбранном контейнере приложения и переходит к URI, указанному функцией AuthenticateAsync.
  10. Когда пользователи взаимодействуют с веб-страницами, брокер проверяет каждый URL для указанного URI обратного вызова.
  11. Как только обнаруживается совпадение, веб-хост прекращает навигацию и посылает брокеру сигнал. Брокер убирает диалоговое окно, очищает любые сохраненные файлы cookie, созданные веб-хостом, из контейнера приложения и возвращает данные протокола обратно приложению.

В мире подключенных приложений, чтобы получить согласие и одобрение пользователя, важно запрашивать его удостоверения через безопасный и доверяемый механизм.

Рис. 3 иллюстрирует модальный диалог WebAuthenticationBroker в приложении-примере Dropbox после того, как веб-хост перешел на начальный URI. Поскольку Dropbox ожидает входа пользователей до появления страницы авторизации, веб-хост перенаправляет процесс навигации на страницу входа в Dropbox.

Страница входа в Dropbox, показываемая в модальном диалоге
Рис. 3. Страница входа в Dropbox, показываемая в модальном диалоге

Как только пользователь вошел в Dropbox, веб-хост переходит к URI авторизации. Это отражено на рис. 4. Из рис. 3 и 4 понятно, что диалог размещается поверх UI моего приложения. UI также остается согласованным безотносительно приложения-источника, вызывающего метод WebAuthenticationBroker::AuthenticateAsync. Поскольку вся пользовательская среда сохраняет согласованность, пользователи могут предоставлять удостоверения, не беспокоясь о приложениях, обрабатывающих эту информацию, и о ее случайной утечке.

Dropbox запрашивает согласие пользователя на авторизацию приложения
Рис. 4. Dropbox запрашивает согласие пользователя на авторизацию приложения

Одна важная вещь, о которой я не упомянул, — необходимость вызова функции WebAuthenticationBroker::AuthenticateAsync из UI-потока. Все веб-запросы в C++ REST SDK выдаются в фоновом потоке, а вывести UI из фонового потока нельзя. Поэтому я использую системный диспетчер и вызываю его функцию-член RunAsync для отображения модального UI (рис. 5).

Рис. 5. Использование системного диспетчера для отображения модального UI

auto action = m_dispatcher->RunAsync(
  Windows::UI::Core::CoreDispatcherPriority::Normal,
  ref new Windows::UI::Core::DispatchedHandler([this]()
  {
    auto beginUri = ref new Uri(ref new String(m_authurl.c_str()));
    task<WebAuthenticationResult^> authTask(WebAuthenticationBroker::
      AuthenticateAsync(WebAuthenticationOptions::None, beginUri));
      authTask.then([this](WebAuthenticationResult^ result)
      {
        String^ statusString;
        switch(result->ResponseStatus)
        {
          case WebAuthenticationStatus::Success:
          {
            auto actionEnable = m_dispatcher->RunAsync(
              Windows::UI::Core::CoreDispatcherPriority::Normal,
              ref new Windows::UI::Core::DispatchedHandler([this]()
              {
                UploadFileBtn->IsEnabled = true;
              }));
          }
        }
      });
}));

Как только пользователь вошел в Dropbox, веб-хост переходит к URI авторизации.

По окончании процесса авторизации я снова запускаю диспетчер, чтобы сделать доступной кнопку Upload File в основном UI. Эта кнопка остается недоступной, пока пользователи не аутентифицировали и не авторизовали мое приложение для доступа к Dropbox.

Создание цепочки асинхронных веб-запросов

Теперь легко свести все воедино. Во всех функциях, не взаимодействующих с Windows Runtime, я повторно использовал код из моего настольного приложения. Крупных изменений в коде нет, кроме одного: в функции UploadFileToDropboxAsync вместо C++ iostream используется WinRT-объект StorageFile.

При написании приложений Windows Store приходится учитывать некоторые ограничения, с которыми вы должны смириться. Одно из них — необходимость использования WinRT-объектов StorageFile вместо C++-потоков (streams) для чтения и записи данных в файлы. При разработке приложения Windows Store с применением C++ REST SDK все операции, связанные с файлами, ожидают передачи объекта StorageFile, а не C++-объекта потока данных. Внеся это небольшое изменение, я смог повторно использовать весь свой стандартный C++-код, поддерживающий код OAuth-авторизации и Dropbox, в приложении-примере для Windows Store.

Вот как выглядит соответствующий псевдокод (индивидуальные функции мы обсудим после этого псевдокода):

При щелчке кнопки SignIn
  Вызов функции oAuthLoginAsync
    Затем вызов WebAuthenticationBroker::AuthenticateAsync
    Затем делаем доступной кнопку Upload File в UI
При щелчке кнопки Upload File
   Вызов функции Windows::Storage::Pickers::FileOpenPicker::
     PickSingleFileAsync
    Затем вызов функции oAuthAcquireTokenAsync
    Затем вызов функции UploadFileToDropboxAsync

В обработчике событий кнопки SignInBtnClicked, показанном на рис. 6, я сначала выполняю простую проверку параметров, чтобы удостовериться, что в параметрах ConsumerKey и ConsumerSecret не передаются пустые строки, отправляемые потом в Dropbox для аутентификации. Затем я получаю экземпляр объекта Dispatcher, сопоставленного с текущим потоком CoreWindow, и сохраняю его как переменную-член класса MainPage. Dispatcher отвечает за обработку оконных сообщений и диспетчеризацию событий для приложения. Далее я создаю экземпляр класса OnlineIdAuthenticator. Этот класс содержит вспомогательные функции, которые позволяют мне выводить модальное диалоговое окно приложения и выполнять защищенный рабочий процесс авторизации. Это избавляет от необходимости запуска экземпляра браузера и перевода фокуса ввода с приложения в браузер.

Рис. 6. Функция SignInBtnClicked

void MainPage::SignInBtnClicked(Platform::Object^ sender, 
  RoutedEventArgs^ e)
{
  if ((ConsumerKey->Text == nullptr) || 
    (ConsumerSecret->Text == nullptr))
  {
    using namespace Windows::UI::Popups;
    auto msgDlg = ref new MessageDialog(
      "Please check the input for the Consumer Key and/or Consumer Secret tokens");
    msgDlg->ShowAsync();
  }
  m_dispatcher =
     Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher;
  m_creds = std::make_shared<AppCredentials>();
  m_authenticator = ref new OnlineIdAuthenticator();
  consumerKey = ConsumerKey->Text->Data();
  consumerSecret = ConsumerSecret->Text->Data();
  ConsumerKey->Text = nullptr;
  ConsumerSecret->Text = nullptr;
  OAuthLoginAsync(m_creds).then([this]
  {          
    m_authurl = DropBoxAuthorizeURI;               
    m_authurl += 
      utility::conversions::to_string_t(this->m_creds->Token());
    m_authurl += L"&oauth_callback=";
    m_authurl += WebAuthenticationBroker::
      GetCurrentApplicationCallbackUri()->AbsoluteUri->Data();
    auto action = m_dispatcher->RunAsync(
      Windows::UI::Core::CoreDispatcherPriority::Normal,
      ref new Windows::UI::Core::DispatchedHandler([this]()
    {
      auto beginUri = ref new Uri(ref new String(m_authurl.c_str()));
      task<WebAuthenticationResult^>authTask(
        WebAuthenticationBroker::AuthenticateAsync(
        WebAuthenticationOptions::None, beginUri));
      authTask.then([this](WebAuthenticationResult^ result)
      {
        String^ statusString;
        switch(result->ResponseStatus)
        {
          case WebAuthenticationStatus::Success:
          {
            auto actionEnable = m_dispatcher->RunAsync(
              Windows::UI::Core::CoreDispatcherPriority::Normal,
              ref new Windows::UI::Core::DispatchedHandler([this]()
              {
                UploadFileBtn->IsEnabled = true;
              }));
          }
        }
      });
    }));
}

Затем я вызываю функцию OAuthLoginAsync, которая выполняет операцию входа в Dropbox. Как только эта асинхронная функция возвращает управление, я использую функцию RunAsync объекта Dispatcher для маршалинга вызова обратно в UI-поток из фонового потока асинхронной задачи. Функция RunAsync принимает два параметра: значение приоритета и экземпляр DispatchedHandler. Я задаю приоритет как «Normal» и передаю функцию лямбды экземпляру DispatchedHandler. В теле лямбды я вызываю статическую функцию AuthenticateAsync класса WebAuthenticationBroker, которая потом отображает модальный диалог приложения и помогает выполнить защищенную аутентификацию.

При написании приложений Windows Store приходится учитывать некоторые ограничения, с которыми вы должны смириться.

По окончании рабочего процесса диалог удаляется, и функция возвращает либо код успешного завершения, либо обнаруженные ошибки. В моем случае я просто обрабатываю возвращаемый тип WebAuthenticationStatus::Success и снова использую объект диспетчера, чтобы сделать доступной кнопку UploadFile в UI. Поскольку все вызываемые мной функции являются асинхронными, мне нужно задействовать объект диспетчера для маршалинга вызовов в UI-поток, если я хочу обращаться к каким-либо UI-элементам.

Обработчик событий UploadFileBtnClicked показан на рис. 7. В самом обработчике кода не так уж много. Я вызываю функцию FileOpenPicker::PickSingleFileAsync, которая позволяет выбрать один текстовый файл через интерфейс выбора (picker interface). Затем вызываю функцию OAuthAcquireTokenAsync (рис. 8) и при успешном завершении обращаюсь к функции UploadFileToDropBoxAsync (рис. 9).

Рис. 7. Функция UploadFileBtnClicked

void MainPage::UploadFileBtnClicked(  Platform::Object^ sender, 
  RoutedEventArgs^ e)
{
  using namespace Windows::Storage::Pickers;
  using namespace Windows::Storage;
  auto picker = ref new FileOpenPicker();
  picker->SuggestedStartLocation = PickerLocationId::DocumentsLibrary;
  picker->FileTypeFilter->Append(".txt");
  task<StorageFile^> (picker->PickSingleFileAsync())
    .then([this](StorageFile^ selectedFile)
  {
    m_fileToUpload = selectedFile;
    OAuthAcquireTokenAsync(m_creds).then([this](){
      UploadFileToDropBoxAsync(m_creds);
    });
  });         
}

Рис. 8. Функция OAuthAcquireTokenAsync

task<void> MainPage::OAuthAcquireTokenAsync(
  std::shared_ptr<AppCredentials>& creds)
{
  uri url(DropBoxAccessTokenURI);
  std::shared_ptr<OAuth> oAuthObj = std::make_shared<OAuth>();
  auto signatureParams =
    oAuthObj->CreateOAuthSignedParameters(url.to_string(),
    L"GET",
    NULL,
    consumerKey,
    consumerSecret,
    creds->Token(),
    creds->TokenSecret()
    );
  std::wstring sb = oAuthObj->OAuthBuildSignedHeaders(url);
  http_client client(sb);   
  // Выдаем запрос и асинхронно обрабатываем ответ
  return client.request(methods::GET)
    .then([&creds](http_response response)
  {
    if(response.status_code() != status_codes::OK)
    {
      auto stream = response.body();                    
      container_buffer<std::string> inStringBuffer;
      return stream.read_to_end(inStringBuffer)
        .then([inStringBuffer](pplx::task<size_t> previousTask)
      {
        UNREFERENCED_PARAMETER(previousTask);
        const std::string &text = inStringBuffer.collection();
        // Преобразуем текст ответа в широкосимвольную строку
        std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,
           wchar_t> utf16conv;
        std::wostringstream ss;
        ss << utf16conv.from_bytes(text.c_str()) << std::endl;
        OutputDebugString(ss.str().data());
        // Обработка ошибок                  
        return pplx::task_from_result();
      });
    }
    // Здесь выполняем операции, читая из потока ответа
    istream bodyStream = response.body();
    container_buffer<std::string> inStringBuffer;
    return bodyStream.read_to_end(inStringBuffer)
      .then([inStringBuffer, &creds](pplx::task<size_t> previousTask)
    {
      UNREFERENCED_PARAMETER(previousTask);
      const std::string &text = inStringBuffer.collection();
      // Преобразуем текст ответа в широкосимвольную строку
      std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, 
        wchar_t> utf16conv;
      std::wostringstream ss;
      std::vector<std::wstring> parts;
      ss << utf16conv.from_bytes(text.c_str()) << std::endl;
      Split(ss.str(), parts, '&', false);
      unsigned pos = parts[1].find('=');
      std::wstring token = parts[1].substr(pos + 1, 16);
      pos = parts[0].find('=');
      std::wstring tokenSecret = parts[0].substr(pos + 1);
      creds->SetToken(token);
      creds->SetTokenSecret(tokenSecret);
    });
  });
}

Рис. 9. Функция UploadFileToDropBoxAsync

task<void> MainPage::UploadFileToDropBoxAsync(
  std::shared_ptr<AppCredentials>& creds)
{
  using concurrency::streams::file_stream;
  using concurrency::streams::basic_istream;
  uri url(DropBoxFileUploadURI);
  std::shared_ptr<oAuth> oAuthObj = std::make_shared<oAuth>();
  auto signatureParams =
    oAuthObj->CreateOAuthSignedParameters(url.to_string(),
    L"PUT",
    NULL,
    consumerKey,
    consumerSecret,
    creds->Token(),
    creds->TokenSecret()
  );          
  std::wstring sb = oAuthObj->OAuthBuildSignedHeaders(url);
  return file_stream<unsigned char>::open_istream(this->m_fileToUpload)
    .then([this, sb, url](pplx::task<basic_istream<unsigned char>> previousTask)
  {
    try
    {
      auto fileStream = previousTask.get();
      // Получаем длину контента,
     // присвоенного свойству Content-Length
      fileStream.seek(0, std::ios::end);
      auto length = static_cast<size_t>(fileStream.tell());
      fileStream.seek(0, 0);
      // Выдаем HTTP-запрос с файловым потоком в качестве тела
      http_request req;
      http_client client(sb);
      req.set_body(fileStream, length);
      req.set_method(methods::PUT);
      return client.request(req)
        .then([this, fileStream](pplx::task<http_response> previousTask)
      {
        fileStream.close();
        std::wostringstream ss;
        try
        {
          auto response = previousTask.get();
          auto body = response.body();                  
          // Протоколируем код успешного ответа
          ss << L"Server returned status code "
          << response.status_code() << L"."
          << std::endl;
          OutputDebugString(ss.str().data());
          if (response.status_code() == web::http::status_codes::OK)
          {
            auto action = m_dispatcher->RunAsync(
              Windows::UI::Core::CoreDispatcherPriority::Normal,
              ref new Windows::UI::Core::DispatchedHandler([this]()
              {
                using namespace Windows::UI::Popups;
                auto msgDlg = ref new MessageDialog(
                  "File uploaded successfully to Dropbox");
                msgDlg->ShowAsync();
              }));
          }
        }
        catch (const http_exception& e)
        {
          ss << e.what() << std::endl;
          OutputDebugString(ss.str().data());
        }
      });           
    }                         
    catch (const std::system_error& e)
    {
      // Здесь протоколируем любые ошибки
      // и возвращаем пустую задачу
      std::wostringstream ss;
      ss << e.what() << std::endl;
      OutputDebugString(ss.str().data());
      return pplx::task_from_result();
    }
  });
}

Функция OAuthAcquireTokenAsync получает маркер, сопоставленный с учетной записью Dropbox. Сначала я формирую необходимую строку доступа (access string) и заголовки HTTP-запроса и вызываю сервис Dropbox для проверки удостоверений. Этот HTTP-запрос имеет тип GET, а ответ возвращается как поток символов. Я разбираю этот поток для получения самого маркера и секрета, которые потом сохраняются в экземпляре класса AppCredentials.

Успешно получив маркер и его секрет от Dropbox, я использую их для загрузки файла в Dropbox. Как и в случае любой конечной точки веб-доступа к Dropbox, сначала формируется строка параметров и HTTP-заголовки. Затем вызывается конечная точка сервиса Dropbox, сопоставленная с загрузкой файлов. Этот HTTP-запрос имеет тип PUT, поскольку я пытаюсь поместить контент в сервис. Перед этим мне также нужно сообщить Dropbox о размере контента. Это указывается установкой значения свойства content_length в методе HTTP_request::set_body равным размеру загружаемого файла. После успешного возврата PUT-метода с помощью объекта диспетчера я вывожу пользователю сообщение об успешном завершении операции.

На очереди Linux

Интеграция C++ REST SDK в приложения Windows 8 (как Windows Store, так и настольные) проста и прямолинейна. Добавьте преимущества написания кода, который может быть общим между двумя платформами, применение идиом программирования на современном C++ и тот факт, что данный код является портируемым между приложениями как Windows, так и других ОС, — и приз ваш. Вы можете больше не беспокоиться о специфичных для платформ тонкостях, относящихся к сетевым API, и вместо этого уделять больше времени продумыванию функциональности, которую должно поддерживать ваше приложение. В этом простом примере я задействовал C++ REST SDK для аутентификации пользователя в Dropbox и последующей загрузки файла в облако Dropbox. Подробнее о Dropbox REST API см. документацию по ссылке bit.ly/10OdTD0. В следующей статье я покажу, как выполнять те же задачи из Linux-клиента.


Сридхар Подури (Sridhar Poduri) — менеджер программ в группе Windows в Microsoft. Страстный поклонник C++ и автор книги «Modern C++ and Windows Store Apps» (Sridhar Poduri, 2013), регулярно пишет о C++ и Windows Runtime в своем блоге sridharpoduri.com.

Выражаю благодарность за рецензирование статьи экспертам Microsoft Никласу Густаффсону (Niklas Gustaffson), Сана Митани (Sana Mithani) и Огги Шобахичу (Oggy Sobajic).