2011-07-16 18 views
7

Sembra che non ci sia modo di implementare una soluzione (JSON con Padding) usando DataSnap, ma voglio lanciare questa domanda qui nel caso qualcuno abbia risolto questo problema.C'è un modo per usare JSONP con un server REST Delphi DataSnap?

Sfondo: JSONP è un meccanismo che sfrutta la capacità di riferimento cross site dell'elemento di script HTML per superare lo stesso criterio di origine della classe XmlHttpRequest. Utilizzando una XmlHttpRequest è possibile ottenere solo dati (oggetti JSON) dallo stesso dominio che ha servito il documento HTML. Ma cosa succede se si desidera recuperare i dati da più siti e associare tali dati ai controlli nel browser?

Con JSONP, l'attributo src dell'elemento di script non fa riferimento a un file JavaScript, ma fa riferimento a un metodo Web (uno che può risiedere su un dominio diverso da cui è stato recuperato l'HTML). Questo metodo Web restituisce il codice JavaScript.

Il tag dello script presuppone che i dati restituiti siano un file JavaScript e lo eseguano normalmente. Tuttavia, ciò che effettivamente restituisce il metodo Web è una chiamata di funzione con un oggetto JSON letterale come parametro. Supponendo che la funzione chiamata sia definita, la funzione viene eseguita e può operare sull'oggetto JSON. Ad esempio, la funzione può estrarre i dati dall'oggetto JSON e associare tali dati al documento corrente.

I pro e contro di JSONP sono stati ampiamente discussi (rappresenta un problema di sicurezza molto serio), quindi non è necessario ripeterlo qui.

Quello che mi interessa è se qualcuno là fuori ha capito come usare JSONP con i server REST di DataSnap di Delphi. Ecco il problema, come vedo io. Un tipico utilizzo JSONP può includere un tag script che sembra qualcosa di simile:

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

Il metodo Web GetData restituirebbe una chiamata qualcosa di simile al seguente:

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

e la funzione Workit potrebbe essere simile questo:

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

Il problema è che DataSnap non sembra in grado di restituire una stringa semplice come

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

Invece, è avvolto, come la seguente:

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

Chiaramente questo non è eseguibile JavaScript.

Qualche idea?

+2

la questione può essere ridotto a " DataSnap offre un filtro/hook/evento che consente di modificare la risposta JSON generata prima che venga inviata al client "? – mjn

risposta

9

C'è una via nei metodi Delphi DataSnap REST per bypassare il trattamento personalizzato JSON e restituire esattamente la JSON si volere. Qui è una funzione di classe che uso (nel mio quadro Relax) per restituire i dati semplici per un jqGrid:

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

Info a http://blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata/.

BTW, è possibile assegnare zero al risultato della funzione REST.

+0

Grazie, Marco! Ottima soluzione. E grazie per aver incluso il link al blog di Mat DeLong su questa tecnica. –

+0

Sì, grazie per il collegamento al mio blog Marco :) –

+0

Il collegamento 'http: // blogs.embarcadero.com/mathewd/2011/01/18/invocation-metadata /' non è più accessibile. Di cosa tratta la classe 'TRlxjqGrid'? –

4

È possibile scrivere un discendente TDSHTTerviceComponent e collegarlo con l'istanza di TDSHTTPService. Nell'esempio seguente viene creata un'istanza di TJsonpDispatcher in fase di esecuzione (per evitare la registrazione nel 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

Soluzione molto bella. Grazie per la risposta. Vorrei poter accettare sia la tua risposta sia la risposta di Marco come corretta (non possibile). Ho accettato la risposta di Marco per la sua semplicità. Ma ho votato la tua soluzione. –

+0

@Cary Grazie! Preferirei anche la risposta di Marco, è molto interessante conoscere 'TDSInvocationMetadata'. Purtroppo non sembra esporre informazioni sulla richiesta, solo la risposta. A volte potrebbe essere utile avere accesso ai dati della richiesta all'interno del metodo server. –

+0

Soluzione molto bella, ma nel mio codice la procedura DoCommand non viene mai chiamata. Utilizzando Delphi XE7 .. –

3

il problema della politica di origine può essere risolto facilmente in DataSnap. È possibile personalizzare l'intestazione di risposta in questo modo:

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

Non ho avuto l'opportunità di provare questo approccio, ma grazie per aver contribuito. –

+0

Questa è una bella soluzione. –

2

La risposta da Nicolás Loaiza mi motiverà a trovare una soluzione per TDSHTTPService, impostare intestazione di risposta dei clienti in caso Traccia:

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; 
Problemi correlati