Recent

Author Topic: Simple XML-RPC Server  (Read 6011 times)

Edson

  • Hero Member
  • *****
  • Posts: 1326
Simple XML-RPC Server
« on: September 28, 2015, 06:40:42 am »
Is there a simple library/code to implement a simple XML-RPC server?

I was trying to use "Web Service Toolkit" and after reading the wiki for hours, I'm still confused. It's too complicated, just to public a simple function like hello_world.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

derek.john.evans

  • Guest
Re: Simple XML-RPC Server
« Reply #1 on: September 28, 2015, 08:07:53 am »
Unsure. You could use the units fphttpclient and DOM to transfer your own XML. I guess, the complexity comes with the automated binding.

Ok, here comes another "opinion". I hate automated remoting. To me it feels a little brittle and over cooked. I haven't dont much networking stuff in years, but here is how I did it in C#.

Write a HTTP server. Use the HTTP querystring for parameters. (I reparse the querystring, because I dont need the name/values). The sample code remotes 7 functions. Its pretty easy to return arrays.

Code: Pascal  [Select][+][-]
  1. program server;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   FileUtil,
  7.   fphttpserver,
  8.   HttpDefs,
  9.   Math,
  10.   SysUtils,
  11.   UriParser,
  12.   Variants;
  13.  
  14. type
  15.   TMyRemotingServer = class(TFPHttpServer)
  16.     procedure HandleRequest(var ARequest: TFPHTTPConnectionRequest; var AResponse: TFPHTTPConnectionResponse);
  17.       override;
  18.   end;
  19.  
  20.   procedure TMyRemotingServer.HandleRequest(var ARequest: TFPHTTPConnectionRequest;
  21.   var AResponse: TFPHTTPConnectionResponse);
  22.  
  23.     function GetString(const AIndex: Integer): String;
  24.     begin
  25.       Assert(InRange(AIndex, 0, ARequest.QueryFields.Count - 1));
  26.       Result := HTTPDecode(ARequest.QueryFields[AIndex]);
  27.     end;
  28.  
  29.     function GetInteger(const AIndex: Integer): Integer;
  30.     begin
  31.       Result := StrToInt(GetString(AIndex));
  32.     end;
  33.  
  34.     function GetDouble(const AIndex: Integer): Double;
  35.     begin
  36.       Result := StrToFloat(GetString(AIndex));
  37.     end;
  38.  
  39.     procedure AddString(const AValue: String);
  40.     begin
  41.       AResponse.Contents.Add(HTTPEncode(AValue));
  42.     end;
  43.  
  44.     procedure AddDouble(const AValue: Double);
  45.     begin
  46.       AddString(FloatToStr(AValue));
  47.     end;
  48.  
  49.   begin
  50.     try
  51.       AResponse.Code := 200;
  52.       ARequest.QueryFields.StrictDelimiter := True;
  53.       ARequest.QueryFields.Delimiter := '&';
  54.       ARequest.QueryFields.DelimitedText := ARequest.QueryString;
  55.       case LowerCase(ParseURI(ARequest.URI).Document) of
  56.         'hello': begin
  57.           AddString('Hello ' + GetString(0) + '.');
  58.         end;
  59.         'copy': begin
  60.           AddString(Copy(GetString(0), GetInteger(1), GetInteger(2)));
  61.         end;
  62.         'readfiletostring': begin
  63.           AddString(ReadFileToString(GetString(0)));
  64.         end;
  65.         'uppercase': begin
  66.           AddString(UpperCase(GetString(0)));
  67.         end;
  68.         'lowercase': begin
  69.           AddString(LowerCase(GetString(0)));
  70.         end;
  71.         'sin': begin
  72.           AddDouble(Sin(GetDouble(0)));
  73.         end;
  74.         'cos': begin
  75.           AddDouble(Cos(GetDouble(0)));
  76.         end else begin
  77.           raise Exception.Create('Unknown Command');
  78.         end;
  79.       end;
  80.     except
  81.       on E: Exception do begin
  82.         AResponse.CodeText := E.Message;
  83.         AResponse.Code := 400;
  84.       end;
  85.     end;
  86.   end;
  87.  
  88. begin
  89.   try
  90.     with TMyRemotingServer.Create(nil) do begin
  91.       try
  92.         Port := 1234;
  93.         Active := True;
  94.       finally
  95.         Free;
  96.       end;
  97.     end;
  98.   except
  99.     on E: Exception do WriteLn(E.Message);
  100.   end;
  101.   ReadLn;
  102. end.
  103.  

Then on the client side, I code up a generic Http remote function which accepts an array of string, and returns an array of string. Using that function, I create local function wrappers for each of the remote functions.

