RAD Server (EMS) のウィザードで C++ 向けに生成される初期コードは微妙に不親切でした……
Delphi Advent Calendar 2017 21日目の記事 Delphi/C++BuilderでREST APIをカンタンに実装できるRAD ServerでJSONを返す方法を3種類作ってみるの初稿では Delphi のことしか説明していなかったので、C++ のコード例も書こうかと思いました。(現在の記事は C++ のコード例も併記済みです)
※2018年3月にリリースされた RAD Studio/C++Builder 10.2 Tokyo Update 3 でC++向けの RAD Server のプロジェクトが作成できない場合は下記のパッチを適用してください。
https://cc.embarcadero.com/item/30832
というわけで、まずは RAD Server 向けプロジェクトにウィザードで C++ 向けプロジェクトを生成して動作確認と思ってそのままビルド、実行してみたら、ブラウザでアクセスしてもHTTPレスポンスのBodyがない。Delphi 向けのプロジェクトをそのままビルド、実行した場合はリソース名等が返されるのですが、C++ 向けの初期コードはそのようなサンプルが含まれておらず、応答が返されません。そこで、まずはその差分を埋めることにしてみます。
初期コードの違いを確かめてみる
いずれもリソース名は EMS にしました。またエンドポイントは下記のとおりです。要するにデフォルトと同じですね。
Endpoint | URL |
---|---|
Get | localhost:8080/EMS |
GetItem | localhost:8080/EMS/{item} |
では Delphi と C++Builder での初期コードを引用してみましょう。
Delphi の例
unit Unit1;
// EMS Resource Module
interface
uses
System.SysUtils, System.Classes, System.JSON,
EMS.Services, EMS.ResourceAPI, EMS.ResourceTypes;
type
[ResourceName('EMS')]
TEMSResource1 = class(TDataModule)
published
procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
[ResourceSuffix('{item}')]
procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
end;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
procedure TEMSResource1.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
begin
// Sample code
AResponse.Body.SetValue(TJSONString.Create('EMS'), True)
end;
procedure TEMSResource1.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
var
LItem: string;
begin
LItem := ARequest.Params.Values['item'];
// Sample code
AResponse.Body.SetValue(TJSONString.Create('EMS ' + LItem), True)
end;
procedure Register;
begin
RegisterResource(TypeInfo(TEMSResource1));
end;
initialization
Register;
end.
Delphi を良く知らなかったとしても、Get や GetItem の最後にレスポンスを返す処理が書かれているなあ、くらいのことは分かりますよね。
C++Builder の例
//---------------------------------------------------------------------------
#pragma hdrstop
#include "Unit1.h"
#include <memory>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma classgroup "System.Classes.TPersistent"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
__fastcall TEMSResource1::TEMSResource1(TComponent* Owner)
: TDataModule(Owner)
{
}
void TEMSResource1::Get(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
}
void TEMSResource1::GetItem(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
String item;
item = ARequest->Params->Values["item"];
}
static void Register()
{
std::auto_ptr<TEMSResourceAttributes> attributes(new TEMSResourceAttributes());
attributes->ResourceName = "EMS";
attributes->ResourceSuffix["GetItem"] = "{item}";
RegisterResource(__typeinfo(TEMSResource1), attributes.release());
}
#pragma startup Register 32
C++ のコードをよ~く見てみると、Get は何もしておらず、GetItem はパラメータを取得しているだけです。つまり Get, GetItem が呼ばれても実質的な応答が返されないのですね。大したコードではないとはいえ、初期状態は同等のほうが良いなあと思います。
C++Builder 向けのコードを修正して Delphi の初期コードと同じ状態にする
というわけで、同じ状態にするためにこんなふうにしてみました。詳細についてはコードをご覧ください。また、ビルドしたら localhost:8080/EMS や localhost:8080/EMS/test などにアクセスしてみると応答が返って来ますから動いていることが確認できます。
//---------------------------------------------------------------------------
#pragma hdrstop
#include "Unit1.h"
#include <memory>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma classgroup "System.Classes.TPersistent"
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
__fastcall TEMSResource1::TEMSResource1(TComponent* Owner)
: TDataModule(Owner)
{
}
void TEMSResource1::Get(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
// EMS という固定文字列で JSONString 型を作って SetValue で返す
AResponse->Body->SetValue(new TJSONString("EMS"), true);
}
void TEMSResource1::GetItem(TEndpointContext* AContext, TEndpointRequest* ARequest, TEndpointResponse* AResponse)
{
String item;
item = ARequest->Params->Values["item"];
// "EMS " + item という可変文字列で JSONString 型を作って SetValue で返す
AResponse->Body->SetValue(new TJSONString("EMS " + item), true);
}
static void Register()
{
std::auto_ptr<TEMSResourceAttributes> attributes(new TEMSResourceAttributes());
// Delphi での [ResourceName('EMS')] に相当する設定
attributes->ResourceName = "EMS";
// Delphi での [ResourceSuffix('{item}')] に相当する設定
attributes->ResourceSuffix["GetItem"] = "{item}";
RegisterResource(__typeinfo(TEMSResource1), attributes.release());
}
#pragma startup Register 32
これでプロジェクトを新規に開いた場合の初期コードが同じ状態になりました。
See also