2011-07-16 101 views
7

似乎沒有辦法使用DataSnap來實現帶有Padding的JSONP(JSON)解決方案,但是我想在這裏拋出這個問題以防有人解決了這個問題。有沒有辦法在Delphi DataSnap REST服務器上使用JSONP?

背景:JSONP是一種利用HTML腳本元素的跨站點引用功能來克服XmlHttpRequest類的相同源策略的機制。使用XmlHttpRequest,您只能從HTML文檔提供的相同域中獲取數據(JSON對象)。但是如果你想從多個站點檢索數據並將數據綁定到瀏覽器中的控件呢?

使用JSONP,腳本元素的src屬性不引用JavaScript文件,而是引用Web方法(可駐留在檢索到HTML的不同域上的Web方法)。這個Web方法返回JavaScript。

腳本標籤假定返回的數據是一個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。

任何想法?

+2

這個問題可以降低到「 DataSnap提供了一個過濾器/鉤子/事件,它允許在發送給客戶端之前修改生成的JSON響應「? – mjn

回答

9

有在Delphi的DataSnap REST方法一種方法來繞過定製JSON處理並返回準確的JSON你想。這是一類功能我用(在我放鬆框架),以純數據返回給jqGrid的:在http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/

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

信息。

順便說一下,您可以將nil分配給REST函數的結果。

+0

謝謝,Marco!好的解決方案感謝您提供Mat DeLong關於這項技術的博客鏈接。 –

+0

是的,感謝鏈接到我的博客Marco :) –

+0

鏈接'http:// blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata /'不再可用。什麼是類'TRlxjqGrid'? –

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; 
+0

非常好的解決方案。謝謝您的回覆。我希望我可以接受你的答案和馬可的回答是正確的(不可能)。由於其簡單性,我已經接受了Marco的答案。但是,我已經投票了你的解決方案。 –

+0

@Cary謝謝!我更喜歡Marco的回答,瞭解'TDSInvocationMetadata'非常有趣。不幸的是,它似乎沒有公開關於請求的信息,只有響應。有時從服務器方法中訪問請求數據可能很有用。 –

+0

非常好的解決方案,但在我的代碼中,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; 
+0

我沒有機會嘗試這種方法,但感謝您的貢獻。 –

+0

這是一個不錯的解決方案。 –

2

Nicolás Loaiza答案激勵我找到跟蹤事件TDSHTTPService解決方案,建立客戶響應頭:

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; 
相關問題