Adding HttpAPI to a Server (Delphi)

This topic will run you through the steps of adding HttpAPI support to a Remoting SDK server. If you don't have a server project yet, create a new CodeFirst Remoting SDK application using the corresponding template.

Then drop TROHttpApiDispatcher to near ROServer component and link it with ROServer via Object Inspector or manually like

procedure TServerForm.FormCreate(Sender: TObject);
begin
  ROHttpApiDispatcher1.Server := ROServer;  //<<<<
  ROServer.Active := true;
end;

You can now run the server and go to http://localhost:8099/api and have a look. The server will expose its meta-information there as JSON, e.g.:

{
  "swagger":"2.0",
  "info":{
    "title":"NewLibrary",
    "version":"1.0.0"
    },
  "basePath":"\/api",
  "consumes":[
    "application\/json"
  ],
  "produces":[
    "application\/json"
  ],
  "paths":{
  }
}

As an exersise modify the dispatcher's properties to expose base hostname, custom server name and version.

Now let's expose some server methods to play with. Add a new service to the server defined as such:

unit SampleService_Impl;

{$I RemObjects.inc}
interface

uses
  System.SysUtils, System.Classes, System.TypInfo, System.Generics.Collections,
  uROXMLIntf, uROClientIntf, uROClasses, uROTypes, uROServer, uROServerIntf, uROSessions,
  uRORemoteDataModule, uRORTTIAttributes, uRORTTIServerSupport, uROArray;

const
  __ServiceName ='SampleService';

type
  [ROService(__ServiceName)]
  TSampleService = class(TRORemoteDataModule)
  public
    [ROServiceMethod]
    [ROCustom('HttpApiPath','convert/{value}')]
    function ConvertToString(value: Integer): String;

    [ROServiceMethod]
    [ROCustom('HttpApiPath','calculate')]
    [ROCustom('HttpApiMethod','GET')]
    function Calculate([ROCustom('HttpApiQueryParameter','1')] a: Integer;
                       [ROCustom('HttpApiQueryParameter','1')] b: Integer): TROArray<Integer>;

    [ROServiceMethod]
    [ROCustom('HttpApiPath','cache/{key}')]
    [ROCustom('HttpApiMethod','POST')]
    [ROCustom('HttpApiResult','201')]
    procedure AddToCache(key: String;
                        [ROCustom('HttpApiQueryParameter','1')] value: String);

    [ROServiceMethod]
    [ROCustom('HttpApiPath','cache/{key}')]
    [ROCustom('HttpApiMethod','GET')]
    function ReadFromCache(key: String): String;

    [ROServiceMethod]
    [ROCustom('HttpApiPath','cache/{key}')]
    [ROCustom('HttpApiMethod','PUT')]
    procedure UpdateCache(key: String;
                          [ROCustom('HttpApiQueryParameter','1')] value: String);

    [ROServiceMethod]
    [ROCustom('HttpApiPath','cache/{key}')]
    [ROCustom('HttpApiMethod','DELETE')]
    procedure DeleteFromCache(key: String);

  end;

implementation
uses
  uROHttpApiUtils, uROHTTPTools;

{$R *.dfm}

var
  _dataCache: TDictionary<String, String>;

const
  HTTP_409_code               = 409;
  HTTP_409_status             = 'Conflict';

{ TNewService }

procedure TSampleService.AddToCache(key, value: String);
begin
  if (_dataCache.ContainsKey(key)) then raise EROHttpApiException.Create(HTTP_409_code, HTTP_409_status);
  _dataCache.Add(key, value);
end;

function TSampleService.Calculate(a, b: Integer): TROArray<Integer>;
begin
  Result := TROArray<Integer>.Create;
  Result.Add(a * b);
  // This code line will result in the DivideByZero exception
  // if the b parameter value is not provided or its value is 0
  // Client will receive a generic 500 Internal Server Error response code
  Result.Add(Round(a / b));
  Result.Add(a + b);
  Result.Add(a - b);
end;

function TSampleService.ConvertToString(value: Integer): String;
begin
  Result :=  'The value is ' + IntToStr(value);
end;

procedure TSampleService.DeleteFromCache(key: String);
begin
  if not _dataCache.ContainsKey(key) then raise EROHttpApiException.Create(HTTP_404_code, HTTP_404_status);
  _dataCache.Remove(key);
end;

function TSampleService.ReadFromCache(key: String): String;
begin
  if not _dataCache.ContainsKey(key) then raise EROHttpApiException.Create(HTTP_404_code, HTTP_404_status);
  result := _dataCache[key];
end;

procedure TSampleService.UpdateCache(key, value: String);
begin
  if not _dataCache.ContainsKey(key) then raise EROHttpApiException.Create(HTTP_404_code, HTTP_404_status);
  _dataCache[key] := value;
end;


initialization
  RegisterCodeFirstService(TSampleService);
  _dataCache:= TDictionary<String, String>.Create;
finalization
  _dataCache.Free;
end.

Note how several service methods are defined on the same HTTP path. Also take a look how custom error codes are returned to the client.

Restart the server and go to http://localhost:8099/api again. If everything was done correct you'll see much longer service description JSON than in the first time.

You can open the online Swagger Editor and copy-paste the API description JSON from http://localhost:8099/api. This tool not only allows to generate the client-side code to access the server, it also allows to try out the server methods:

Play with different methods and key values to see how the *cache method will return different results and Http result codes.