Can RemObjects SDK parameters be passed via the URI?
http://stackoverflow.com/questions/14645022/can-remobjects-sdk-parameters-be-passed-via-the-uri/14648334#14648334
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¶m2=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:
- The URI is not decoded before it is passed to the message handler. This results in a JSON error if any of the characters have been encoded e.g. %20 etc.
- There seems to be a bug in the ROIndyHTTPServer code that mishandles the URI parameters.
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.
Here's a play on norgepaul's solution that looks good and returns JSON. It's based on the same idea of intercepting the HTTP request using a descendant of TROIndyHTTPServer
, but this time I'm not only fixing the parameters of the request, I'm creating the "JSON" post that the client didn't send!
Here's the code that I used to test with the default "VCL Standalon" server implementation:
TUriROIndyHTTPServer = class(TROIndyHTTPServer) protected procedure InternalServerCommandGet(AThread: TIdThreadClass; RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo); override; end; procedure TUriROIndyHTTPServer.InternalServerCommandGet(AThread: TIdThreadClass;RequestInfo: TIdHTTPRequestInfo; ResponseInfo: TIdHTTPResponseInfo); var A, B: Integer; NewPost: AnsiString; begin if RequestInfo.Document = '/json/sum' then begin // Extract the parameters A := StrToIntDef(RequestInfo.Params.Values['a'], 0); B := StrToIntDef(RequestInfo.Params.Values['b'], 0); NewPost := AnsiString(Format('{"version":"1.1","method":"NewService.Sum","params":{"A":"%d","B":"%d"}}', [A, B])); // Prepare the (fake) post-stream RequestInfo.PostStream.Free; RequestInfo.PostStream := TMemoryStream.Create; RequestInfo.PostStream.Write(NewPost[1], Length(NewPost)); RequestInfo.PostStream.Position := 0; end else if RequestInfo.Document = '/json/getservertime' then begin // Extract the parameters NewPost := '{"version":"1.1","method":"NewService.GetServerTime"}'; // Prepare the (fake) post-stream RequestInfo.PostStream.Free; RequestInfo.PostStream := TMemoryStream.Create; RequestInfo.PostStream.Write(NewPost[1], Length(NewPost)); RequestInfo.PostStream.Position := 0; end; inherited; end;
With this sort of code in place, I can make requests like this:
http://localhost:8080/json/sum?a=1&b=2
returns (in browser!)
{"version":"1.1","result":"3"}
and this:
http://localhost:8080/json/getservertime
returns this (well, at the time of this writing):
{"version":"1.1","result":"2013-02-01T19:24:24.827"}
- The whole code for my server's main form pastebin link
- The DFM for my server's main form pastebin link
The result (in browser or foreign application) is pure JSON because it's been formated as a "JSON Message" using RO's code.