Есть ли способ использовать JSONP с REST-сервером Delphi DataSnap? - PullRequest
7 голосов
/ 16 июля 2011

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

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

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

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

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

Что меня интересует, так это то, что кто-нибудь уже понял, как использовать JSONP с REST-серверами Delphi DataSnap REST. Вот проблема, на мой взгляд. Типичное использование JSONP может включать тег script, который выглядит примерно так:

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

Веб-метод getdata возвращает вызов, похожий на следующий:

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.

Есть идеи?

Ответы [ 4 ]

9 голосов
/ 18 июля 2011

В методах REST Delphi DataSnap есть способ обойти пользовательскую обработку 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/.

Кстати, вы можете присвоить nil результат функции REST.

4 голосов
/ 17 июля 2011

Вы можете написать потомок 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 голосов
/ 14 сентября 2011

проблема политики происхождения может быть легко решена в 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;
2 голосов
/ 29 июня 2012

Ответ от 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;
...