第 16 章: Windows 7 Web サービス API の使用

Hilo Browser アプリケーションでは、[Share] ダイアログを使用して、Flickr 経由で写真を共有することができます。第 15 章では、[Share] ダイアログで、Windows HTTP サービス API を使用して、マルチパートの HTTP POST 要求を作成し、選択した写真を Flickr にアップロードする方法を説明しました。写真をアップロードするには、まずセッション トークン (フロブ**) を取得して Flickr に対する Hilo Browser の認証を行い、次にアクセス トークンを取得することにより、写真をアップロードする許可を得る必要があります。この 2 つのステップを実行するために、Hilo Browser では、Windows 7 Web サービス アプリケーション プログラミング インターフェイス (WWSAPI) を使用し、Web サービスを通じて Flickr にアクセスします。この章では、Hilo Browser がこのライブラリをどのように使用しているかを検証します。

Web サービスを介した Flickr へのアクセス

前の章では、Hilo が、HTTP POST 要求を通じて、写真を Flickr Web サーバーにアップロードする方法を説明しました。この要求の重要なパラメーターは、Flickr アプリケーションである Hilo が、ユーザーの Flickr アカウントへのアクセスを承認されていることを示すアクセス トークンです。さらに、Hilo の Flickr アカウントは Flickr Web サーバーでの認証を経由して識別される必要があり、このサーバーから Hilo はフロブと呼ばれるセッション キーを取得します。この 2 つのアクション (フロブの取得とアクセス トークンの取得) は、Web サービスの呼び出しを通じて実行されます。

Web サービスは、インターネット上でクライアント アプリケーションが Web サーバーと通信して、要求を送信したり、応答としてデータを受信したりするためのメカニズムです。Web サービスにおいて重要な部分は、クライアントがどのように要求を行い、サーバーがどのように応答するかを記述するコントラクトと、可能な応答です。通常、Web サービスは HTTP 上で XML メッセージを使用して実装され、メッセージの形式には SOAP が指定されますが、これ以外の選択肢もあります。クライアントとサーバーとの間のコントラクトは不変であり、サーバー開発者がパラメーターを追加したり、パラメーターの種類を変更したりしてサービスを改良したい場合は、以前の Web サービスをサポートしながら、別のコントラクトで新しい Web サービスを提供する必要があります。

このコントラクトは公開された仕様として提供される場合があり、Web サービス クライアントの開発者はこの仕様を厳守する必要があります。ただし、コントラクトが、Web サービスの標準 XML 記述である Web サービス記述言語 (WSDL) で提供される場合もあります。WSDL の場合、コード生成ツールを使用して、プロキシ コードを生成することができます。プロキシ コードは、サービスに送信されるパケットの構築およびサービスから返されたパケットのデコード処理を実行します。

Flickr Web サービスについては、Flickr API の Web サイト (英語) で解説されています。Flickr では WSDL は提供されず、代わりに要求 SOAP パケット応答 SOAP パケットにより、さまざまな API メソッドの汎用的な記述が提供されます。これを基に WSDL ファイルを作成することができるため、Hilo 開発者はこの方法で Browser プロジェクトの Flickr.wsdl ファイルを作成しました。このファイルのプロパティ ページには、wsutil.exe と呼ばれるツールを使用する、カスタム ビルド ステップが用意されています (図 1)。このツールは Windows 7 ソフトウェア開発キット (SDK) に含まれているので、このツールを使用するには、SDK をインストールし、SDK の bin へのパスをプロジェクトの実行可能ファイルのパスに直接追加します。wsutil ツールは、WSDL ファイルを読み取って、Web サービスにアクセスするための C ヘッダーおよび C ソース ファイルを生成します。

図 1 WSDL ファイルのカスタム ビルド ステップ

Gg288974.788a345c-6f8c-4b56-8a05-7ab5533df744-thumb(ja-jp,MSDN.10).png

Flickr API 呼び出しの認証

