简体   繁体   English

有没有办法将 JSONP 与 Delphi DataSnap REST 服务器一起使用?

[英]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.似乎没有办法使用 DataSnap 实现JSONP (带填充的 JSON)解决方案,但我想在这里提出这个问题,以防有人解决了这个问题。

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.背景:JSONP 是一种利用 HTML 脚本元素的跨站点引用功能来克服 XmlHttpRequest class 的同源策略的机制。 Using an XmlHttpRequest you can only obtain data (JSON objects) from the same domain that served the HTML document.使用 XmlHttpRequest 您只能从为 HTML 文档提供服务的同一域中获取数据(JSON 对象)。 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).使用 JSONP,脚本元素的 src 属性不会引用 JavaScript 文件,而是引用 Web 方法(可以驻留在检索 Z4C4AD5FCA2E7A3F74DBB1CED0038AA 的不同域中的方法)。 This Web method returns the JavaScript.此 Web 方法返回 JavaScript。

The script tag assumes that the returned data is a JavaScript file and executes it normally. script标签假设返回的数据是JavaScript文件,正常执行。 However, what the Web method actually returns is a function call with a literal JSON object as its parameter.然而,Web 方法实际返回的是一个 function 调用,它的参数为 JSON ZA8CFDE639114B6666。 Assuming that the function that is called is defined, the function executes and can operate on the JSON object.假设定义了被调用的function,则function执行并可以对Z0ECD11C1D7A287401D148A23BBD7A2F456EB2666693ACBDBDBD11C1D7A287401D148A23BBD7A2F496EB2696进行操作。 For example, the function can extract data from the JSON object and bind that data to the current document.例如,function 可以从 JSON object 中提取数据并将该数据绑定到当前文档。

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. JSONP 的优劣已经被广泛争论(它代表了一个非常严重的安全问题),所以这里没有必要重复。

What I am interested in is if anybody out there has figured out how to use JSONP with Delphi's DataSnap REST servers.我感兴趣的是,如果有人知道如何将 JSONP 与 Delphi 的 DataSnap REST 服务器一起使用。 Here's the problem, as I see it.正如我所看到的,这就是问题所在。 A typical JSONP usage may include a script tag that looks something like this:一个典型的 JSONP 用法可能包括一个如下所示的脚本标记:

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

The getdata Web method would return a call something like the following: getdata Web 方法将返回如下调用:

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

and the workit function might look something like this:工作台 function 可能看起来像这样:

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问题是 DataSnap 似乎无法返回一个简单的字符串,例如

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.显然这是不可执行的 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.在 Delphi DataSnap REST 方法中有一种方法可以绕过自定义 JSON 处理并准确返回 Z0ECD11C1D7A2BBD78740。 Here is a class function I use (in my Relax framework) to return plain data to a jqGrid:这是一个 class function 我使用(在我的 Relax 框架中)将纯数据返回到 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/ . http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/上的信息。
Info at https://mathewdelong.wordpress.com/2011/01/18/invocation-metadata/ . https://mathewdelong.wordpress.com/2011/01/18/invocation-metadata/上的信息。

BTW, you can assign nil to the result of the REST function.顺便说一句,您可以将 nil 分配给 REST function 的结果。

You can write a TDSHTTPServiceComponent descendant and hook it up with your instance of TDSHTTPService .您可以编写一个 TDSHTTPServiceComponent 后代并将其与您的TDSHTTPService实例挂钩。 In the following example an instance of TJsonpDispatcher is created at runtime (to avoid registering it in the IDE):在以下示例中,在运行时创建了一个TJsonpDispatcher实例(以避免在 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 answer from Nicolás Loaiza motivate me to find solution for TDSHTTPService, set customer response header in Trace event: Nicolás Loaiza的回答激励我寻找 TDSHTTPService 的解决方案,在 Trace 事件中设置客户响应 header:

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;

the origin policy problem can be solved easyly in DataSnap.在 DataSnap 中可以很容易地解决源策略问题。 You can Customize the response header in this way:您可以通过这种方式自定义响应 header:

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM