C++

为 C++ 开发人员带来 RESTful 服务

Sridhar Poduri

下载代码示例

在这篇文章我会展示如何使用 c + + 其余 SDK 生成一个简单的基于 Windows 的客户端应用程序,将文件上载到 Dropbox,,同时支持 OAuth 的标准 c + + 类。

连接设备的世界越来越多地进入异构混合物的本机应用程序、 Web 应用程序和 Web 站点的所有连接到云-和非--基于云服务的混合。 这些应用程序共享和消耗数据通过此混杂的设备和服务,并为最终用户提供丰富、 身临其境的体验。这些用户越来越多地使用其个人设备 (智能手机、 平板电脑、 Pc 和其他类似的设备) 上安装的应用程序。 开发人员的任务是创建在那些应用程序,以便分享、 组织和查看数据的无缝体验 — — 和喜悦的用户。

应用程序是以多种编程语言编写的。 一些,如 PHP、 C# 和 Java,则由默认启用 Web 和可能满足一些常见的方案分享和消费数据。 在其他情况下,可能会利用专门的图书馆,执行服务器端计算、 处理和分析,并将响应返回到客户端应用程序。

在他的论文,"欢迎您到丛林"(bit.ly/uhfrzH)、 草本萨特毫不含糊地描述了主流移动到弹性、 分布式计算作为自然的演进,同样的规模和性能趋势,有驱动的多核和异构计算纳入主流。

C + + 时跨平台的代码共享和性能的设计考虑的首选的编程语言。 当说到云计算时,虽然,c + + 看似不会送上充分发挥其潜力。 引此明显失败的主要原因之一是图书馆的缺乏的跨平台、 高性能、 高效可扩展,使 c + + 开发人员要到云环境中无缝地集成现有的 C 和 c + + 代码。

C + + 其余 SDK (bit.ly/VI67I5) 是微软首次进军使移到云环境的本机代码。 它的目的是为开发人员提供的工具和 Api,解决日常问题的本机代码迁移到云中。 初始版本是客户端、 跨平台的图书馆访问其他服务。 目标是使 c + + 其余 SDK 可以解锁在云计算中的本机代码的真正潜力的真正的跨平台图书馆。 C + + 其余 SDK 可以建设更专门的库,如那些支持创作,Windows Azure 服务的基础,它将使轻量化、 高性能的 Web 服务,在便携式的 c + + 代码,而不需要额外的框架或运行时部署基于云计算的虚拟机 (Vm) 中写。

为什么选择 c + + 其余 SDK?

用于访问 REST 服务,c + + 开发人员可以生成抽象 WinINet 或 Windows 上 WinHTTP 的 C 样式 Api 和类似的 Api 在其他平台上。 鉴于这种选项,呼吁立即回答的一个问题是:为什么开发商应该选择 c + + 其余 SDK?

C + + 其余 SDK 设计和从地面使用现代 c + + 来写。 功能包括:

  • 用于访问基于 REST 的服务,从 Windows Vista、 Windows 7、 Windows 8、 Windows 应用商店的应用程序,通过提供异步绑定到 HTTP,JSON,XML,Uri,Linux 上的本机代码支持,等等。
  • Visual Studio 扩展 SDK 允许 Windows 应用商店的应用程序中的 SDK 的消费。
  • 撰写的异步操作一致且功能强大的编程模型基于标准 C + + 11 的功能。
  • 执行异步流和可用于读取和写入文件/设备流的流缓冲区。

C + + 其他的客户端类

C + + 休息是使用现代 c + + 和异步编程模式的前提下建立的。 我与 Dropbox 的实验,我用的 http_client 类、 任务类和异步流类。 我将逐一讨论每个部分。

班 http_client web::http 命名空间中的 http_client 类顾名思义,用来设置和维护到 HTTP Web 服务的连接。 如果您创建的 http_client 和到服务终结点的 URI 实例,对象实例可用于使客户端的请求。 异步是内置的其余部分 c + + 库,所以作为任务由库返回响应。

