2010-06-24 4 views
15

La documentazione PHP states che php://input può essere letta solo una volta.Perché php: // input può essere letto più volte nonostante la documentazione indichi altrimenti?

Nella mia applicazione ho bisogno di leggerlo due volte, una volta per scopi di autenticazione e una volta per l'elaborazione effettiva del contenuto, ed entrambe le funzioni sono gestite da diversi moduli indipendenti. La cosa pazzesca è: funziona.

Posso contare su questo funzionamento in tutto il mondo, o si tratta di un problema nella mia versione di PHP (5.2.10)? L'unica documentazione che posso trovare su questo è quella che afferma che non dovrebbe funzionare, senza menzionare la limitazione della versione.


Seguendo un'intuizione Dennis', ho fatto questo test:

$in = fopen('php://input', 'r'); 
echo fread($in, 1024) . "\n"; 
fseek($in, 0); 
echo fread($in, 1024) . "\n"; 
fclose($in); 
echo file_get_contents('php://input') . "\n"; 

Curling:

$ curl http://localhost:8888/tests/test.php -d "This is a test" 
This is a test 

This is a test 

Apparentemente è limitata a un solo leggere ogni handle aperto.


Un po 'più di scavo ha rivelato che in effetti php://input può essere letto solo una volta, mai, per le richieste PUT. L'esempio precedente utilizzava una richiesta POST.

+3

... e ora, 4,5 anni dopo, PHP 5.6 ufficialmente [supporta] (http://docs.php.net/manual/en/migration56.new-features.php) leggendo da 'php: // input 'più di una volta, e anche cercare le operazioni :) –

+1

Per quelli di noi non ancora su PHP 5.6, avvolgendo' file_get_contents ('php: // input') 'in una funzione che memorizza nella cache il risultato e la chiamata che invece è un lavoro fattibile -in giro. – Umbrella

risposta

21

Un piccolo controllo del codice sorgente fornisce le risposte.

In primo luogo, sì, si è limitato ad una lettura per maniglia perché il flusso sottostante non implementa l'seek gestore:

php_stream_ops php_stream_input_ops = { 
    php_stream_input_write, 
    /* ... */ 
    "Input", 
    NULL, /* seek */ 
    /* ... */ 
}; 

In secondo luogo, il gestore di lettura ha due comportamenti differenti a seconda che il "POST dati "è stato letto e memorizzato in SG(request_info).raw_post_data.

if (SG(request_info).raw_post_data) { 
    read_bytes = SG(request_info).raw_post_data_length - *position; 
    /* ...*/ 
    if (read_bytes) { 
     memcpy(buf, SG(request_info).raw_post_data + *position, read_bytes); 
    } 
} else if (sapi_module.read_post) { 
    read_bytes = sapi_module.read_post(buf, count TSRMLS_CC); 
    /* ... */ 
} else { 
    stream->eof = 1; 
} 

Quindi abbiamo tre possibilità: i dati del corpo

  1. La richiesta è stata già lette e conservati in SG(request_info).raw_post_data. In questo caso, poiché i dati sono archiviati, possiamo aprire e leggere più handle per php://input.
  2. I dati del corpo della richiesta sono stati letti, ma il suo contenuto non è stato memorizzato da nessuna parte. php://input non può darci nulla.
  3. I dati della richiesta non sono stati ancora letti. Ciò significa che possiamo aprire php://input e leggerlo solo una volta.

NOTA: Quello che segue è il comportamento predefinito. SAPI diversi o estensioni aggiuntive potrebbero modificare questo comportamento.

In caso di richieste POST, PHP definisce un diverso lettore POST e un gestore POST in base al tipo di contenuto.

Caso 1. Questo accade quando abbiamo una richiesta POST:

  • Con tipo di contenuto application/x-www-form-encoded. sapi_activate rileva una richiesta POST con un tipo di contenuto e chiama sapi_read_post_data. Questo rileva il tipo di contenuto e definisce la coppia di lettori/gestori POST. Il lettore POST è sapi_read_standard_form_data, che viene immediatamente chiamato e copia il corpo della richiesta su SG(request_info).post_data. Viene quindi chiamato il lettore di post predefinito php_default_post_reader, che riempie $HTTP_RAW_POST_DATA se l'impostazione ini always_populate_post_data è impostata e quindi copia SG(request_info).post_data in SG(request_info).raw_post_data e cancella il primo. La chiamata al gestore non ha importanza qui e viene posticipata fino a quando non vengono creati i superglobali (il che potrebbe non accadere, nel caso in cui il JIT sia attivato e non siano usati i superglobali).
  • Con un tipo di contenuto non riconosciuto o inesistente. In questo caso, non ci sono lettori e gestori POST definiti. Entrambi i casi finiscono in php_default_post_reader senza alcun dato letto. Poiché si tratta di una richiesta POST e non esiste una coppia lettore/gestore, verrà chiamato sapi_read_standard_form_data. Questa è la stessa funzione del gestore di lettura del tipo di contenuto application/x-www-form-encoded, quindi tutti i dati vengono ingeriti su SG(request_info).post_data. Le uniche differenze da ora in poi sono che $HTTP_RAW_POST_DATA è sempre popolato (indipendentemente dal valore di always_populate_post_data) e non esiste alcun gestore per la creazione dei superglobali.

Caso 2. Questo succede quando abbiamo un modulo di richiesta con il tipo di contenuto "/ form-data multipart". Il lettore POST è NULL, quindi il gestore, che è rfc1867_post_handler, funge da misto reader/handler. Nessun dato di qualsiasi tipo viene letto nella fase sapi_activate. La funzione sapi_handle_post viene infine chiamata in una fase successiva, che a sua volta chiama il gestore POST. rfc1867_post_handler legge i dati della richiesta, popola POST e FILES, ma non lascia nulla in SG(request_info).raw_post_data.

Caso 3. Quest'ultimo caso si verifica con richieste diverse dal POST (ad esempio PUT). php_default_post_reader viene chiamato direttamente. Poiché la richiesta non è una richiesta POST, i dati vengono inghiottiti da sapi_read_standard_form_data. Dal momento che nessun dato viene letto, non rimane nulla da fare.

+0

Risposta ben dettagliata! Ma c'è una buona ragione per questo comportamento, o è semplicemente una svista? Cioè funziona "come previsto" o "come codificato"? :) – deceze

+1

@deceze L'implementazione sembra ragionevole. i dati codificati application/x-www-form sono generalmente brevi, quindi la penalità della memoria per mantenerla in memoria è piccola. multipart/form-data è in genere significativamente più grande (ad esempio include i file), sarebbe più oneroso avere memoria. Con altre richieste POST, non penso che sia una buona idea tenere tutto in memoria, ma è necessario per la retrocompatibilità (i vecchi script avevano solo $ HTTP_RAW_POST_DATA). Per quanto riguarda altri metodi di richiesta, l'implementazione sembra ragionevole: consente all'utente di leggere i dati in modo stream, risparmiando memoria. – Artefacto

+0

Immagino che abbia senso. Grazie! – deceze

3

Forse significano fseek() o rewind() non disponibili. Hai provato una di quelle funzioni su un php: // input aperto?

+1

Sembra che questo possa essere. Buona impressione Vedi domanda aggiornata. – deceze

+0

Forse sto trascurando qualcosa, ma ... qual è la nuova domanda? –