アプリケーションは、Flickr API を使用することにより、Flickr アカウントにアクセスできます。この処理では、Flickr アプリケーションとユーザー アカウントの 2 つが重要であり、どちらも Flickr アカウントを必要とします。すなわち Hilo の場合、Hilo 用の Flickr アカウントを作成し、Flickr に Hilo を識別させるための Flickr API キーを取得する必要があるということです。Hilo はユーザーが指定したアカウントに写真をアップロードします。このメカニズムを機能させるため、ユーザーは Hilo を承認し、Hilo が指定したアカウントへのアクセス権を持つことを Flickr に示す必要があります。これには、複数のステップにわたる、認証と承認の処理が必要です。

Flickr API キーの取得を申請すると (第 12 章を参照)、2 つの文字列が付与されます。1 つは API キーで、もう 1 つはシークレットです。API キーは、ユーザー (Hilo Flickr アプリケーション) を識別します。シークレットは開発者と Flickr のみが知っており、メッセージ ダイジェストを作成するために使用されます (前の章を参照)。Flickr では、このメッセージ ダイジェストを使用して、パラメーターが伝送中に (悪意によってまたは偶然に) 壊れていないかを検証します。

Hilo で写真をアップロードできるようにするには、Flickr API キーを使用して Flickr にログオンする必要があります。そのために、Hilo では、API キーをパラメーターとして、Web サービス メソッドの flickr.auth.getFrob を呼び出す必要があります。このメソッドは、フロブと呼ばれるログオン用のセッション キーを返します。フロブは現在のセッションについてのみ Hilo アプリケーションを識別し、60 分間のみ有効ですが、Hilo では無効になるまでフロブを保持するのではなく、ユーザーが写真をアップロードしようとするたびに新しいフロブを取得するという、より簡単な方法を採用しています。

Hilo は写真を Flickr アカウントにアップロードするので、現在のセッション (フロブによって識別される) で実行されるすべてのアクションが、指定されたアカウントに対して適用される必要があるということを、Flickr に通知しなければなりません。Hilo アプリケーションは、写真をアップロードするときにどのような Flickr アカウントを使用するかは認識しておらず、その必要もありません。Hilo で行うべき処理は、現在のセッションを使用してアップロードを実行し、Flickr がデータの保存場所を決定できるようにすることだけです。これを行うには、フロブと API キーを含む Flickr ログオン URL を作成し、ブラウザーでこの URL を開きます。ユーザーはここから目的のアカウントにログオンし、現在のセッションにおいて Hilo Browser がそのアカウントにアクセスすることを承認できます。ユーザーがアカウントに対する Hilo Browser のアクセスを承認した後は、写真をアップロードするためのアクセス トークンを取得する必要があります。そのために、Web サービス メソッドの flickr.auth.getToken を呼び出して、パラメーターとして API キーとフロブを渡し、アクセス トークンを受け取ります。

Windows 7 Web サービス API の使用

前の章で説明した、FlickrUploader::UploadPhotos メソッドと FlickrUploader::GetPhotoId メソッドを使用して、Web サービスへのアクセスおよび応答のデコードをハード コーディングする方法には弱点があります。要求の形式が若干異なっているだけでも Web サービスで要求が拒否されてしまうので、要求が厳密に正しい形式で行われるように、開発者はプロトコルのすべての詳細を把握していなければなりません。また、プロトコルが変更された場合はどうなるでしょうか。公開された後は、Web サービスのインターフェイスは変更されません。ただし、サービスの開発中には、インターフェイスが変更され、クライアントの変更が必要になる場合があります。このような場合、エラーの発生する可能性が高くなります。さらに、適切な形式のメッセージを記述するという問題に加えて、トランスポート コードの管理についても問題が生じてきます。

Windows 7 Web サービス API を使用すると、要求パケットの作成、応答パケットの解釈、トランスポート コードの作成のための大量のコードを記述しなくとも、Web サービスにアクセスすることができます。Web サービス クライアント アプリケーションの開発には、まずサービスの WSDL 仕様が必要です。Hilo 開発チームでは、この情報を使用して WSDL ファイル Flickr.wsdl を作成しました。このファイルは、ソリューション エクスプローラー内の Browser プロジェクトの最上位フォルダーに格納されています。

Web サービス記述言語ファイルの解説