任务类任务表示有可能可以完成函数产生说任务已经返回时的操作。 并非所有任务都完成,都运行,也不能保证完成后,按。 每个任务的对象的一个成员函数,is_done,它返回一个布尔值。 当该任务已完成运行时,is_done,则返回 true ; 否则,它将返回 false。 一旦任务已完成运行 — — is_done 成员函数所返回的布尔值所示 — — 该任务调用 get 函数返回的值从任务。 调用 get 函数的 is_done 函数返回 false 时要当心。 这样做会阻塞线程,并击败建筑在代码中的异步模式的整个目的。

而不是不断地检查 is_done,它是更好地使用然后函数。 它依赖于一个处理函数附加到任务,类似于在 JavaScript 扩展 Windows 应用商店的应用程序中的承诺。 它应该易于识别为使用 PPL 任务用于 Windows 运行时 (WinRT) 异步操作编程的开发人员。 传递给函数然后处理函数应采取参数类型 T 或 <T> 的任务。 使用的参数类型任务 <T> 赠送一个额外的好处:这是唯一的方法,来捕获任务操作本身所引发的异常 !

因为在任务完成后才调用然后函数的处理程序,调用 get 函数处理程序里面是安全和不会阻塞线程。

异步流其他地区 c + + 库包括一套的读取和写入流和流缓冲区作为封装对象的帮助器类。 以下的图案和优先级设置在标准 c + + 库、 流和其他 c + + 中的缓冲区分隔的格式数据的输入和输出的读和写字节或集合的字节数和从一些底层的介质,如 TCP 套接字、 磁盘文件或甚至内存缓冲区的关注从关注。 在某种程度上,流在断开与底层的介质,用于读取和写入数据。 在 c + + 其余流与大的区别是他们支持异步读取和写入操作,不同于标准的 c + + 类,阻塞。 正如与其他 c + + 其余对象的设计,流类中的异步方法返回任务 <T> 而不是值。

这在 c + + 休息的底漆,与现在是时间去想,其余 Dropbox API。 这篇文章的其余部分,我将讨论访问 Dropbox 其余 API 使用 c + + 休息要上载的文件从本地计算机运行 Windows 用户的 Dropbox 文件夹。

Dropbox 其余 API

Dropbox 使用 OAuth 版本 1 到它的 API 的所有请求进行身份验证 (bit.ly/ZJLP4o),并要求所有的请求都是通过 SSL。 为支持 OAuth 的标准 c + + 库的互联网搜索返回仅 OAuth 库中,liboauth,并且它要求要么打开­SSL (bit.ly/BpfcH) 或 Mozilla 网络安全服务 (NSS) (mzl.la/abU77o)。 我想要支持 OAuth,因此规定要建立一个支持身份验证 Dropbox 轻量级、 跨平台的类。

好奇的人们,溪英里有伟大的系列从 Win32 访问 Twitter 的 c + + 中的 OAuth 的职位 (bit.ly/137Ms6y)。 我已经建成后他基本的想法,但重构代码使用 Dropbox Api,使用 c + + 尽可能多的休息所支持的标准 c + + 类型。 此外,我不想使用 WinINet 或 WinHTTP 执行任何 Web 请求,因为那样会把我绑到 Windows 平台只逼我使用 C 样式的 API。

现在我将讨论建立一个简单的 c + + 类,支持 OAuth 和 Win32 应用程序中使用 c + + 休息,Dropbox 调用并将文件上载到 Dropbox。 在后续的文章中,我会展示如何使用 c + + 休息从 Windows 应用程序商店和上传文件到 Dropbox。

开始编写的应用程序可以访问 Dropbox 之前,我需要用 Dropbox 注册它。 这是在 Dropbox 软件控制台门户 (bit.ly/R14tjq)。 我注册了一个免费的 Dropbox 账户、 登录到控制台应用程序门户,点击"创建应用"按钮创建一个新的应用程序。

