POST-Daten im Ajax nicht korrekt?
Klaus
- php
Hallo,
ich starte wie folgt den Ajax-Request, in dem ich die Formulardaten manuell erzeuge und an das Ajax-Script übergebe:
var file = document.getElementById("fileupload").files[0];
if (!file) {
return;
}
var formData = new FormData();
client = new XMLHttpRequest();
var prog = document.getElementById("fortschritt");
prog.value = 0;
prog.max = 100;
formData.append("datei", file);
formData.append("feld1", document.share.feld1.value);
formData.append("feld2", document.share.feld2.value);
client.onerror = function(e) {
alert("onError");
};
client.onload = function(e) {
document.getElementById("fortschritt_txt").innerHTML = "100%";
prog.value = prog.max;
};
client.upload.onprogress = function(e) {
var p = Math.round(100 / e.total * e.loaded);
document.getElementById("fortschritt").value = p;
document.getElementById("fortschritt_txt").innerHTML = p + "%";
};
client.onabort = function(e) {
alert("Upload abgebrochen");
};
client.open("POST", "upload.php");
client.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
client.setRequestHeader("Content-length", formData.length);
client.setRequestHeader("Connection", "close");
client.send(formData);
In der upload.php habe ich im Moment lediglich folgende Zeilen:
echo "--> ".$_POST['datei']." --> ".$_POST['feld1']."<br/>";
var_dump($_POST);
Zurückgegeben wird aber:
--> -->
array(1) { ["-----------------------------135241000314365 Content-Disposition:_form-data;_name"]=> string(316) ""datei"; filename="test.txt" Content-Type: text/plain Test -----------------------------135241000314365 Content-Disposition: form-data; name="feld1" ZZZZ -----------------------------135241000314365 Content-Disposition: form-data; name="feld2" XXXX -----------------------------135241000314365-- " }
Was hab ich falsch gemacht, dass ich nicht auf die $_POST-Daten zugreifen kann?
Ein var_dump($_FILES) gibt ein leeres Array zurück.
Klaus
client.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ändern in "multipart/form-data" dann sollte auch in $_FILES was zu finden sein.
Ben Akiba
client.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ändern in "multipart/form-data" dann sollte auch in $_FILES was zu finden sein.
Damit erhalte ich die folgende Warnung:
Warning: Missing boundary in multipart/form-data POST data in Unknown on line 0
Bei einer schnelle Suche bei Google, wird aus Lösung gsagt, man solle stattdessen "application/x-www-form-urlencoded" verwenden, oder ganz weglassen.
Aber auch diese Warnung ignoriered, erhalte ich für $_FILES weiterhin array(0) { }
Klaus
client.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
ändern in "multipart/form-data" dann sollte auch in $_FILES was zu finden sein.
Damit erhalte ich die folgende Warnung:
Warning: Missing boundary in multipart/form-data POST data in Unknown on line 0
Du möchtest beim Erstellen des FormData-Objekts das Formular übergeben:
Setze im Form den Enctype="multipart/form-data"
var formData = new FormData(dein_form_object);
dann gibt es auch eine Boundary.
MfG
Du möchtest beim Erstellen des FormData-Objekts das Formular übergeben:
var formData = new FormData(document.share);
Setze im Form den Enctype="multipart/form-data"
<form name="share" enctype="multipart/form-data" method="post">
Am Ergebnis hat sich aber nichts geändert. Also weiterhin die Warnung und weiterhin ein leeres $_FILES.
ein Upload funktioniert nur mit dem Enctype="multipart/form-data" und method="POST". Wenn file ein Objekt File ist (quasi ein Blob), wird dieser Enctype erstellt, mehrere Parts enthaltend (wie der Name schon sagt), welche infolge einer Boundary voneinander getrennt sind.
Bei Dir kommt dieser Enctype nicht zustande, daher die Fehlermeldung wegen der fehlenden Boundary. Dein document.share ist wahrscheinlich kein Formular-Objekt. Mach das alles einmal über ein ganz normales Formular mit den entsprechenden Attributen, übergib das Formular-Objekt dem Konstruktor FormData und sende das FormData-Objekt per POST. Dann funktionier auch das Upload mit Ajax. Btw. den Request-Header Content-Length: form_data.length lass weg, das liefert ohnehin nicht den richtigen Wert. Im Prinzip mussr Du nur das FormData-Objekt richtig erstellen, wenn Du das Formular-Objekt übergibst, sind alle Eingaben und auch die Datei im Request mit diesem Enctype enthalten.
MfG
FormData-Objekt per POST
Das hat mich gerade einige Zeit gekostet, hab's mit Chrome nicht zum Laufen gebracht. Der meinte immer, er solle ein File senden und das wäre verboten.
Also mach ich es hübsch zu Fuß:
function sendData(formular) {
var data='';
var t='';
arElements=formular.elements;
for (var i=0; i<arElements.length; i++ ) {
if (arElements[i].name) {
data = data + t + encodeURIComponent(arElements[i].name) + '=' + encodeURIComponent(arElements[i].value);
t = '&';
}
}
url="<?php echo $_SERVER['REQUEST_SCHEME'], '://', $_SERVER['SERVER_NAME'], dirname($_SERVER['REQUEST_URI']); ?>/usermin_Reaktor.php";
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.onload = function(e) {
if ('O.K.' == xhr.responseText) {
return false;
} else {
alert(xhr.responseText + "\r\n" + e);
}
};
xhr.send(data);
return false; // Formular wird nicht abgesendet
}
FormData-Objekt per POST
Das hat mich gerade einige Zeit gekostet, hab's mit Chrome nicht zum Laufen gebracht. Der meinte immer, er solle ein File senden und das wäre verboten.Also mach ich es hübsch zu Fuß:
Vor einiger Zeit habe ich das hier mal aufgeschrieben, was es mit dem Enctype="multipart/form-data" auf sich hat. Die Parts sind einfach aneinandergehängt und das sieht so aus:
-----------------------------14430236131380
Content-Disposition: form-data; name="name"
Lastname
-----------------------------14430236131380
Content-Disposition: form-data; name="vname"
Firstname
-----------------------------14430236131380
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: text/plain; charset=UTF-8
Firstname Lastname
-----------------------------14430236131380--
Zu sehen ist die Boundary, welche die Komponenten trennt. Jetzt betrachte mal die letzte Komponente, die nämlich zeigt, dass ein Upload stattgefunden hat: Während die ersten Beiden Parts nur Parameter(name, vname) = Wert (Lastname, Firstname) haben, steht in der letzten Komponente (Part) noch ein weiterer Parameter: filename="blob", welcher infolge der Verwendung des FormData-Objekts
formData.append('file', new Blob([data], { type: 'text/plain; charset=UTF-8' }));
hinzugefügt wird. Wobei: name="file" habe ich selbst vergeben, filename="blob" jedoch fügt das FormData-Objekt hinzu (Default). Aufgrunddessen und nur deswegen ist der Inhalt laut name="file" serverseitig in $_FILES zu finden. Aus dem Enctype="multipart/form-data" wird beim Senden mit POST der Request-Header Content-Type: multipart/form-data und wenn Du Dir auf meiner Demo-Site diesen Header anschaust, ist da auch die Boundary mit dabei. Letzteres ist wichtig, damit der serverseitige Parser die einzelnen Komponenten wiederherstellen kann.
In PHP ist der Part in $_File zu finden, in Perl erstellt CGI.pm aus dem Parameter name="file" ein Dateihandle auf eine temporäre Datei. Der Name des Parameters lautet im Beispiel "file" der kann natürlich auch anders lauten.
Es ist zugegebendermaßen etwas verwirrend (den Header Content-Type gibt es auch in der Response, jedoch mit einer völlig anderen Bedeutung), aber ich habe mir das nicht ausgedacht. Es steht in einschlägigen RFCs, zum eigenen Verständnis einfach mal machen.
Schöne Grüße.
-----------------------------14430236131380
Content-Disposition: form-data; name="name"Lastname
-----------------------------14430236131380
Content-Disposition: form-data; name="vname"Firstname
-----------------------------14430236131380
Content-Disposition: form-data; name="file"; filename="blob"
Content-Type: text/plain; charset=UTF-8Firstname Lastname
-----------------------------14430236131380--
Derlei habe ich wohl gesehen.
Das blöde ist, ich habe das Skript geändert, nunmehr versucht den Fehler nachzubauen, damit ich das Problem vernünftig und mit der vollständigen Fehlermeldung zeigen kann - und kann ihn einfach nicht wieder provozieren.
Weiß der Teufel, was da los war.
Jörg Reinholz
hi,
Weiß der Teufel, was da los war.
dieser Request-Header muss vorhanden sein, bzw. serverseitig ankommen:
'CONTENT_TYPE' => 'multipart/form-data; boundary=---------------------------715254748006',
(die boundary kann auch anders lauten, die wird vom UserAgent zufällig erzeugt)
--mfg
hi,
Weiß der Teufel, was da los war.
Fehlersuche
dieser Request-Header muss vorhanden sein, bzw. serverseitig ankommen:
'CONTENT_TYPE' => 'multipart/form-data; boundary=---------------------------715254748006',
(die boundary kann auch anders lauten, die wird vom UserAgent zufällig erzeugt)
Mit untenstehendem Script kann das näher untersucht werden.
Fazit: Erstelle ein FormData-Objekt und überlasse das Setzen der Request-Header dem XHR-Objekt, dann funktioniert auch das Upload.
Schöne Grüße.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use CGI -private_tempfiles;
my $cgi = CGI->new;
if($cgi->param){
print "Content-Type: text/plain\n\n", Dumper \%ENV;
}
else{
read(DATA, my $html, -s DATA);
printf $html, $ENV{SCRIPT_NAME}, $ENV{SCRIPT_NAME};
}
__DATA__
Content-Type: text/html; Charset=UTF-8
<!DOCTYPE HTML>
<head>
<title>Upload</title>
</head>
<html><body>
<form action="%s" method="POST" Enctype="multipart/form-data" onSubmit="return false;">
<input type="file" name="file">
<button onClick="xup(this.form, cb)"> Auffi gehts! </button>
</form>
<script>
// Upload mit Ajax
function xup(form, cb){
var form_data = new FormData(form);
var xhr = new XMLHttpRequest();
xhr.open("POST", "%s", true);
// Das ist der Killer!!
// xhr.setRequestHeader("Content-type", "multipart/form-data");
// da fehlt die Boundary
// Das XHR-Objekt setzt diesen Header selbst!!!
xhr.send(form_data);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
cb(xhr.response);
}
}
};
}
// Ajax CallBack
function cb(response){
alert(response);
}
</script>
</body></html>
var form_data = new FormData(form);
Das FormData-Dingenkirchens taugt nur eingeschränkt.
Versuche mal den Multi-Select... http://www.fastix.org/test/xhr_formdata.php Auf dem "herkömmlichen Weg" geht es.
(Hochgeladene Dateien werde hoffentlich irgend wann vom Hoster aus dem Temp-Dir gelöscht...)
Jörg Reinholz
Hakuna matata!
var form_data = new FormData(form);
Das FormData-Dingenkirchens taugt nur eingeschränkt.
Versuche mal den Multi-Select... http://www.fastix.org/test/xhr_formdata.php Auf dem "herkömmlichen Weg" geht es.
Interessant, aber das würde wohl eher bedeuten, dass PHP nur bedingt taugt, denn laut Netzwerkanalyse wird alles korrekt an den Server übertragen. FormData und Ajax ist also nicht schuld daran.
Wenn das name-Attribut auf "[]" endet, dann werden die Werte auch erwartungsgemäß im $_POST-Array aufgeführt.
<select name="multi_selection[]" multiple="" size="3">
<option value="sel 1">selection 1</option>
<option value="sel 2">selection 2</option>
<option>no value</option>
</select>
Bei der herkömmlichen Methode hat man dann allerdings ein zwei dimensionales Array.
Wenn das name-Attribut auf "[]" endet, dann werden die Werte auch erwartungsgemäß im $_POST-Array aufgeführt.
<select name="multi_selection[]" multiple="" size="3">
Bei der herkömmlichen Methode hat man dann allerdings ein zwei dimensionales Array.
Ich probiere mal was. Nämlich das direkte senden mit GET.
Ergebnis:
&multi_selection=sel+1&multi_selection=sel+2&multi_selection=no+value
Die Erfinder von formData haben also tatsächlich ein bekanntes Fehlverhalten nachgebaut. Hm. Eines, wo man in den Bezeichner zwei <http://de.selfhtml.org/html/referenz/attribute.htm#id_idref_name@title=seltsame Zeichen> einbauen muss, damit es funktioniert. (den Trick mit name='foo[]' kannte ich schon von einer "Gruppierung" von Checkboxen..
"Seltsam", weil aktuell für das name-Attribut gilt:
Any non-empty value for name is allowed, but the names "_charset_" and "isindex" are special:
isindex
This value, if used as the name of a Text control that is the first control in a form that is submitted using the application/x-www-form-urlencoded mechanism, causes the submission to only include the value of this control, with no name.
_charset_
This value, if used as the name of a Hidden control with no value attribute, is automatically given a value during submission consisting of the submission character encoding.
Jörg Reinholz
hi Jörg,
Die Erfinder von formData haben also tatsächlich ein bekanntes Fehlverhalten nachgebaut.
Nein. Der Enctype multipart/form-data ist uralt. Mehrere gleichnamige Parameter sind auch beim Upload mit diesem Enctype zulässig, Perl/CGI.pm liefert dann mehrere Dateihandle, Upload mehrerer Dateien funktioniert auch mit Ajax und FormData einfandfrei, genauso wie beim Submit im reinen CGI-Betrieb.
Beherzige meinen Tipp von vorhin und nutze FormData.append(); damit kannst Du die Parameternamen selbst vergeben, z.B. als fortlaufende Nummer.
Schöne Grüße.
hi Jörg,
Die Erfinder von formData haben also tatsächlich ein bekanntes Fehlverhalten nachgebaut.
Nein. Der Enctype multipart/form-data ist uralt.
Mag sein. Aber irgendwo auf der Strecke Browser -> Apache -> PHP geht das "Multi" vom Multi-Select verloren, wenn man dessen Name nicht die Klammern [] anhängt.
Perl/CGI.pm liefert
Das werde ich mir gleich mal ansehen...
Ja-Nee, war mal wieder klar. Ein simples:
#!/usr/bin/perl
use CGI qw/:standard/;
use CGI::Carp 'fatalsToBrowser';
print "content-type:text/html\n\n";
print Dump;
liefert, wie hotti schon schrieb, alle Daten zurück, verschluckt sich also nicht wie PHP am Multi-Select.
dann mehrere Dateihandle,
um die geht es jetzt gar nicht. Jedenfalls nicht so richtig.
Ausgangspunkt war lediglich, dass der Chrome behauptete, er dürfe keine Datei senden - aber ich hatte zu dem Zeitpunkt im Formular gar keinen Dateiupload (<file ...>) drin. Den Fehle kann ich noch immer nicht nachvollziehen, ich kann ihn einfach nicht provozieren.
Daneben fällt mir auf, dass formData jedenfalls auch dann einen leeren Eintrag für eine Datei sendet, denn gar keine ausgewählt wurde:
Bei allen anderen Methoden (mein Fußweg, direktes Senden per POST/GET) sieht $_FILES (nach dem Versenden des Formulars mit (jeweils aktuellem) Chromium und Firefox so aus:
$_FILES: Array
(
)
mit formData:
$_FILES: Array
(
[file1] => Array
(
[name] =>
[type] =>
[tmp_name] =>
[error] => 4
[size] => 0
)
)
Das ist auch Mist, weil ich nicht einfach mit einem
if ( isset($_FILES['file1']) ) {
...
}
in die Prüfung einsteigen kann und dann darin mit
if ( $_FILES['file1']['error'] {
...
}
prüfen ob ein FEHLER auftrat. Eine gar nicht erst ausgewählte und deshalb nicht gesendete Datei ist kein FEHLER, sondern ein UMSTAND, den ich - mit anderen Methode klappt das, mit if ( isset($_FILES['file1']) ) { ... } prüfen will.
Aus meiner Sicht ist formData also doch "buggy" - denn wenn ich was nicht gebrauchen kann, dann ist es, dass ich die gesendeten Daten je nach Art des Versandes anders auswerten _muss_. Das bedeutet nämlich, ich muss den "Reaktor" (soll ja schließlich auf gesendete Daten reagieren) nicht allgemein schreiben. Das wieder bedeutet, ich kann nicht ohne weiteres einen "Reaktor" schreiben, der unabhängig davon ist, ob JS angeschaltet oder abgeschaltet ist.
Das mit dem Multi-Select und dem File sind also zwei unabhängige Probleme, die ich lösen muss. Beim Multiselect wird es auf den Vorschlag von United herauslaufen, dem Name des Formularelements immer ein [] anzuhängen umd das richtige Verhalten zu erzwingen. Das ist nicht schön, weil ich das sehr genau merken muss - aber ich kann bei jeder Art des Sendens (XHR [zu Fuß, mit formData], direkt per POST oder GET) bei einer Art bleiben.
$_FILES muss ich also, will ich universell bleiben, mehrstufig auswerten.
Wenn Array leer -> nicht gesendet
wenn Array-Element {name=='' && tmp_name=='' && type=='' && error==4 && size==0) -> nicht gesendet.
Was für ein Horror...
Jörg Reinholz
hi Jörg,
Daneben fällt mir auf, dass formData jedenfalls auch dann einen leeren Eintrag für eine Datei sendet, denn gar keine ausgewählt wurde:
Es wird auf jeden Fall der Enctype gesendet. Worauf es ankommt und ein Perl/CGI-Script.
Mit onSubmit="return false" oder true kannst Du den Modus wechseln, CGI oder Ajax.
Dasselbe in PHP, ja, wie Du selbst anmerkst, die [] nicht vergessen:
<?php
if( $_SERVER['REQUEST_METHOD'] == 'POST' ){
if( isset($_SERVER['HTTP_X_REQUEST']) ){
echo print_r($_FILES, 1);
}
else{
echo "<pre>", print_r($_FILES, 1), "</pre>";
}
exit;
}
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Upload Multiple</title>
</head>
<body>
<h1>Upload Multiple</h1>
<form action="<?php echo $_SERVER['SCRIPT_NAME'] ?>" method="POST" Enctype="multipart/form-data" onSubmit="return false">
<fieldset> <legend>Die Auswahl mehrerer Dateien ist zulässig:</legend>
<input type="file" name="file[]" multiple>
<button onClick="xup(this.form, caba)"> Auffi gehts! </button>
</fieldset>
</form>
<pre id="out"> Hier stehts Ergebnis </pre>
<script>
// Upload mit Ajax
function xup(form, caba){
var form_data = new FormData(form);
var xhr = new XMLHttpRequest();
xhr.open("POST", "<?php echo $_SERVER['SCRIPT_NAME'] ?>", true);
xhr.setRequestHeader("x-request", "ajax");
// Zur Unterscheidung Request
xhr.send(form_data);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
caba(xhr.response);
}
}
};
}
// Ajax CallBack
function caba(response){
document.getElementById('out').innerHTML = response;
}
</script>
</body></html>
hi again,
multipart mit PHP ist auch bei mir buggy, ab 3 Dateien fehlt eine und bei 10 Dateien versagt der Upload völlig, unabhängig davon, ob JavaScript FormData oder native CGI/submit.
Mein PHP ist v.5.3.0 auf XP.
Vielleicht kannst Du das verbessern. Evntl. einen eigenen Parser für den Enctype multipart entwickeln.
Schöne Grüße.
Btw., PHP läuft auf einen Stil hinaus, mit dem ich mich noch nie anfreunden konnte.
Hakuna matata!
Aus meiner Sicht ist formData also doch "buggy" - denn wenn ich was nicht gebrauchen kann, dann ist es, dass ich die gesendeten Daten je nach Art des Versandes anders auswerten _muss_.
Ein Ajax-Request mit FormData verhält sich exakt wie ein Formular, das ohne JavaScript abgeschickt wird. Bei einem Blick in deinen Quelltext ist mir allerdings aufgefallen, dass deine „herkömmliche Weise“ nicht den offiziellen Algorithmus implementiert, mit dem Formulardaten vorbereitet werden müssen. Die Methode ist also keineswegs so herkömmlich, wie du glaubst – außergewöhnlich wäre das passendere Attribut.
var form_data = new FormData(form);
Das FormData-Dingenkirchens taugt nur eingeschränkt.
Nutze es anders, also die append-Methode:
var pullfiles = function(form_data_object){
var fileInput = document.querySelector("#myfiles");
var files = fileInput.files;
var fl = files.length;
var i = 0;
var size = 0;
while ( i < fl) {
var file = files[i]; //
var nr = i + 1;
var name = 'file_nr_' + nr;
form_data_object.append(name, file, name);
i++;
}
// weiter mit dem form_data_object
}
// input type file multipart
document.querySelector("#myfiles").onchange = pullfiles;
Ungetestet.
Lieber Klaus,
wie schützt Du Dich davor, dass zu große Dateien ankommen, die von einer Dateigrößenbeschränkung in PHP verworfen werden? Für Datei-Uploads schwöre ich inzwischen auf Plupload. Das könnte Dir in mancherlei Hinsicht bestimmt helfen...
Liebe Grüße,
Felix Riesterer.