WSDL ドキュメントでは、サービスをネットワーク エンドポイントのコレクション、すなわちポートとして定義します。各ポートは特定の形式の要求メッセージを受け取り、特定の形式の応答およびエラー メッセージを返します。ポートを定義する際は、要求と応答で使用されるデータの形式に加えて、使用される実際のエンドポイントを指定する必要があります。柔軟な構築のために、WSDL では、Web サービスのさまざまな側面を記述する抽象エンティティの使用が許可されており、この抽象エンティティと実際の値を組み合わせて、1 つ以上の具象エンティティを作成することができます。たとえば、portType 要素は抽象エンティティであり、Web サービス メソッドを定義しますが、メソッドが実装される場所は定義しません。一方、port 要素は、portType 要素を使用しつつ、Web サービス メソッドがアクセスできる実際のネットワーク エンドポイントを指定している具象エンティティです。

Flickr API は、汎用要求形式と応答データ形式、およびエラーに関する情報を返すための汎用データ形式を定義しています。これらは、Flickr.wsdl ファイルで、抽象 element 型の FlickrRequestFlickrResponse、および FlickrFault として記述されています。これらのデータ型に基づいて、Flickr.wsdl ファイルでは、要求の実行と応答の受信に使用される、抽象 message 形式 (FlickrRequestMessageFlickrResponseMessage、および FlickrFaultMessage 要素) を宣言しています。

Flickr.wsdl ファイルには、2 つのポートが記述されています。これらの抽象 portType 要素は FlickrFrobRequestPort および FlickrTokenRequestPort であり、ポートにアクセスしたときに使用される要求、応答、およびエラー メッセージの詳細を提供します (両方の Web サービス メソッドで同じ要求および応答メッセージが使用されるので、これらの定義は同じです)。メッセージ形式に加えて、これらの portType 要素は呼び出すメソッドの名前 (flickr.auth.getFrob および flickr.auth.getToken) を指定します。これらの定義はすべて抽象的であり、要求および応答の形式を定義しますが、プロトコルに関する詳細や実際のネットワーク エンドポイントは指定しません。一方、bindingport、および service 要素は、使用される実際のプロトコルおよびエンドポイントに関する情報を含むため、具象的な要素です。

binding 要素である FlickrFrobRequestPortBinding および FlickrTokenRequestPortBinding は、portType 定義を参照し、使用されるプロトコルに関する情報を提供します。バインディングは、特定の portType で使用されるプロトコルとデータ形式の具象的指定です。service 要素には、関連するエンドポイントのコレクションの具象定義が含まれ、各エンドポイントは port 要素として指定されます。Flickr.wsdl ファイルでは、flickr.auth.getFrob および flickr.auth.getToken メソッドが、それぞれ異なる 2 つのサービス FlickrFrobRequestPortService および FlickrTokenRequestPortService の一部として宣言されています。各サービス定義は port 要素のコレクションであり、各 port 要素はメソッドと Web サービス メソッドが実装されるエンドポイントの binding 要素を提供します。

プロキシ コードの生成

WSDL ファイルを使用するうえでの大きな利点は、ファイルがコンパイルされることです。これには 2 つのメリットがあります。1 つ目のメリットは、コンパイラで WSDL ファイルが解析され、WSDL コードに無効な定義がある場合はエラーが出力されることです。プロトコル コードを手作業で作成する場合は、このチェック機能がないため、後で識別することが困難な論理エラーが生じやすくなります。2 つ目のメリットは、コンパイラによって Web サービスにアクセスするための C コードが生成され、記述するコードの量が減少することです。WSDL コンパイラ wsutil が C コードを生成することにより、プロトコル バインディング コードのパフォーマンスが向上します。

前述のように WSDL ファイルを作成 (または Web サービスの Web サイトから取得) し、プロジェクトに追加した後、プロジェクトをコンパイルしてプロキシ コード用の C ソース ファイルと C ヘッダー ファイルを生成する必要があります。次に、このプロキシ コードを使用可能にするため、C ファイルをプロジェクトに追加します。このファイルは C++ ファイルとしても問題なくコンパイルできるため、このファイルのコンパイラ オプションを変更する必要はありません。ただし、コードは本来 C コードなので、作成される定義は構造体であり、関数は不透明ハンドルに依存します。