这一进程会要求您选择应用程序名称、 应用程序类型和权限类型。 我输入"测试",选择了核心应用程序类型 (请参见图 1)。 Dropbox 选择器 (JavaScript 开发人员很有用) 和同步 API (最好为 iOS 和 Android 开发者) 的其他应用程序类型。 最后,我选择了作为完整的 Dropbox,权限类型,这给我的阅读、 写作和与特定的文件夹中,由"沙盒"App 文件夹权限类型提供关于 Dropbox 的任何文件夹同步的灵活性。

Choices for Creating a Dropbox App
图 1 选择创建 Dropbox App

单击"创建应用"按钮后,Dropbox 创建应用程序并提供详细信息所需的访问如应用程序键、 app 秘密等等,如图所示,在图 2。 我记下这些因为我需要他们执行方案的身份验证和使用 OAuth 的授权请求。

Choosing Dropbox App Details
图 2 选择 Dropbox App 详细信息

一个简单的跨平台 c + + 类的 OAuth

现在,真正的行动 ! 正如我刚才所说,我想写跨平台类,可用于跨 Windows 和 Linux。 其余 SDK c + + 的当前版本不支持从 linux 系统中制作的 HTTPS 请求。 这是令人失望,而且我听说 Linux 完全 HTTPS 支持即将推出。 我在这里讨论的 c + + 类应跨 Windows 和 Linux 工作,没有重大的修改。 为了 Dropbox 的支持基于 OAuth 的身份验证,我不得不满足下列主要要求 (见 Dropbox 网站的完整的要求):

  • 应通过 SSL 的所有请求。 这意味着使用 HTTPS 只,与 HTTP。
  • OAuth 需要的请求 Uri 和参数是使用 HMAC SHA1 或 RSA SHA1 加密或纯文本,如果请求通过 SSL 进行签名。

以我的实验中,我使用纯文本签名运输,使通过 HTTPS 请求安定了。 建筑工程跨 Windows 和 Linux 的 API 加密非常复杂且耗时的和值得稍后详细的勘探。

一次我安定的要求,它是要生成此类的时间。 我宣布高级别的命名空间,方便调用的身份验证和命名 oAuth 里面的一类。 在命名空间级别,我有几个常量字符串为 URI 的终结点、 应用程序键和 app app app 注册过程中和几个帮助器方法,获得的秘密声明中所示图 3

图 3 建筑 oAuth 类

// Dropbox consumer key and secret
const std::wstring consumerKey = L"Your app key";
const std::wstring consumerSecret = L"Your app secret";
// List of Dropbox authenticate, authorize, request and file upload URIs
const std::wstring DropBoxRequestTokenURI =
  L"https://api.dropbox.com/1/oauth/request_token";
const std::wstring DropBoxAuthorizeURI =
  L"https://www.dropbox.com/1/oauth/authorize";
const std::wstring DropBoxAccessTokenURI =
  L"https://api.dropbox.com/1/oauth/access_token";
const std::wstring DropBoxFileUploadURI =
  L"https://api-content.dropbox.com/1/files_put/dropbox/<your file name here>";
const std::wstring LocalFiletoUpload = L"Your local file goes here";

整个 OAuth 协议支持 oAuth 类的 BuildSignedOAuthParameters 方法中实现。 此方法接受 URI 的端点、 HTTP 方法的类型 (GET、 邮政、 投入和等)、 app 键、 app 的秘密、 请求令牌和令牌的秘密,并生成一个签名,应送交跨 Dropbox 随每个请求。 Dropbox 将尝试对其最终使用与 HTTPS 请求传递的参数生成的确切的签名和生成的签名相匹配。 如果签名不匹配,则返回一个 HTTP 错误代码。

签名使用随机数生成的 — — 在 OAuth 术语中称为 nonce — — 包括请求支持 OAuth 协议版本的签名类型、 更多的时间戳。 该方法返回的所有必需的参数,进行排序按名称和签名 URL 编码列表 (见图 4)。

图 4 楼签名

