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) {
$("#namediv").val(obj.id);
$("#pricediv").val(obj.price);
}
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, lik开发者_如何学Ce the following:
{"result":["workit({\"id\":\"Delphi Pro\",\"price\":999});"]}
Clearly this is not executable JavaScript.
Any ideas?
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);
begin
GetInvocationMetadata().ResponseCode := 200;
GetInvocationMetadata().ResponseContent := jObj.ToString;
end;
Info at http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/.
Info at https://mathewdelong.wordpress.com/2011/01/18/invocation-metadata/.
BTW, you can assign nil to the result of the REST function.
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):
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
// e.g. 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;
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);
begin
**Response.SetCustomHeader('access-control-allow-origin','*');**
if FServerFunctionInvokerAction <> nil then
FServerFunctionInvokerAction.Enabled := AllowServerFunctionInvoker;
end;
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:
TDSHTTPResponse);
begin
if AResponse is TDSHTTPResponseIndy then
(AResponse as TDSHTTPResponseIndy).ResponseInfo.CustomHeaders.AddValue('access-control-allow-origin', '*');
end;
精彩评论