2011-07-16 37 views
7

Parece que no hay forma de implementar una solución JSONP (JSON con relleno) usando DataSnap, pero quiero plantear esta pregunta aquí en caso de que alguien haya resuelto este problema.¿Hay alguna manera de usar JSONP con un servidor REST Delphi DataSnap?

Antecedentes: JSONP es un mecanismo que aprovecha la capacidad de referencia cruzada del elemento de script HTML para superar la misma política de origen de la clase XmlHttpRequest. Con XmlHttpRequest solo puede obtener datos (objetos JSON) del mismo dominio que sirvió el documento HTML. Pero, ¿qué sucede si desea recuperar datos de múltiples sitios y vincular esos datos a controles en el navegador?

Con JSONP, su atributo src del elemento de script no hace referencia a un archivo JavaScript, sino que hace referencia a un método web (uno que puede residir en un dominio diferente del que se recuperó el HTML). Este método web devuelve el JavaScript.

La etiqueta del script asume que los datos devueltos son un archivo JavaScript y se ejecuta normalmente. Sin embargo, lo que realmente devuelve el método Web es una llamada a función con un objeto JSON literal como su parámetro. Suponiendo que se define la función que se llama, la función se ejecuta y puede operar en el objeto JSON. Por ejemplo, la función puede extraer datos del objeto JSON y vincular esos datos al documento actual.

Los pros y contras de JSONP se han discutido extensamente (representa un problema de seguridad muy serio), por lo que no es necesario repetirlo aquí.

Lo que me interesa es si alguien ha descubierto cómo usar JSONP con los servidores de REST DataSnap de Delphi. Aquí está el problema, como lo veo. Un uso típico JSONP puede incluir una etiqueta de script que se ve algo como esto:

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

El método Web leedato volvería una llamada algo así como lo siguiente:

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

y la función Workit podría ser algo como esto:

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

La cuestión es que DataSnap no parece capaz de devolver una cadena simple como

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

En su lugar, se envuelve, como la siguiente:

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

Es evidente que esto no es ejecutable JavaScript.

¿Alguna idea?

+2

la cuestión se reduce a " ¿DataSnap ofrece un filtro/gancho/evento que permite modificar la respuesta JSON generada antes de que se envíe al cliente? – mjn

Respuesta

9

Hay camino en los métodos de Delphi DataSnap REST para eludir el procesamiento JSON personalizada y devolver exactamente el JSON que querer. Aquí es una función de clase utilizo (en mi marco Relax) para devolver datos sin formato a una jqGrid:

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

Información en http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/.

Por cierto, puede asignar nil al resultado de la función REST.

+0

¡Gracias, Marco! Gran solución Y gracias por incluir el enlace al blog de Mat DeLong sobre esta técnica. –

+0

Sí, gracias por vincular mi blog Marco :) –

+0

Ya no se puede acceder al enlace 'http: // blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata /'. ¿De qué se trata la clase 'TRlxjqGrid'? –

4

Puede escribir un descendiente TDSHTTPServiceComponent y conectarlo con su instancia de TDSHTTPService. En el siguiente ejemplo se crea una instancia de TJsonpDispatcher en tiempo de ejecución (para evitar registrándolo en el 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

Muy buena solución. Gracias por su respuesta. Desearía poder aceptar tanto su respuesta como la respuesta de Marco como correctas (no posibles). He aceptado la respuesta de Marco debido a su simplicidad. Pero, he votado su solución. –

+0

@Cary Gracias! Preferiría la respuesta de Marco, también, es muy interesante aprender sobre 'TDSInvocationMetadata'. Desafortunadamente, no parece exponer información sobre la solicitud, solo la respuesta. A veces puede ser útil tener acceso a los datos de solicitud desde el método del servidor. –

+0

Muy buena solución, pero en mi código nunca se invoca el procedimiento DoCommand. Usando Delphi XE7 .. –

3

el problema de la política de origen se puede resolver fácilmente en DataSnap. Se puede personalizar la cabecera de la respuesta de esta manera:

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

No he tenido la oportunidad de probar este enfoque, pero gracias por contribuir. –

+0

Esta es una buena solución. –

2

La respuesta de Nicolás Loaiza motivarme para encontrar solución para TDSHTTPService, cabecera de respuesta al cliente ubicado en el evento de seguimiento:

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; 
Cuestiones relacionadas