HTTPParameters BuildSignedOAuthParameters( 
  const HTTPParameters& requestParameters,
  const std::wstring& url,
  const std::wstring& httpMethod,
  const HTTPParameters* postParameters,
  const std::wstring& consumerKey,
  const std::wstring& consumerSecret,
  const std::wstring& requestToken = L"",
  const std::wstring& requestTokenSecret = L""
  )
{
  std::wstring timestamp = OAuthCreateTimestamp();
  std::wstring nonce = OAuthCreateNonce();          

  m_oauthParameters[L"oauth_timestamp"] = timestamp;
  m_oauthParameters[L"oauth_nonce"] = nonce;
  m_oauthParameters[L"oauth_version"] = L"1.0";
  m_oauthParameters[L"oauth_signature_method"] = L"PLAINTEXT";
  m_oauthParameters[L"oauth_consumer_key"] = consumerKey;

  // Add the request token if found
  if (!requestToken.empty())
  {
    m_oauthParameters[L"oauth_token"] = requestToken;
  }

            
  // Create a parameter list containing both oauth and original
  // parameters; this will be used to create the parameter signature
  HTTPParameters allParameters = requestParameters;
  if(Compare(httpMethod, L"POST", false) && postParameters)
  {
    allParameters.insert(postParameters->begin(), 
      postParameters->end());
  }
  allParameters.insert(m_oauthParameters.begin(), 
    m_oauthParameters.end());

  // Prepare a signature base, a carefully formatted string containing 
  // all of the necessary information needed to generate a valid signature
  std::wstring normalUrl = OAuthNormalizeUrl(url);
  std::wstring normalizedParameters = 
    OAuthNormalizeRequestParameters(allParameters);

  std::wstring signatureBase = 
    OAuthConcatenateRequestElements(httpMethod, 
    normalUrl, 
    normalizedParameters);

  // Obtain a signature and add it to header requestParameters
  std::wstring signature = OAuthCreateSignature(signatureBase, 
    consumerSecret,
    requestTokenSecret);

  m_oauthParameters[L"oauth_signature"] = UrlEncode(signature);

  return m_oauthParameters;
}

支持 OAuth 让路,我写的客户端代码访问关于 Dropbox 的文件。 我创建的四种方法:

  1. oAuthLoginAsync:执行到 Dropbox 使用 app 的密钥和秘密的登录。
  2. AuthorizeDropBoxAccess:启动 Internet Explorer 和 Dropbox app 访问授权。 此方法是特定于 Windows,启动 Internet Explorer,不论它是否为默认浏览器。
  3. oAuthAcquireTokenAsync:执行操作,以获得实际的 Dropbox 访问令牌。
  4. UploadFileToDropBoxAsync:上载到 Dropbox 云存储的文件从本地系统。

每个这些操作是由极其方便和无缝使用 c + + 其他的客户端类。

客户端代码

怎么不会客户端代码使用编写异步任务与现代 c + + 匹配反对使用 C 样式的 API? 它是要找出时间。

与 C 样式等 WinINet API,我将不得不作出以下的 WinINet API 调用,让我的应用程序工作:

  • 手动生成的 HTTP 请求标头。
  • 调用 InternetCrackUrl 来解决其余端点 URL。
  • 打电话给 InternetOpen 和获得互联网连接的句柄。 通常这是作为一个 HINTERNET 实例返回。
  • 一旦有效 HINTERNET 句柄了,打个电话到 Http­OpenRequest,它返回 HINTERNET 的另一个实例。
  • 下次打电话到 HttpAddRequestHeaders,它返回一个布尔值,指示是否已成功添加标头信息的 HTTP 请求。
  • 一旦成功完成所有前面的步骤与相应的错误处理到位打到 HttpSendRequest,它将实际请求发送给。
  • 在收到前一个请求的响应,使对 InternetReadFile 的另一个调用来读取响应流。

请注意所有的以前的 Api C 样式的 Api 没有现代 c + + 编程的成语如共享的指针、 lambda 和内置的异步模式的支持。

现在的实际代码使用 c + + 其余 SDK。 图 5 演示的 oAuthLoginAsync 函数,执行登录操作到 Dropbox 和上载到 Dropbox 的文件从本地系统的 UploadFileToDropBoxAsync 函数。

