delphiuridelphi-xe2remobjects

Can RemObjects SDK parameters be passed via the URI?


We have a RemObjects SDK HTTP server that exposes a number of services and methods. Is it possible to call a method via a URI rather than passing the parameters as SOAP/JSON e.g.

http://www.mywebservice.com/servicename/methodname?param1=xxx&param2=yyy

Solution

  • UPDATE

    I've written an improved version of the server descendant. This one converts a formatted URI into a JSON object that will subsequently be handled by the RO JSON Message handler.

    The default handing method is to ignore the URI.

    Change URIHandlingMethod to urhJSON to accept a URI like this:

    http://www.mywebservice.com/json?{JSON OBJECT}
    

    Set URIHandlingMethod to urhParametersto to accept a URI like this:

    http://www.mywebservice.com/json/service/method?param1=xxx&param2=yyy
    

    Here's the code:

    unit ROJSONURIIndyHTTPServer ;
    
    interface
    
    uses
      SysUtils, Classes,
    
      uROIndyHTTPServer,
    
      IdURI, IdCustomHTTPServer;
    
    type
      TURIHandlingMethod = (
        urhNone,
        urhJSON,
        urhParameters
      );
    
      TROJSONURIIndyHTTPServer = class(TROIndyHTTPServer)
      private
        FURIHandlingMethod: TURIHandlingMethod;
        FJSONVersion: String;
    
        function ConvertURIToJSON(const Document, Params: String): String;
        function NextBlock(var Value: String; Delimiter: Char = '/'): String;
      protected
        procedure InternalServerCommandGet(AThread: TIdThreadClass; RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo); override;
      public
        constructor Create(AOwner: TComponent); override;
      published
        property URIHandlingMethod: TURIHandlingMethod read FURIHandlingMethod write FURIHandlingMethod;
        property JSONVersion: String read FJSONVersion write FJSONVersion;
      end;
    
    implementation
    
    { TROJSONURIIndyHTTPServer }
    
    constructor TROJSONURIIndyHTTPServer.Create(AOwner: TComponent);
    begin
      inherited;
    
      FJSONVersion := '1.1';
    end;
    
    function TROJSONURIIndyHTTPServer.NextBlock(var Value: String; Delimiter: Char): String;
    var
      p: Integer;
    begin
      p := 1;
    
      while (p <= length(Value)) and (Value[p] <> Delimiter) do
        Inc(p);
    
      if p = length(Value) then
        Result := Value
      else
        Result := copy(Value, 1, p - 1);
    
      Value := copy(Value, p + 1, MaxInt);
    end;
    
    function TROJSONURIIndyHTTPServer.ConvertURIToJSON(const Document, Params: String): String;
    const
      JSONObjectTemplate = '{"method":"%s.%s"%s,"version": "%s"}';
      JSONParamTemplate = '"%s":"%s"';
      JSONParamsTemplate = ',"params":{%s}';
    var
      CallService, CallMessage,
      ParsedDocument, ParsedParams, JSONParams,
      Param, ParamName, ParamValue: String;
      i: Integer;
    begin
      Result := '';
    
      ParsedDocument := Trim(Document);
    
      // Remove the leading /
      if (length(Document) > 0) and
         (Document[1] = '/') then
        NextBlock(ParsedDocument);
    
      // Remove the message type
      NextBlock(ParsedDocument);
    
      // Extract the service
      CallService := NextBlock(ParsedDocument);
    
      // Exctract the service message (method)
      CallMessage := NextBlock(ParsedDocument);
    
      JSONParams := '';
      ParsedParams := Params;
    
      while ParsedParams <> '' do
      begin
        // Extract the parameter and value
        Param := NextBlock(ParsedParams, '&');
    
        // See RFC 1866 section 8.2.1. TP
        Param := StringReplace(Param, '+', ' ', [rfReplaceAll]);  {do not localize}
    
        // Extract the parameter name
        ParamName := NextBlock(Param, '=');
    
        // Extract the parameter value
        ParamValue := Param;
    
        // Add a delimiter if required
        if JSONParams <> '' then
          JSONParams := JSONParams + ',';
    
        // Build the JSON style parameter
        JSONParams := JSONParams + format(JSONParamTemplate, [ParamName, ParamValue]);
      end;
    
      if JSONParams <> '' then
        JSONParams := format(JSONParamsTemplate, [JSONParams]);
    
      // Make sure we have values for all the object variables, then build the JSON object
      if (CallService <> '') and
         (CallMessage <> '') and
         (FJSONVersion <> '') then
        Result := format(JSONObjectTemplate, [CallService, CallMessage, JSONParams, JSONVersion]);
    end;
    
    procedure TROJSONURIIndyHTTPServer.InternalServerCommandGet(
      AThread: TIdThreadClass; RequestInfo: TIdHTTPRequestInfo;
      ResponseInfo: TIdHTTPResponseInfo);
    begin
      if FURIHandlingMethod in [urhJSON, urhParameters] then
      begin
        // Parse parameters into JSON if required
        if FURIHandlingMethod = urhParameters then
          RequestInfo.UnparsedParams := ConvertURIToJSON(RequestInfo.Document, RequestInfo.UnparsedParams);
    
        // Decode the URI e.g. converts %20 to whitespace
        RequestInfo.UnparsedParams := TIdURI.URLDecode(RequestInfo.UnparsedParams);
    
        //  This works around a bug in TROIndyHTTPServer. By adding a whitespace to the
        //  end of the QueryParams it forces the http server to process the parameters
        RequestInfo.QueryParams := TIdURI.URLDecode(RequestInfo.QueryParams) + ' ';
      end;
    
      inherited;
    end;
    
    end.
    

    Original Answer

    This is a follow up to André's answer.

    With the current version of RemObjects SDK the following URI should work, but doesn't:

    http://www.mywebservice.com/JSON?{"id":"{392543cf-f110-4ba3-95471b02ce5bd693}","method":"servicename.methodname","params":{"param1":"xxx","param2":"yyy"}}:
    

    There are 2 reasons why:

    I've created a ROIndyHTTPServer descendant that fixes both problems. Here's the code:

    unit FixedROIndyHTTPServer;
    
    interface
    
    uses
      SysUtils, Classes,
    
      uROIndyHTTPServer,
    
      IdURI, IdCustomHTTPServer;
    
    type
      TFixedROIndyHTTPServer = class(TROIndyHTTPServer)
      protected
        procedure InternalServerCommandGet(AThread: TIdThreadClass; RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo); override;
      public
        constructor Create(AOwner: TComponent); override;
      end;
    
    implementation
    
    { TFixedROIndyHTTPServer }
    
    constructor TFixedROIndyHTTPServer.Create(AOwner: TComponent);
    begin
      inherited;
    end;
    
    procedure TFixedROIndyHTTPServer.InternalServerCommandGet(
      AThread: TIdThreadClass; RequestInfo: TIdHTTPRequestInfo;
      ResponseInfo: TIdHTTPResponseInfo);
    begin
      // This fixes 2 issues with TROIndyHTTPServer
      //  1) It decodes the parameters e.g. converts %20 to whitespace
      //  2) It adds a whitespace to the end of the QueryParams. This
      //     forces the http server to process the parameters.
    
      RequestInfo.QueryParams := TIdURI.URLDecode(RequestInfo.QueryParams) + ' ';
      RequestInfo.UnparsedParams := TIdURI.URLDecode(RequestInfo.UnparsedParams);
    
      inherited;
    end;
    
    end.
    

    This doesn't answer my question, but it is a workaround for anybody having similar problems.

    I'm still keen to hear if RO SDK supports the use of custom URIs.