Come recuperare contenuti interdominio utilizzando un proxy PHP

Se hai mai provato a recuperare una risorsa al di fuori del tuo dominio tramite una chiamata Ajax, probabilmente hai ricevuto questo messaggio di errore:

XMLHttpRequest non può caricare http://www.domain.com/path/filename. L'origine null non è consentita da Access-Control-Allow-Origin.

Questo errore è stato attivato dalla politica Same-origine. Consente agli script in esecuzione su pagine originate dallo stesso sito di accedere ai dati degli altri senza restrizioni specifiche, ma impedisce agli script di accedere ai dati offerti da un dominio diverso. Fortunatamente, ci sono dei modi per aggirarlo, tra cui:

- Prosegue dopo la pubblicità -
  1. CORS (Cross-Origin Resource Sharing)
  2. JSONP (JSON Padding)
  3. il metodo postMessage ()
  4. proxy locale

Questo tutorial descrive come configurare un proxy locale, come per il punto 4 sopra, per combinare frammenti di documenti da un altro dominio con il proprio contenuto web.

- Prosegue dopo la pubblicità -

Come funziona

Tutti i workaround di cui sopra hanno i loro punti di forza e di debolezza, ma trovo che la soluzione proxy sia la migliore per servire contenuti HTML non adulterati da un altro dominio quando il tuo host web supporta alcuni tipi di scripting lato server come PHP, .NET, Python, eccetera.

- Prosegue dopo la pubblicità -

L’idea è semplice; fai una richiesta al tuo script per recuperare una risorsa da un altro dominio e la restituisce al tuo browser. Quindi, il contenuto proviene dal tuo server – non più errore Access-Control-Allow-Origin! Ecco un diagramma per illustrare:

- Prosegue dopo la pubblicità -

La nostra pagina loadFrame.html chiamerà il nostro script proxy.php e quindi analizzerà la risposta per visualizzare i contenuti selezionati dalla mia pagina di destinazione robgravelle.com in un iFrame. Puoi leggere ulteriori informazioni sul filtraggio dei contenuti iFrame qui .

La pagina finita sarà simile a questa:

Il codice del server

Lo script proxy viene eseguito sul server in modo che, per quanto ne sa il browser, il contenuto provenga da esso e da nessun’altra parte. Sto usando PHP perché rende il recupero di contenuti Web in un attimo e perché viene eseguito sul mio WampServer locale senza alcuna configurazione.

Accesso al parametro URL

Come accennato in precedenza, l’URL viene passato allo script proxy come parametro GET. Ecco come leggerlo con PHP:

1
2
3
<?php<font></font>
$url = (isset($_GET['url'])) ? $_GET['url'] : false;<font></font>
if(!$url) exit;<font></font>

Offrire la bontà

PHP offre diversi modi per recuperare contenuti web; Ho optato per file_get_contents (). Ho appena impostato l’intestazione Content type, ho recuperato la pagina web e l’ho inviata di nuovo al browser:

1
2
3
header('Content-Type: text/html');<font></font>
$string = file_get_contents($url);<font></font>
echo $string;<font></font>

In caso di emergenza

Diciamo, ci mancherebbe, che l’URL fosse malformato o che l’altro server non fosse attivo. Lo script dovrebbe includere alcuni errori di gestione per questo. La funzione file_get_contents () è un po ‘strana in quanto, anziché lanciare un’eccezione, attiva solo un evento di livello E_WARNING. Pertanto, non è possibile utilizzare un try / catch. Ho scoperto che, per i miei soldi, ciò che funziona meglio è impostare il mio gestore degli errori per E_WARNINGs. Ecco come funziona:

1
2
3
4
5
6
7
8
9
10
11
12
set_error_handler("warning_handler", E_WARNING);<font></font>
<font></font>
header('Content-Type: text/html');<font></font>
$string = file_get_contents($url);<font></font>
echo $string;<font></font>
<font></font>
restore_error_handler();<font></font>
<font></font>
function warning_handler($errno, $errstr) { <font></font>
  header('Content-Type: text/plain');<font></font>
  echo $errstr;<font></font>
}<font></font>

Il codice cliente

Di nuovo sul lato client, la pagina loadFrame.html contiene uno script basato su jQuery che esegue la chiamata Ajax sul carico del documento. Il dominio di destinazione è memorizzato in una variabile perché ne avremo bisogno in seguito. All’interno del gestore di successo (), otteniamo un riferimento ai contenuti di iFrame:

