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¶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.