是否可以在Delphi DataSnap REST服务器上使用JSONP?

问题描述 投票:7回答:4

[似乎没有办法使用DataSnap来实现JSONP(带有Padding的JSON)解决方案,但是如果有人解决了这个问题,我想在这里抛出这个问题。

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

[使用JSONP,您的script元素的src属性不引用JavaScript文件,而是引用Web方法(该方法可以驻留在从中检索HTML的其他域中)。该Web方法返回JavaScript。

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

JSONP的优缺点已被广泛争论(这是一个非常严重的安全问题),因此在此无需重复。

[我感兴趣的是,如果有人知道如何在DELPHI的DataSnap REST服务器中使用JSONP。正如我所看到的,这就是问题所在。典型的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。

有什么想法吗?

delphi rest jsonp delphi-xe datasnap
4个回答
9
投票

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/上的信息。

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


4
投票

您可以编写一个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;

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;

3
投票

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;
© www.soinside.com 2019 - 2024. All rights reserved.