Code: Pascal  [Select][+][-]
  1. program client;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   Classes,
  7.   fphttpclient,
  8.   HttpDefs,
  9.   SysUtils,
  10.   Types,
  11.   Variants;
  12.  
  13.   function HttpRemoteCall(const AName: String; const AParams: array of String;
  14.   const AExpectedCount: Integer): TStringDynArray;
  15.   var
  16.     LIndex: Integer;
  17.   begin
  18.     with TStringList.Create do begin
  19.       try
  20.         for LIndex := Low(AParams) to High(AParams) do begin
  21.           Add(HttpEncode(AParams[LIndex]));
  22.         end;
  23.         StrictDelimiter := True;
  24.         Delimiter := '&';
  25.         DelimitedText := TFPHTTPClient.SimpleGet('http://127.0.0.1:1234/' + AName + '?' + DelimitedText);
  26.         Assert(Count = AExpectedCount);
  27.         SetLength(Result, Count);
  28.         for LIndex := 0 to Count - 1 do begin
  29.           Result[LIndex] := HttpDecode(Strings[LIndex]);
  30.         end;
  31.       finally
  32.         Free;
  33.       end;
  34.     end;
  35.   end;
  36.  
  37.   function HttpSin(const AValue: Double): Double;
  38.   begin
  39.     Result := StrToFloat(HttpRemoteCall('sin', [FloatToStr(AValue)], 1)[0]);
  40.   end;
  41.  
  42.   function HttpCos(const AValue: Double): Double;
  43.   begin
  44.     Result := StrToFloat(HttpRemoteCall('cos', [FloatToStr(AValue)], 1)[0]);
  45.   end;
  46.  
  47.   function HttpReadFileToString(const AFileName: String): String;
  48.   begin
  49.     Result := HttpRemoteCall('readfiletostring', [AFileName], 1)[0];
  50.   end;
  51.  
  52.   function HttpHello(const AString: String): String;
  53.   begin
  54.     Result := HttpRemoteCall('hello', [AString], 1)[0];
  55.   end;
  56.  
  57.   function HttpCopy(const AString: String; const APos, ALen: Integer): String;
  58.   begin
  59.     Result := HttpRemoteCall('copy', [AString, IntToStr(APos), IntToStr(ALen)], 1)[0];
  60.   end;
  61.  
  62. begin
  63.   try
  64.     WriteLn(HttpReadFileToString('C:\Documents and Settings\Admin\Desktop\Remoting\server.lpr'));
  65.     WriteLn(HttpHello('World'));
  66.     WriteLn('Sin(1.0)=', HttpSin(1.0));
  67.     WriteLn('Cos(0.5)=', HttpCos(0.5));
  68.     WriteLn('Copy=', HttpCopy('Lazarus is Fantasic!', 1, 7));
  69.   except
  70.     on E: Exception do WriteLn(E.Message);
  71.   end;
  72.   ReadLn;
  73. end.
  74.  

I got pretty good results using this method.

Note: Obviously, the ideal situation would be to have all your remoted API in a unit, and written in such a way that the local "client" code doesn't even look like it is using a remoted API.
« Last Edit: October 02, 2015, 02:57:35 am by Geepster »

Edson

  • Hero Member
  • *****
  • Posts: 1326
Re: Simple XML-RPC Server
« Reply #2 on: September 28, 2015, 07:52:06 pm »
Good. It's a simple way of doing a custom Web Service, using the GET style.  :)

Sadly, I need to implement a real XML-RPC server, to listen some VB clients. I don't need a WSDL file, just to response to 5 or 6 functions with simple parameters.

I really miss the simplicity of Python doing this:

Code: [Select]
from SimpleXMLRPCServer import SimpleXMLRPCServer

def hello_world(nombre):
    return "hello"

server = SimpleXMLRPCServer(("localhost", 8080))
server.register_function(hello_world)
server.serve_forever()
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

Edson

  • Hero Member
  • *****
  • Posts: 1326
Re: Simple XML-RPC Server
« Reply #3 on: September 29, 2015, 07:14:06 pm »
Finally, I had to implement my own XML-RPC Server.   :o

I share this for someone who need a simpe Server:

https://github.com/t-edson/ThXmlRpc

With this unit, a Server can be implemented as simple as:

Code: [Select]
program server;
{$mode objfpc}{$H+}
uses
  SysUtils, ThXmlRpc;

procedure Hello(params: TthXmlRpcParams; response: TthXmlRpcResult);
begin
  if (params.Count<>1) or (params[0].rpcTyp<>xmlRpcString) then begin
    writeln('Parameters error.');
    exit;
  end;
  response.valStr := 'Hi ' + params[0].valStr + '!';
end;

var
  Serv: TthXmlRpcServer;
begin
  RegisterFunction('Hello', @Hello);
  writeln('Listening...');
  Serv := TthXmlRpcServer.Create(nil);
  try
    Serv.Port:=1234;
    Serv.Active:=true;
  finally
    Serv.Free;
  end;
  ReadLn;
end.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

tigerA15

  • New Member
  • *
  • Posts: 17
Re: Simple XML-RPC Server
« Reply #4 on: November 30, 2015, 07:20:08 am »
marked ::)

 

TinyPortal © 2005-2018