AJAX POST
Nyligen hade jag problem med att skicka AJAX "POST" med nyare versioner av Safari (5.0+) samt Google Chrome. POST -till skillnad från GET- är önskvärt om du ska skicka större datamängder med AJAX. Att skicka via GET är förhållandevis lätt. För att förstå det här jag kommer att skriva om förutsätter att du har god kännedom om både CGI programmering, JavaScript, HTML och DOM. Låt oss hoppa rakt in i det praktiska problemet.
Problem med POST
Jag fick följande felmeddelanden både i Safari och Google Chrome.
Refused to set unsafe header "Connection"
Refused to set unsafe header "Content-length"
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Att förstå vad dom två övre raderna talar om och var felet låg var inte helt lätt då jag inte märkt av att Safari har klagat på detta tidigare. För att ta det från början, här är HTML-koden:
<form name="form">
<input type="hidden" name="id" value="666" />
<textarea name="ajaxtxt" id="ajaxtxt">(Här har jag skrivit hela min C-uppsats..)</textarea>
<input type="button" value="Spara Data" onclick="sparaData('666')" />
</form>
JavaScript funktionen som tar emot sparaData():
function sparaData(id) {
var url = "ajax.cgi";
var t = document.form.ajaxtxt.value;
// Förbered att skicka MIME POST data
var boundaryString = 'capitano';
var boundary = '--' + boundaryString;
var requestbody = boundary + '\\n'
+ 'Content-Disposition: form-data; name="id"' + '\\n'
+ id + '\\n'
+ boundary + '\\n'
+ 'Content-Disposition: form-data; name="ajaxtxt"' + '\\n'
+ t + '\\n'
+ boundary + '\\n';
loadXMLDoc(url,requestbody);
}
(OBS! Detta är en del av ett mycket längre JavaScript.)
Egentligen ska det vara en avslutande "--" efter sista boundary om man ska följa MIME standarden.
Funktionen sparaData() skickar vidare till loadXMLDoc('ajax.cgi','[Allt MIME POST data]'). Tidigare versioner av Safari verkar ha behövt async = false, alltså req.open('POST', url, false). Detta gör dock numera att inget funkar med Chrome eller Safari 5.0+.
// Skicka MIME POST data
function loadXMLDoc(url,params) {
if (window.XMLHttpRequest) {
req = new XMLHttpRequest();
var boundaryString = 'capitano';
// async = true
req.open('POST', url, true);
req.setRequestHeader('Content-type', 'multipart/form-data; boundary=\"' + boundaryString + '\"');
// Dessa rader måste bort för att undvika Safari krupp
// req.setRequestHeader("Connection", "close");
// req.setRequestHeader("Content-length", params.length);
req.send(params);
req.onreadystatechange = processReqChange;
} else if (window.ActiveXObject) {
req = new ActiveXObject("Microsoft.XMLHTTP");
if (req) {
alert('IE XMLHTTP');
req.onreadystatechange = processReqChange;
var boundaryString = 'capitano';
req.open('POST', url, true);
req.setRequestHeader('Content-type', 'multipart/form-data; boundary=\"' + boundaryString + '\"');
// Osäker på vad älre M$IE tycker om detta
// Nyare M$IE 8 och 9 använder sig av window.XMLHttpRequest
req.setRequestHeader('Connection', 'close');
req.setRequestHeader('Content-length', params.length);
// alert(params); // om du vill se vad som händer
req.send(params);
}
}
}
Så här ser det ut i den inkommande trafiken på servern (Apache) från Safari:
$ENV{'CONTENT_TYPE'} = multipart/form-data; boundary="capitano"; charset=UTF-8
Och så här ser ut på servern från Firefox:
$ENV{'CONTENT_TYPE'} = multipart/form-data; charset=UTF-8; boundary="capitano"
"Boundary", som innehåller den informationen från klienten jag vill komma åt, kommer på olika ställen. Safari (WebKit) lägger "charset=UTF-8" sist. Vilket sätt är rätt? Spelar det någon roll? Ja, i mitt fall gjorde det det då jag försöker filtrera ut "bounary". Rätt grep-filter för detta borde egentigen vara, sök till nästa ";" (semikolon).
$grans = $ENV{'CONTENT_TYPE'} =~ /boundary=(.*)[;]$/; (kolla detta)
Om du vill läsa mer om olika internet media typer..
Den sista raden i felmeddelandena från Safari är ett rent cgi-fel som visade sig vara det faktum att Safari och Firefox skickar "CONTENT_TYPE'" på olika sätt. Ordningen på innehållet skiljer sig åt. Detta gick dock att fixa med att skriva en ersättningssträng som filtrerade bort oönskad information. Tidigare hade jag utgått från att "CONTENT_TYPE" skulle se ut på detta sätt:
CONTENT_TYPE : multipart/form-data; boundary
Så här löste sig det problemet. Koden är skrivet i Perl
sub tolka_multipart {
if ($ENV{'CONTENT_TYPE'} =~ m/multipart\/form-data/) {
my ($grans, $rad, $index, $falttyp, $add);
$add = "";
($grans) = $ENV{'CONTENT_TYPE'} =~ /boundary=(.*)/;
$grans =~ s/"//g;
$grans =~ s/; charset=UTF-8//g; # Safari strul!
$falttyp = "";
while ($rad = <STDIN>) {
if ($rad =~ m{^Content-Disposition:\ ?form-data;\ ?name="(.*?")}ix) {
$index = $1;
$index =~ s/"//g;
chomp($index);
$form{$index} = "";
next;
}
if ($rad =~ /^\--$grans?/) {
$add = 1;
next;
}
if ($add) {
if ($rad =~ /^\--$grans?/) { $add = ""; next;}
$form{$index} .= $rad;
}
}
}
}
Det fullständiga som kommer in till Apache och CGI-scriptet som <STDIN> via "$ENV{'CONTENT_TYPE'}" är:
--capitano
Content-Disposition: form-data; name="id"
666
--capitano
Content-Disposition: form-data; name="ajaxtxt"
(Här har jag skrivit hela min C-uppsats..)
--capitano
Vad jag får ut av detta är två hash-värden som kommer från det som skickas in. Detta kan jag sedan spara i någon databas. Variablerna jag får ut är:
$form{'id'} = 666;
och
$form{'ajaxtxt'} = "(Här har jag skrivit hela min C-uppsats..)";
Lösning
Lösningen på mitt problem var att Safari inte gillar när man fipplar med "Connection" och Content-length" manuellt.
req.setRequestHeader("Connection", "close");
req.setRequestHeader("Content-length", params.length);
Dessa rader måste bort för att det ska funka att AJAX-spara POST text med Safari och Chrome. Det är bättre att låta webbläsaren ta hand om detta, Firefox behöver den inte heller.
Läs mer om XMLHttpRequest: http://www.w3.org/TR/XMLHttpRequest/
Läs mer om MIME: http://en.wikipedia.org/wiki/MIME
Författat av: Hans E Andersson, 2010-11-23