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

It seems like there is no way to implement a JSONP solution (JSON with Padding) using DataSnap, but I want to throw this question here if someone has solved this problem.

Reference Information. JSONP is a mechanism that uses the cross-site capabilities of an HTML script element to overcome the same source policy of the XmlHttpRequest class. Using XmlHttpRequest, you can only receive data (JSON objects) from the same domain that served the HTML document. But what if you want to receive data from several sites and associate this data with browser controls?

With JSONP, your script element src attribute does not reference a JavaScript file, but instead refers to a web method (one that may be in a different domain from which the HTML was derived). This web method returns JavaScript.

The script tag assumes that the returned data is JavaScript files and runs 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 being called is defined, the function executes and can work with the JSON object. For example, a function can retrieve data from a JSON object and associate this data with the current document.

All the pros and cons of JSONP have been widely discussed (this is a very serious security issue), so there is no need to repeat it here.

What interests me is that someone there figured out how to use JSONP with Delphi DataSnap REST servers. This is the problem, as I see it. A typical use of JSONP 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 will return a call something like this:

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

and the workit function might look something like this:

 function workit(obj) { $("#namediv").val(obj.id); $("#pricediv").val(obj.price); } 

The problem is that DataSnap doesn't seem to be able to return a simple string, like

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

Instead, it is wrapped as shown below:

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

Obviously, this is not JavaScript executable.

Any ideas?

+7
source share
4 answers

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

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

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

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

+9
source

You can write a descendant of the TDSHTTPServiceComponent and associate it with your TDSHTTPService instance. The following example creates an instance of TJsonpDispatcher at run time (so as not to register it with the IDE):

 type TJsonpDispatcher = class(TDSHTTPServiceComponent) public procedure DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest; AResponseInfo: TDSHTTPResponse; const ARequest: string; var AHandled: Boolean); override; end; TServerContainer = class(TDataModule) DSServer: TDSServer; DSHTTPService: TDSHTTPService; DSServerClass: TDSServerClass; procedure DSServerClassGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); protected JsonpDispatcher: TJsonpDispatcher; procedure Loaded; override; end; implementation procedure TServerContainer.DSServerClassGetClass(DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass); begin PersistentClass := ServerMethodsUnit.TServerMethods; end; procedure TServerContainer.Loaded; begin inherited Loaded; JsonpDispatcher := TJsonpDispatcher.Create(Self); JsonpDispatcher.Service := DSHTTPService; end; procedure TJsonpDispatcher.DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest; AResponseInfo: TDSHTTPResponse; const ARequest: string; var AHandled: Boolean); begin // eg http://localhost:8080/getdata?callback=workit if SameText(ARequest, '/getdata') then begin AHandled := True; AResponseInfo.ContentText := Format('%s(%s);', [ARequestInfo.Params.Values['callback'], '{"id": "Delphi Pro", "price":999}']); end; end; 
+4
source

The origin policy issue can be easily resolved in DataSnap. You can customize the response header as follows:

 procedure TWebModule2.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin **Response.SetCustomHeader('access-control-allow-origin','*');** if FServerFunctionInvokerAction <> nil then FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker; end; 
+3
source

The answer from Nicolás Loaiza motivates me to find a solution for the TDSHTTPService, to set the client response header in the Trace event:

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

All Articles