图 5 oAuthLoginAsync 函数

HTTPParameters BuildSignedOAuthParameters(
  const HTTPParameters& requestParameters,
  const std::wstring& url,
  const std::wstring& httpMethod,
  const HTTPParameters* postParameters,
  const std::wstring& consumerKey,
  const std::wstring& consumerSecret,
  const std::wstring& requestToken = L"",
  const std::wstring& requestTokenSecret = L""
  )
{
  std::wstring timestamp = OAuthCreateTimestamp();
  std::wstring nonce = OAuthCreateNonce();                                         
  m_oauthParameters[L"oauth_timestamp"] = timestamp;
  m_oauthParameters[L"oauth_nonce"] = nonce;
  m_oauthParameters[L"oauth_version"] = L"1.0";
  m_oauthParameters[L"oauth_signature_method"] = L"PLAINTEXT";
  m_oauthParameters[L"oauth_consumer_key"] = consumerKey;
  // Add the request token if found
  if (!requestToken.empty())
  {
    m_oauthParameters[L"oauth_token"] = requestToken;
  }
  // Create a parameter list containing both oauth and original
  // parameters; this will be used to create the parameter signature
  HTTPParameters allParameters = requestParameters;
  if(Compare(httpMethod, L"POST", false) && postParameters)
  {
    allParameters.insert(postParameters->begin(), 
      postParameters->end());
  }
  allParameters.insert(m_oauthParameters.begin(), 
    m_oauthParameters.end());
  // Prepare a signature base, a carefully formatted string containing
  // all of the necessary information needed to generate a valid signature
  std::wstring normalUrl = OAuthNormalizeUrl(url);
  std::wstring normalizedParameters =
    OAuthNormalizeRequestParameters(allParameters);
  std::wstring signatureBase =
    OAuthConcatenateRequestElements(httpMethod,
    normalUrl,
    normalizedParameters);
  // Obtain a signature and add it to header requestParameters
  std::wstring signature = OAuthCreateSignature(signatureBase,
    consumerSecret,
    requestTokenSecret);
  m_oauthParameters[L"oauth_signature"] = UrlEncode(signature);
  return m_oauthParameters;
}
task<void> oAuthLoginAsync(std::shared_ptr<app_credentials>& creds)
{           
  uri url(DropBoxRequestTokenURI);
  std::shared_ptr<oAuth> oAuthObj = std::make_shared<oAuth>();
  auto signatureParams =
   oAuthObj->CreateOAuthSignedParameters(url.to_string(),
   L"GET",
   NULL,
   consumerKey,
   consumerSecret
   );
  std::wstring sb = oAuthObj->OAuthBuildSignedHeaders(url);
  http_client client(sb);   
  // Make the request and asynchronously process the response
  return client.request(methods::GET)
    .then([&creds](http_response response)
  {                                          
    if(response.status_code() != status_codes::OK)
    {                             
      // Handle error cases ...
return pplx::task_from_result();
    }
    // Perform actions here reading from the response stream ...
// in this example, parse the response body and
    // extract the token and token secret
    istream bodyStream = response.body();
    container_buffer<std::string> inStringBuffer;
    return bodyStream.read_to_end(inStringBuffer)
      .then([inStringBuffer, &creds](pplx::task<size_t> previousTask)
    {
      const std::string &text = inStringBuffer.collection();
      // Convert the response text to a wide-character
      // string and then extract the tokens
      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->set_Token(token);
      creds->set_TokenSecret(tokenSecret);
    });
  });
}

在 oAuthLoginAsync 函数中,我首先构造一个 URI 实例从终结点登录 URI 的字符串表示形式。 下一步,创建 oAuth 类的实例并调用 CreateOAuthSignedParameters,它生成一张地图,其中包含所有必要的 OAuth 请求参数的成员函数。 最后,我签了标头通过调用 OAuthBuildSignedHeaders 的成员函数。 签署的标头是根据 OAuth 规范强制性的。 现在开始的 HTTP 通信。 我只需要创建一个 http_client 的实例和签名的请求字符串传递给它。 Dropbox 将使用请求字符串和头信息和生成相同的尝试请求在服务器端的字符串匹配它反对什么我作为 HTTP 请求的一部分发送。 中的字符串匹配,如果我能成功返回代码 ; 否则,出现错误。

