2011-07-16 1 views
7

Похоже, что нет возможности реализовать решение JSONP (JSON с Padding) с использованием DataSnap, но я хочу бросить этот вопрос здесь, если кто-то решил эту проблему.Есть ли способ использовать JSONP с сервером REST Delphi DataSnap?

Справочная информация: JSONP - это механизм, который использует возможности перекрестного сайта для элемента HTML-скрипта для преодоления одной и той же исходной политики класса XmlHttpRequest. Используя XmlHttpRequest, вы можете получать только данные (объекты JSON) из того же домена, который служил HTML-документу. Но что, если вы хотите получать данные с нескольких сайтов и связывать эти данные с элементами управления в браузере?

С помощью JSONP атрибут src элемента сценария не ссылается на файл JavaScript, но вместо этого ссылается на метод Web (который может находиться в другом домене, из которого был получен HTML-код). Этот веб-метод возвращает JavaScript.

Тэг сценария предполагает, что возвращаемые данные являются файлами JavaScript и выполняются в обычном режиме. Однако то, что фактически возвращает Web-метод, является вызовом функции с буквальным объектом JSON в качестве его параметра. Предполагая, что функция, которая вызывается, определена, функция выполняет и может работать с объектом JSON. Например, функция может извлекать данные из объекта JSON и связывать эти данные с текущим документом.

Достоинства и недостатки JSONP широко обсуждались (это представляет собой очень серьезную проблему безопасности), поэтому нет необходимости повторять это здесь.

Меня заинтересовало то, что кто-то там выяснил, как использовать JSONP с серверами DataSnap REST от Delphi. Вот проблема, как я вижу. Типичное использование 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:

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/.

BTW, вы можете назначить nil для результата функции REST.

+0

Спасибо, Марко! Отличное решение. И спасибо за включение ссылки на блог Мат ДеЛонга об этой технике. –

+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

Очень хорошее решение. Спасибо за ваш ответ. Хотелось бы, чтобы я мог ответить как на ваш ответ, так и на ответ Марко как правильный (не возможно). Я принял ответ Марко из-за его простоты. Но я проголосовал за ваше решение. –

+0

@ Cary Спасибо! Я бы предпочел ответ Марко, очень интересно узнать о '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;