WSDL コンパイラはデータ型、メッセージ、バインディング、およびサービス記述の構造体を作成し、WSDL ファイル内の具象定義からデータを取得して、これらの構造体のインスタンスを作成します。さらに、コンパイラは、WSDL で定義されている各 Web サービスについて、チャネルとプロトコル バインディングを作成するための関数を生成します。これらの関数は、Web サービスへのプロキシ、すなわち、コードで Web サービス メソッドと同様に呼び出される関数を作成します。プロキシ関数の名前は、binding 要素の名前に _CreateServiceProxy を追加して生成されます。Flickr.wsdl には 2 つの binding 要素があるため、FlickrFrobRequestPortBinding_CreateServiceProxyFlickrTokenRequestPortBinding_CreateServiceProxy の 2 つの関数が生成されます。これらの関数はいずれも、WS_SERVICE_PROXY 構造体へのポインターである、ハンドルを返します。構造体へのハンドルの割り当てはシステムによって行われるので、呼び出しが完了したら、WWSAPI 関数 WsFreeServiceProxy を呼び出してこのハンドルを解放する必要があります。

Web サービスは、エラー オブジェクトを介して豊富なエラー情報を返します。この情報を受け取ることを選択した場合は、不透明ハンドルである WS_ERROR ポインターを返す WsCreateError 関数を呼び出してエラー オブジェクトを作成します。この不透明ハンドルの割り当ては、処理が終了したときに WsFreeError 関数を呼び出すことによって、解除できます。エラー情報を返すことができるすべてのメソッドにこのハンドルを渡し、WsGetErrorString および WsGetErrorProperty 関数を使用してエラーに関する情報を取得することができます。エラー オブジェクトは、WsResetError 関数を呼び出すことによって再利用できるので、エラー オブジェクトの割り当ては 1 回で済み、後は Web サービスの呼び出しが完了したときに解除するだけです。

WWSAPI が Web サービスにアクセスするときには、さまざまなバッファーを割り当てる必要があります。個々のメモリの割り当てを開発者が行うと、メモリの割り当て解除を忘れて、メモリ リークが発生する可能性があるため、WWSAPI はメモリ ヒープ オブジェクトを使用して、自動的にメモリの割り当てを実行します。開発者が行う必要があるのは、Web サービスの呼び出しを行う前に、WsCreateHeap 関数を呼び出してヒープ オブジェクトを作成することと、すべての Web サービスの呼び出しが完了したときに、WsFreeHeap 関数を呼び出してヒープ オブジェクトを解放することだけです。

WSDL コンパイラは、Web サービス メソッドに対して実際の呼び出しを行う関数を作成します。関数の名前は、バインド要素とバインドされているメソッドから生成されます。Flickr.wsdl の場合、コンパイラによって FlickrFrobRequestPortBinding_flickr_auth_getFrobFlickrTokenRequestPortBinding_flickr_auth_getToken の 2 つの関数が作成されます。これらの関数には、サービス プロキシのパラメーター、入力パラメーター (要求に使用)、出力パラメーター用のバッファーへのポインター (応答に使用)、ヒープ オブジェクトのハンドル、使用されるエラー オブジェクトが含まれます。

プロキシ コードの呼び出し

Hilo では、FlickrUploader クラス、具体的には GetToken および ObtainFrob メソッドで、WWSAPI を使用します。WWSAPI を使用するには、Windows 7 SDK に含まれる WebServices.h ヘッダー ファイルをインクルードし、WebServices.lib ライブラリにリンクする必要があります。また、wsutil ツールを使用して WSDL ファイルから作成されたヘッダー ファイル、および生成された C ファイルをプロジェクトにインクルードすることも必要です。

WWSAPI を使用するメソッドは 2 つあり、FlickrUploader クラスでは、プロキシ コードを初期化およびクリーンアップするコードをプライベート メソッドの CreateWebProxy および CloseWebProxy に組み込んでいます。CreateWebProxy は、ヒープ オブジェクトとエラー オブジェクトを作成した後、プロキシを作成して開きます。柔軟性を向上させるため、CreateWebProxy メソッドは、wsutil ツールによって生成されたコードを使用して Web プロキシ オブジェクトを作成するのではなく、直接 WWSAPI 関数を呼び出します。CreateWebProxy メソッドはエラー オブジェクトへのハンドルを返し、Hilo はこれを Web サービス プロキシに渡しますが、現在のバージョンの Hilo は基本的なエラー処理のみ提供し、エラー オブジェクト内のデータにはアクセスしません。

