MicrosoftがGitHubでオープンソースとして公開しているcpprestsdkを使い、VC++でHTTP通信する方法を解説します。REST APIを実行する場合に利用します。
環境構築
cpprestsdkのダウンロード
Visual StudioでパッケージマネージャーであるNuGetを使い、cpprestsdkをダウンロードします。
Visual Studioを起動し、新しいプロジェクトを作成します。
C++用の空のプロジェクトを作成します。
任意の場所に、任意の名前でプロジェクトを作成します。
ここでは、「C:\temp\application」にプロジェクトを作成します。
NuGetを使い、cpprestsdkをダウンロードします。
「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native」以下に、ヘッダとモジュール(dllファイルやlibファイルなど)がダウンロードできました。
「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native」以下のみを使います。作成したプロジェクトファイルなどは削除してOKです。
プロジェクトファイルの用意
こちらの通り、新たにプロジェクトファイル、プロパティファイルを作成します。その上で、以下を実施します。
ヘッダの配置
「C:\src\include\cpprestsdk」フォルダを作成し、「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native\include」の中身をコピーします。
モジュールの配置
「C:\src\build2019\cpprestsdk\debug」フォルダを作成し、「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native\x64\bin」と「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native\x64\lib」の中身の内、ファイル名に「d」が付いたファイルをコピーします。
コピー後、libファイルについては、ファイル名の「d」は削除しておきます。
次いで、「C:\src\build2019\cpprestsdk\release」フォルダを作成し、「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native\x64\bin」と「C:\temp\application\packages\cpprestsdk.<バージョン>\build\native\x64\lib」の中身の内、ファイル名に「d」が付いていないファイルをコピーします。
プロパティファイルの書き換え
cpprestsdkを使うため、プロパティファイルを以下のように書き換えます。
src/components/make.props
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
<Import Project="..\make.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_PropertySheetDisplayName>components</_PropertySheetDisplayName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<AdditionalIncludeDirectories>$(HOGE_SRC_DIR)\include\cpprestsdk;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <!-- ★変更箇所 -->
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(ProjectName).exe</OutputFile>
<AdditionalDependencies>cpprest141_2_10.lib;%(AdditionalDependencies)</AdditionalDependencies> <!-- ★変更箇所 -->
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
src/components/make_debug64.props
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_PropertySheetDisplayName>components.debug64</_PropertySheetDisplayName>
<OutDir>$(HOGE_BUILD_DIR)\components\bin\x64\Debug\</OutDir>
<IntDir>$(HOGE_VER)\$(PlatformName)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup>
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(HOGE_BUILD_DIR)\cpprestsdk\debug;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <!-- ★変更箇所 -->
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
src/components/make_release64.props
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets">
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_PropertySheetDisplayName>components.releas64</_PropertySheetDisplayName>
<OutDir>$(HOGE_BUILD_DIR)\components\bin\x64\Release\</OutDir>
<IntDir>$(HOGE_VER)\$(PlatformName)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup>
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<WholeProgramOptimization>false</WholeProgramOptimization>
</ClCompile>
<Link>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(HOGE_BUILD_DIR)\cpprestsdk\release;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> <!-- ★変更箇所 -->
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<ItemGroup />
</Project>
以上で、環境構築は完了です。
サンプルコード
cpprestsdkを使い、POST/GET/PUT/DELETEを行うサンプルコードを記述します。
「C:\src\components\component1\component1.vcxproj」に、「HttpClient.h」ファイルと「HttpClient.cpp」ファイルを追加し、HttpClientクラスを実装します。
以下のように実装します。
src/components/component1/HttpClient.h
#pragma once
#include <stdio.h>
#include "cpprest/http_client.h"
#include "cpprest/filestream.h"
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace concurrency::streams;
// HTTP(S)クライアント
class HttpClient
{
public:
// コンストラクタ
HttpClient::HttpClient() { this->_m_nStatusCode = 0; }
// POST
int post(const std::string& inUrl, const json::value& inData, json::value& outData);
// GET
int get(const std::string& inUrl, json::value& outData);
// PUT
int put(const std::string& inUrl, const json::value& inData, json::value& outData);
// DELETE
int del(const std::string& inUrl, json::value& outData);
private:
pplx::task<int> _post(const std::string& inUrl, const json::value& inData);
pplx::task<int> _get(const std::string& inUrl);
pplx::task<int> _put(const std::string& inUrl, const json::value& inData);
pplx::task<int> _del(const std::string& inUrl);
private:
json::value _m_cResultValue;
int _m_nStatusCode;
};
src/components/component1/HttpClient.cpp
#include "HttpClient.h"
int HttpClient::post(const std::string& inUrl, const json::value& inData, json::value& outData)
{
try {
this->_post(inUrl, inData).wait();
outData = this->_m_cResultValue;
} catch (const std::exception& e) {
::printf("<!> %s. %s \n", inUrl.c_str(), e.what());
if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
if (this->_m_nStatusCode == 200) return 0;
else if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
int HttpClient::get(const std::string& inUrl, json::value& outData)
{
try {
this->_get(inUrl).wait();
outData = this->_m_cResultValue;
} catch (const std::exception& e) {
::printf("<!> %s. %s \n", inUrl.c_str(), e.what());
if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
if (this->_m_nStatusCode == 200) return 0;
else if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
int HttpClient::put(const std::string& inUrl, const json::value& inData, json::value& outData)
{
try {
this->_put(inUrl, inData).wait();
outData = this->_m_cResultValue;
} catch (const std::exception& e) {
::printf("<!> %s. %s \n", inUrl.c_str(), e.what());
if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
if (this->_m_nStatusCode == 200) return 0;
else if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
return 0;
}
int HttpClient::del(const std::string& inUrl, json::value& outData)
{
try {
this->_del(inUrl).wait();
outData = this->_m_cResultValue;
} catch (const std::exception& e) {
::printf("<!> %s. %s \n", inUrl.c_str(), e.what());
if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
}
if (this->_m_nStatusCode == 200) return 0;
else if (this->_m_nStatusCode == 0) return 1;
else return this->_m_nStatusCode;
return 0;
}
pplx::task<int> HttpClient::_post(const std::string& inUrl, const json::value& inData)
{
const std::string sUrl = inUrl;
const json::value cValue = inData;
// 実行タスク生成
return pplx::create_task([sUrl, cValue]
{
utility::string_t sUtf16 = utility::conversions::utf8_to_utf16(sUrl);
http_client cClient(sUtf16);
http_request cRequest(methods::POST);
cRequest.set_body(cValue.serialize(), L"application/json");
return cClient.request(cRequest);
}).then([this, sUrl](http_response cResponse)
{
if (cResponse.status_code() == status_codes::OK) {
::printf("[POST] %s success. \n", sUrl.c_str());
} else {
::printf("<!> [POST] %s failed. status=%d \n", sUrl.c_str(), cResponse.status_code());
}
this->_m_nStatusCode = cResponse.status_code();
return cResponse.extract_json(true);
}).then([this](json::value cJson)
{
this->_m_cResultValue = cJson;
return 0;
});
}
pplx::task<int> HttpClient::_get(const std::string& inUrl)
{
const std::string sUrl = inUrl;
// 実行タスク生成
return pplx::create_task([sUrl]
{
utility::string_t sUtf16 = utility::conversions::utf8_to_utf16(sUrl);
sUtf16 = uri::encode_uri(sUtf16, uri::components::component::query);
http_client cClient(sUtf16);
http_request cRequest(methods::GET);
return cClient.request(cRequest);
}).then([this, sUrl](http_response cResponse)
{
if (cResponse.status_code() == status_codes::OK) {
::printf("[GET] %s success. \n", sUrl.c_str());
http_headers cHeader = cResponse.headers();
} else {
::printf("<!> [GET] %s failed. status=%d \n", sUrl.c_str(), cResponse.status_code());
}
this->_m_nStatusCode = cResponse.status_code();
return cResponse.extract_json(true);
}).then([this](json::value cJson)
{
this->_m_cResultValue = cJson;
return 0;
});
}
pplx::task<int> HttpClient::_put(const std::string& inUrl, const json::value& inData)
{
const std::string sUrl = inUrl;
const json::value cValue = inData;
// 実行タスク生成
return pplx::create_task([sUrl, cValue]
{
utility::string_t sUtf16 = utility::conversions::utf8_to_utf16(sUrl);
http_client cClient(sUtf16);
http_request cRequest(methods::POST);
cRequest.set_body(cValue.serialize(), L"application/json");
return cClient.request(cRequest);
}).then([this, sUrl](http_response cResponse)
{
if (cResponse.status_code() == status_codes::OK) {
::printf("[PUT] %s success. \n", sUrl.c_str());
} else {
::printf("<!> [PUT] %s failed. status=%d \n", sUrl.c_str(), cResponse.status_code());
}
this->_m_nStatusCode = cResponse.status_code();
return cResponse.extract_json(true);
}).then([this](json::value cJson)
{
this->_m_cResultValue = cJson;
return 0;
});
}
pplx::task<int> HttpClient::_del(const std::string& inUrl)
{
const std::string sUrl = inUrl;
// 実行タスク生成
return pplx::create_task([sUrl]
{
utility::string_t sUtf16 = utility::conversions::utf8_to_utf16(sUrl);
sUtf16 = uri::encode_uri(sUtf16, uri::components::component::query);
http_client cClient(sUtf16);
http_request cRequest(methods::GET);
return cClient.request(cRequest);
}).then([this, sUrl](http_response cResponse)
{
if (cResponse.status_code() == status_codes::OK) {
::printf("[DELETE] %s success. \n", sUrl.c_str());
http_headers cHeader = cResponse.headers();
} else {
::printf("<!> [DELETE] %s failed. status=%d \n", sUrl.c_str(), cResponse.status_code());
}
this->_m_nStatusCode = cResponse.status_code();
return cResponse.extract_json(true);
}).then([this](json::value cJson)
{
this->_m_cResultValue = cJson;
return 0;
});
}
HttpClientクラスを呼び出すメインプログラムは、例えば以下のように実装します。
src/components/component1/main.cpp
#include "HttpClient.h"
int main(int argc, const char** argv)
{
int ret;
json::value cOutData;
HttpClient cHttpClient;
ret = cHttpClient.get("http://localhost", cOutData); // localhostへGETする例
if (ret == 0) {
ret = cOutData[utility::conversions::utf8_to_utf16("result")].as_integer();
::printf("result=%d \n", ret);
}
return 0;
}
ビルドすると、「src\build2019\components\bin\x64\Debug(Release)\component1.exe」が出力されます。同フォルダに「C:\src\build2019\cpprestsdk\debug(release)\cpprest141_2_10(d).dll」をコピーして下さい。
component1.exeを実行すると、HTTP通信が実行されます。
以上で、cpprestsdkを使ったHTTP通信をすることができるようになりました。