我开始通信过程通过创建 http_client 类的一个实例,然后调用请求的成员函数。 我指定的 HTTP 方法得到。 当请求方法执行结束后时,它返回一个 http_response 对象,我用来分析和提取的令牌和令牌的秘密,其中存储在 app_credentials 类的实例。 令牌应该和 API 的所有后续请求发给收存。

所示的 UploadFileToDropBoxAsync 函数图 6。 它的 oAuthLoginAsync 函数到遵循类似的模式,直到生成签名的 OAuth 报头。 一旦我建立的头信息,创建一个任务,将文件从本地文件系统读取到一个 file_stream 对象,并设置该 file_stream 对象作为 HTTP 请求正文。 我可以创建 http_client 类的一个实例和设置请求实例,其中包含 file_stream 内容作为正文,然后放置 PUT 请求。 完成后,我得到包含 http_response 可解析的成功或失败的任务。 真的就这么简单。

图 6 UploadFileToDropBoxAsync 函数

task<void> UploadFileToDropBoxAsync(std::shared_ptr<app_credentials>& 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(LocalFiletoUpload)
    .then([sb, url](pplx::task<basic_istream<unsigned char>> previousTask)
  {
    try
    {
      auto fileStream = previousTask.get();
      // Get the content length, which is used to set the
      // Content-Length property
      fileStream.seek(0, std::ios::end);
      auto length = static_cast<size_t>(fileStream.tell());
      fileStream.seek(0, 0);
      // Make HTTP request with the file stream as the body
      http_request req;                                    
      http_client client(sb);                
      req.set_body(fileStream, length);
      req.set_method(methods::PUT);
      return client.request(req)
        .then([fileStream](pplx::task<http_response> previousTask)
      {
        fileStream.close();
        std::wostringstream ss;
        try
        {
          auto response = previousTask.get();
          auto body = response.body();                  
          ss << L"Server returned returned status code "
            << response.status_code() << L"."
            << std::endl;      
          std::wcout << ss.str();
        }
        catch (const http_exception& e)
        {
          ss << e.what() << std::endl;
             }
        std::wcout << ss.str();
      });
      }                       
      catch (const std::system_error& e)
      {
        std::wostringstream ss;
        ss << e.what() << std::endl;
        std::wcout << ss.str();
        // Return an empty task
        return pplx::task_from_result();
      }
  });
}

使用 C 样式的 API,用于 Web 通信或走的路线建设支持使用平台-相比­WinINet 等特定的 Api,使用现代 c + + 编写的代码是更简洁、 易读和优雅。 事实上,所有的低级执行细节都抽象化图书馆的公共接口。 C + + 其余 SDK 生成对现代 c + + 的承诺,并采用同样的设计原则的 rest 风格的通信。 最终的结果是一个极其精心设计和图案库使用现代 c + +,使得建立连接的应用程序很容易和无缝的过程。

下一主题:Windows 应用程序商店

在这篇文章我已经探讨如何构建一个简单的基于 Windows 的客户端应用程序使用 c + + 其余 SDK 将文件上载到 Dropbox。 一路走来,我一直还讨论了建立支持 OAuth 的标准 c + + 类。 在后续的文章中,我会展示如何构建 Windows 应用商店 app 使用 c + + 其余 SDK。 请继续关注!

Sridhar Poduri 是在 microsoft Windows 团队的项目经理。C + + 爱好者和作者的书,"现代 c + + 和 Windows 商店 Apps"(Sridhar Poduri,2013年),他经常博客 c + + 和 Windows 运行时在 sridharpoduri.com

衷心感谢以下技术专家对本文的审阅: Artur Laksberg (Microsoft)
Artur Laksberg 是在 c + + 其余 SDK 团队中工作的高级发展线索。