CreateWebProxy メソッドは、WsCreateServiceProxy 関数を呼び出し、使用するチャネルの種類を指定することによって Web サービス プロキシを作成します。これに関連するコードをリスト 1 に示します。最初のパラメーターは、要求を実行し、応答を受信するためにプロキシが呼び出されることを示します。2 番目のパラメーターは、サービスにアクセスするために SOAP over HTTP が使用されることを示します。

WsCreateServiceProxy 関数は、6 番目のパラメーターとして WS_CHANNEL_PROPERTY 構造体の配列を指定し、追加の値を提供することができます。これらの各構造体には、プロパティ ID、続いてプロパティ値へのポインターおよびプロパティのサイズが含まれます。CreateWebProxy メソッドは 3 つのプロパティを提供します。1 つ目は使用する SOAP のバージョンを指定します。2 つ目はアドレス指定に関するヘッダーが SOAP エンベロープの一部として伝送されないことを指定します。最後のプロパティは、すべてのデータが UTF8 で提供されることを指定します。

リスト 1 プロキシの作成

WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_2;
WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
WS_ENCODING encoding = WS_ENCODING_XML_UTF8;

WS_CHANNEL_PROPERTY channelProperties[3] =
{
   {
      WS_CHANNEL_PROPERTY_ENVELOPE_VERSION,
      &soapVersion, sizeof(WS_ENVELOPE_VERSION)
   },
   {
      WS_CHANNEL_PROPERTY_ADDRESSING_VERSION,
      &addressingVersion, sizeof(WS_ADDRESSING_VERSION)
   },
   {
      WS_CHANNEL_PROPERTY_ENCODING,
      &encoding, sizeof(WS_ENCODING)
   }
};

if(SUCCEEDED(hr))
{
   hr = WsCreateServiceProxy(
      WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, 
      nullptr, nullptr, 0, 
      channelProperties, ARRAYSIZE(channelProperties),
proxy, // 出力パラメーター、プロキシを返す
      *error);
}

Web サービスはエンドポイントで実装されるので、WsOpenWebServiceProxy 関数を呼び出して、このエンドポイントをプロキシに提供する必要があります。これを行うための CreateWebProxy メソッドを含むコードを、リスト 2 に示します。

リスト 2 プロキシを開く

static const wchar_t* flickr_soap_endpoint_url = L"http://api.flickr.com/services/soap/";

WS_ENDPOINT_ADDRESS address = 
{
   {
      static_cast<unsigned long>(wcslen(flickr_soap_endpoint_url)), 
      const_cast<wchar_t*>(flickr_soap_endpoint_url)
   }
};

if(SUCCEEDED(hr))
{
   hr = WsOpenServiceProxy(*proxy, &address, nullptr, *error);
}

Web サービス プロキシを作成したら、WsCall 関数の呼び出しを通じて、Web サービスを呼び出すことができます。WsCall 関数は、呼び出す Web サービス操作を指定するパラメーターおよびサービスに渡されるパラメーターを使用し、Web サービスを呼び出してその結果を返します。WsCall メソッドの呼び出しは、wsutil ツールを使用して WSDL ファイルから生成された関数により行われます。

CreateWebProxy メソッドは、プロキシ オブジェクトと共にヒープおよびエラー オブジェクトも作成し、これらはすべて Web サービスへの呼び出しを行うために使用されます。これらのオブジェクトはすべてリソースを割り当てるので、プロキシを使用し終えたときにこのリソースを解放する必要があります。この処理は、CloseWebProxy メソッドを呼び出すことによって行われます (リスト 3)。

リスト 3 リソースの解放