1
2
3
4
5
6
7
8
9
10
var baseUrl = 'http://www.robgravelle.com/';<font></font>
$(function() {<font></font>
  $.ajax({<font></font>
    url: "proxy.php?url=" + baseUrl + 'news/',<font></font>
    success: function(data, status, jqXHR) {<font></font>
      var iFrameContents = $('iframe').contents();<font></font>
      //do stuff...<font></font>
         }<font></font>
  });<font></font>
});<font></font>

Controllo per il tutto chiaro

La nostra chiamata Ajax contiene solo un gestore di successo () perché il nostro proxy non genera errori HTTP. Invece, restituisce una stringa in chiaro con il messaggio di errore (avvertenza). Possiamo verificarlo testando il contenuto “text / plain”. Se lo è, impostiamo il contenuto di iFrame sulla stringa di dati e gli diamo un carattere rosso per una buona misura:

1
2
3
4
5
6
7
8
9
10
11
success: function(data, status, jqXHR ) {<font></font>
  var iFrameContents = $('iframe').contents();<font></font>
  if( jqXHR.getResponseHeader('content-type').indexOf('text/plain') >= 0 ) {<font></font>
    iFrameContents.find('body')<font></font>
      .css('background-color', 'white')<font></font>
      .append( $('P').css('color', 'red').text(data) ); <font></font>
  }<font></font>
  else {<font></font>
    //do stuff...<font></font>
  }<font></font>
}<font></font>

Analisi del documento di destinazione

Ho provato a rimandare la conversione dell’HTML in un DOM il più a lungo possibile. Ecco perché vedrai alcuni parsing di RegEx nel codice. Non è fino al recupero del DIV “header_content_footer_wrapper” che la conversione avviene tramite il metodo $ .parseHTML (). Il secondo argomento (contesto) è impostato su null in modo che gli eventi inline non vengano eseguiti quando l’HTML viene analizzato. Il falso argomento garantisce che tutti gli script passati nella stringa HTML vengano rimossi:

1
2
3
4
5
var head        = /<head(.*)>([\s\S]+)<\/head>/.exec(data),<font></font>
    body        = /<body(.*)>([\s\S]+)<\/body>/.exec(data),<font></font>
    bodyClasses = body[1].match(/class=['|"]([^'|"]*)['|"]/)[1],<font></font>
    tempDOM     = $('<output>').append( $.parseHTML(body[2], null, false) ),<font></font>
    mainDiv     = tempDOM.find('#header_content_footer_wrapper');<font></font>

Impostazione delle proprietà personalizzate

Alcune immagini nella pagina di destinazione sono impostate utilizzando JavaScript che non è stato incluso nella nostra pagina. Non ne abbiamo bisogno in quanto è solo facile da fare da qui:

1
2
mainDiv.find('#content').css("background-image", "url('/@/Storage/_files/68/file.jpg')");  <font></font>
mainDiv.find('#main_heading img').attr('src', '/@/Storage/_files/62/file.gif');<font></font>

Aggiunta delle parti del documento

L’ultima riga nello script popola l’iFrame. In ordine, questo:

  1. Aggiunge il tag <BASE> con href impostato sulla variabile baseUrl. Senza di esso, tutti i collegamenti relativi saranno rotti!
  2. Aggiunge il contenuto HEAD del documento all’iFrame
  3. Inserisce le classi nel tag body iFrame
  4. Aggiunge il contenuto DIV al corpo iFrame.
1
2
3
4
5
6
7
<font></font>
iFrameContents.find('head')<font></font>
    .append( $('<base>').attr('href', baseUrl) )<font></font>
    .append(head)<font></font>
       .next('body')<font></font>
            .attr('class', bodyClasses)<font></font>
            .append(mainDiv);<font></font>

Ho incluso i due file in un file zip per il tuo divertimento. Estraili alla radice www del tuo server e apri la pagina loadFrame.html nel tuo browser usando l’URL del tuo server come in http://localhost:8080/Frames/loadFrame.html.

Conclusione

L’utilizzo di un proxy locale è la soluzione migliore per pubblicare contenuti HTML da un altro dominio quando il tuo host web supporta alcuni tipi di script sul lato server. Basta fare attenzione perché è anche la soluzione più rischiosa tra i domini cross-domain a causa dell’importazione di HTML non elaborato, e possibilmente di script, da altri domini.