TAGS :Viewed: 14 - Published at: a few seconds ago

[ Is there a way to use JSONP with a Delphi DataSnap REST server? ]

It appears that there is no way to implement a JSONP (JSON with Padding) solution using DataSnap, but I want to throw this question out here in case someone has solved this problem.

Background: JSONP is a mechanism that exploits the cross site referencing capability of the HTML script element to overcome the same origin policy of the XmlHttpRequest class. Using an XmlHttpRequest you can only obtain data (JSON objects) from the same domain that served the HTML document. But what if you want to retrieve data from multiple sites and bind that data to controls in the browser?

With JSONP, your src attribute of the script element does not reference a JavaScript file, but instead references a Web method (one that can reside on a different domain from which the HTML was retrieved). This Web method returns the JavaScript.

The script tag assumes that the returned data is a JavaScript file and executes it normally. However, what the Web method actually returns is a function call with a literal JSON object as its parameter. Assuming that the function that is called is defined, the function executes and can operate on the JSON object. For example, the function can extract data from the JSON object and bind that data to the current document.

The pros and cons of JSONP have been argued extensively (it represents a very serious security problem), so it is not necessary to repeat that here.

What I am interested in is if anybody out there has figured out how to use JSONP with Delphi's DataSnap REST servers. Here's the problem, as I see it. A typical JSONP usage may include a script tag that looks something like this:

<script type="application/javascript" src="http://someserver.com/getdata?callback=workit"> </script>

The getdata Web method would return a call something like the following:

workit({"id": "Delphi Pro", "price":999});

and the workit function might look something like this:

function workit(obj) {

The issue is that DataSnap does not seem capable of returning a simple string like

workit({"id": "Delphi Pro", "price":999});

Instead, it is wrapped, like the following:

{"result":["workit({\"id\":\"Delphi Pro\",\"price\":999});"]}

Clearly this is not executable JavaScript.

Any ideas?

Answer 1

There is a way in Delphi DataSnap REST methods to bypass the custom JSON processing and return exactly the JSON you want. Here is a class function I use (in my Relax framework) to return plain data to a jqGrid:

class procedure TRlxjqGrid.SetPlainJsonResponse(jObj: TJSONObject);
  GetInvocationMetadata().ResponseCode := 200;
  GetInvocationMetadata().ResponseContent := jObj.ToString;

Info at http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/.

BTW, you can assign nil to the result of the REST function.

Answer 2

You can write a TDSHTTPServiceComponent descendant and hook it up with your instance of TDSHTTPService. In the following example an instance of TJsonpDispatcher is created at runtime (to avoid registering it in the IDE):

  TJsonpDispatcher = class(TDSHTTPServiceComponent)
    procedure DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest; AResponseInfo: TDSHTTPResponse;
      const ARequest: string; var AHandled: Boolean); override;

  TServerContainer = class(TDataModule)
    DSServer: TDSServer;
    DSHTTPService: TDSHTTPService;
    DSServerClass: TDSServerClass;
    procedure DSServerClassGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
    JsonpDispatcher: TJsonpDispatcher;
    procedure Loaded; override;


procedure TServerContainer.DSServerClassGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
  PersistentClass := ServerMethodsUnit.TServerMethods;

procedure TServerContainer.Loaded;
  inherited Loaded;
  JsonpDispatcher := TJsonpDispatcher.Create(Self);
  JsonpDispatcher.Service := DSHTTPService;

procedure TJsonpDispatcher.DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest;
  AResponseInfo: TDSHTTPResponse; const ARequest: string; var AHandled: Boolean);
  // e.g. http://localhost:8080/getdata?callback=workit
  if SameText(ARequest, '/getdata') then
    AHandled := True;
    AResponseInfo.ContentText := Format('%s(%s);', [ARequestInfo.Params.Values['callback'], '{"id": "Delphi Pro", "price":999}']);

Answer 3

the origin policy problem can be solved easyly in DataSnap. You can Customize the response header in this way:

procedure TWebModule2.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
  if FServerFunctionInvokerAction <> nil then
    FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;

Answer 4

The answer from Nicolás Loaiza motivate me to find solution for TDSHTTPService, set customer response header in Trace event:

procedure TDataModule1.DSHTTPService1Trace(Sender:
    TObject; AContext: TDSHTTPContext; ARequest: TDSHTTPRequest; AResponse:
  if AResponse is TDSHTTPResponseIndy then
    (AResponse as TDSHTTPResponseIndy).ResponseInfo.CustomHeaders.AddValue('access-control-allow-origin', '*');