【问题标题】:Is there a way to use JSONP with a Delphi DataSnap REST server?有没有办法将 JSONP 与 Delphi DataSnap REST 服务器一起使用?
【发布时间】:2011-10-06 20:56:12
【问题描述】:

似乎没有办法使用 DataSnap 实现JSONP(带填充的 JSON)解决方案,但我想在这里提出这个问题,以防有人解决了这个问题。

背景:JSONP 是一种利用 HTML 脚本元素的跨站点引用能力来克服 XmlHttpRequest 类的同源策略的机制。使用 XmlHttpRequest,您只能从提供 HTML 文档的同一域中获取数据(JSON 对象)。但是,如果您想从多个站点检索数据并将该数据绑定到浏览器中的控件,该怎么办?

使用 JSONP,脚本元素的 src 属性不会引用 JavaScript 文件,而是引用 Web 方法(可以驻留在检索 HTML 的不同域中的方法)。此 Web 方法返回 JavaScript。

script标签假定返回的数据是一个JavaScript文件并正常执行。然而,Web 方法实际返回的是一个以文字 JSON 对象作为参数的函数调用。假设定义了调用的函数,该函数执行并可以对 JSON 对象进行操作。例如,该函数可以从 JSON 对象中提取数据并将该数据绑定到当前文档。

JSONP 的优缺点已经被广泛争论(它代表了一个非常严重的安全问题),所以这里没有必要重复。

我感兴趣的是,是否有人知道如何将 JSONP 与 Delphi 的 DataSnap REST 服务器一起使用。正如我所看到的,这就是问题所在。一个典型的 JSONP 用法可能包括一个如下所示的脚本标记:

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

getdata Web 方法将返回类似于以下内容的调用:

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

workit 函数可能看起来像这样:

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

问题在于 DataSnap 似乎无法返回像

这样的简单字符串
workit({"id": "Delphi Pro", "price":999});

相反,它被包装,如下所示:

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

显然这不是可执行的 JavaScript。

有什么想法吗?

【问题讨论】:

  • 问题可以简化为“DataSnap 是否提供过滤器/挂钩/事件,允许在生成的 JSON 响应发送到客户端之前对其进行修改”?

标签: delphi rest jsonp delphi-xe datasnap


【解决方案1】:

在 Delphi DataSnap REST 方法中有一种方法可以绕过自定义 JSON 处理并准确返回您想要的 JSON。这是我使用(在我的 Relax 框架中)将纯数据返回到 jqGrid 的类函数:

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

http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/ 的信息。
信息https://mathewdelong.wordpress.com/2011/01/18/invocation-metadata/。

顺便说一句,您可以将 nil 分配给 REST 函数的结果。

【讨论】:

  • 谢谢,马可!很好的解决方案。感谢您提供指向 Mat DeLong 博客关于此技术的链接。
  • 是的,感谢您链接到我的博客 Marco :)
【解决方案2】:

您可以编写一个 TDSHTTPServiceComponent 后代并将其与您的 TDSHTTPService 实例挂钩。在以下示例中,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;

【讨论】:

  • 非常好的解决方案。感谢你的回复。我希望我能接受你的回答和马可的回答都是正确的(不可能)。由于其简单性,我接受了 Marco 的回答。但是,我对你的解决方案投了赞成票。
  • @Cary 谢谢!我也更喜欢 Marco 的回答,了解TDSInvocationMetadata 非常有趣。不幸的是,它似乎没有公开有关请求的信息,只公开了响应。有时,从服务器方法中访问请求数据可能很有用。
  • 非常好的解决方案,但在我的代码中,DoCommand 过程从未被调用。使用 Delphi XE7..
【解决方案3】:

DataSnap 可以轻松解决源策略问题。您可以通过这种方式自定义响应标头:

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;

【讨论】:

  • 我没有机会尝试这种方法,但感谢您的贡献。
【解决方案4】:

Nicolás Loaiza 的回答激励我寻找 TDSHTTPService 的解决方案,在 Trace 事件中设置客户响应标头:

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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-08
    • 1970-01-01
    • 2015-11-05
    • 2018-03-15
    • 2016-11-01
    • 1970-01-01
    • 2018-09-07
    • 1970-01-01
    相关资源
    最近更新 更多