void FlickrUploader::CloseWebProxy (WS_HEAP** heap, WS_SERVICE_PROXY** proxy,  WS_ERROR** error)
{
    if (proxy != nullptr && *proxy != nullptr)
    {
        WsCloseServiceProxy(*proxy, nullptr, nullptr);
        WsFreeServiceProxy(*proxy);
    }

    if (heap != nullptr && *heap != nullptr)
    {
        WsFreeHeap(*heap);
    }

    if (error != nullptr && *error != nullptr)
    {
        WsFreeError(*error);
    }
}

Hilo における Windows 7 Web サービス API の使用

WWSAPI を使用して Flickr API Web サービスを呼び出して、フロブを取得し (ObtainFrob メソッド)、アップロード操作用のアクセス トークンを取得します (GetToken メソッド)。リスト 4 に、この呼び出しを行う GetToken メソッドの基本的なコード (プロキシを初期化する、呼び出しを実行する、プロキシを閉じる) を示します。

リスト 4 GetToken メソッド

std::wstring FlickrUploader::GetToken(const std::wstring& frob)
{
   std::wstring outputString;
   WS_ERROR* error = nullptr;
   WS_HEAP* heap = nullptr;
   WS_SERVICE_PROXY* proxy = nullptr;

   HRESULT hr = CreateWebProxy(&heap, &proxy, &error);

// Web サービスを呼び出す...

   CloseWebProxy(&heap, &proxy, &error);
   return outputString;
}

Web サービスを呼び出すコードを、リスト 5 に示します。このメソッドのパラメーターは、メソッドの名前 (L" flickr.auth.getToken")、API キー、署名、およびフロブです。これらのパラメーターは、wsutil ツールによって生成された _FlickrRequest 構造体のインスタンスを介して渡されます。署名は、API キー、フロブ、メソッド名を連結し、MD5 ハッシュを計算することによって生成されます。

リスト 5 トークンを取得するためのコード

if(SUCCEEDED(hr))
{
   _FlickrRequest request = 
   {
      (wchar_t*)(flickr_auth_getToken_method_name), 
(wchar_t*) flickr_api_key, // api_key
nullptr,                          // api_sig は後で計算される
      (wchar_t*)frob.c_str()
   };

   std::wstring params = flickr_secret;
   params += L"api_key";
   params += request.api_key;
   params += L"frob";
   params += request.frob;
   params += L"method";
   params += request.method;

   std::wstring api_sig = CalculateMD5Hash(params);
   request.api_sig = const_cast<wchar_t*>(api_sig.c_str());
   wchar_t* token = nullptr;
   hr = FlickrTokenRequestPortBinding_flickr_auth_getToken(
      proxy, &request, &token, heap, nullptr, 0, nullptr, error);
   if (SUCCEEDED(hr))
   {
      bool errorFound = false;
      std::wstring value = GetXmlElementValueByName(token, L"token", &errorFound);
      if (!errorFound)
      {
         outputString = value;
      }
   }
}

次に、GetToken メソッドは wsutil ツールによって作成された FlickrTokenRequestPortBinding_flickr_auth_getToken 関数を呼び出します。この関数は、WWSAPI の WsCall 関数を呼び出して、結果を SOAP パケットとして返します。最後に、GetToken メソッドは GetXmlElementValueByName メソッドを呼び出します。このメソッドは、XmlLite を使用して、GetToken メソッドから返された token 要素の値を取得します。

まとめ

この章では、Hilo Browser で選択した写真をアップロードするために必要な認証と承認のプロセスを、Windows 7 Web サービス API を使用し、Flickr Web サービスに要求を送信することにより処理する方法を紹介しました。

この章で、このシリーズの記事は終わりです。このシリーズでは、Windows 7 によって提供される強力な機能と API を使用した、最新の Hilo Browser と Hilo Annotator の実装について説明してきました。具体的には、タッチ対応ユーザー インターフェイスの設計、および Direct2D、Windows アニメーション マネージャー、Windows リボンを使用してこれを実装する方法についてお話ししました。また、アプリケーションを Windows シェルに統合する方法や、Windows Imaging Component を使用して画像を操作する方法についても述べ、最後に、HTTP および Web サービスを使用して写真を共有する方法を紹介しました。

このシリーズの記事が、豊富な機能を持つ、競争力の高い Windows アプリケーションの開発の一助となれば幸いです。

前へ | ホーム