onsdag 17 mars 2010

Gzip-komprimering på Amazon S3

Varje dag går världen miste om 99 år på grund av icke-komprimerat innehåll. Genom att gzippa dina html, css och javascript-filer m.m. kan många millisekunder sparas vid varje sidladdning samtidigt som du sparar bandbredd. Nästan alla webbservrar gzippar automatiskt innehåll om klienten berättar att den stöder gzip-kompression. Tyvärr gör inte Amazon S3 detta automatiskt.

Även om inte S3 automatiskt skickar gzippade versioner av dina filer när klienten stöder gzip så kan du självklart lagra gzippade filer på S3. Du kan alltså komprimera t.ex. din css-fil lokalt och sedan ladda upp den till S3. Såhär gzippar du en fil i linux:

gzip -c  plain_text.txt > compressed.txt.gz

Detta löser dock inte problemet helt för alla klienter stöder inte gzip och kommer alltså inte kunna läsa din komprimerade css-fil och därmed inte rendera din sajt korrekt.

Den gyllene medelvägen blir att lagra både en okomprimerad och en komprimerad version på S3 med bara en liten skillnad i namnet, t.ex. style.css och style.gz.css.

Sedan kan man med php eller något annat serverspråk enkelt avgöra om klienten har stöd för gzip och i så fall välja att ladda den gzippade versionen. Här är ett exempel på hur man tar reda på detta i php:

<?php
$gz = (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false)? 'gz.' : '';
?>
<link rel="stylesheet" type="text/css" href="http://s.example.com/style.<?=$gz?>css" />

När du laddar upp den gzippade filen till S3 bör den ha "Content-Encoding: gzip" i sin header. Annars kommer inte webbläsarna förstå att innehållet är gzippat utan försöka läsa det som vanlig text.

Pusha minskade vi på detta sätt storleken på vår css med 78% och javascriptet med nästan lika mycket.

fredag 5 mars 2010

Kom igång med XML-RPC och Loopias API

UPPDATERING: Efter att jag skrivit detta inlägg upptäckte jag att Erik Petterson har skrivit en wrapper-klass till Loopias API så att man slipper tänka på XML-RPC. Du hittar phpLoopia här.

Loopia har släppt ett API som följer XML-RPC-protokollet. Detta är första gången jag använder ett XML-RPC-api, nästan alla web-API:er man stöter på idag är av typen REST. Därför tänkte jag skriva ned en snabb guide till hur man kommer igång med XML-RPC i PHP.

För att komma åt Loopias API så måste du ha ett konto hos Loopia. Om du har det kan du skapa en API-användare under Kontoinställningar > LoopiaAPI.

Vilka metoder som finns i API:t kan du se på https://www.loopia.se/api. Den metod jag tänkte att vi skulle testa är domainIsFree(username,password,domain) som låter oss kontrollera om ett domännamn är ledigt eller upptaget.

XML-RPC är inte inbyggt i PHP som standard så för att skapa en XML-RPC klient måste vi börja med att ladda ned ett bibliotek från phpxmlrpc.sourceforge.net. Ladda ned 3.0.0Beta och packa upp. Filen vi behöver ligger i "lib" katalogen och heter xmlrpc.inc.

include "xmlrpc.inc";

$username = 'användare@loopiaapi';
$password = 'MittHemligaLösenord';

$domain = "kodkodkod.se";

$xmlrpcClient = new xmlrpc_client('RPCSERV','api.loopia.se',443);
$xmlrpcMsg = new xmlrpcmsg('domainIsFree',
                           array(new xmlrpcval($username,'string'),
                                 new xmlrpcval($password,'string'),
                                 new xmlrpcval($domain,'string')));

$xmlrpcResponse = $xmlrpcClient->send($xmlrpcMsg,0,'https');

if (!$xmlrpcResponse->faultCode()) { 
   if ($xmlrpcResponse->value()->scalarVal()=="OK"){
      echo "Domänen ".$domain." är ledig";
   }else if($xmlrpcResponse->value()->scalarVal()=="DOMAIN_OCCUPIED"){
      echo "Domänen ".$domain." är upptagen";
   }else{
      echo "Ett oväntat fel inträffade";
   }
}

Koden är förhoppningsvis självförklarande. Vi skapar ett klientobjekt och vi skapar ett xmlrpc-meddelande (ett anrop till domainIsFree) som vi skickar till servern med klienten. Om vi inte får en felkod till svar så kollar vi om svaret är "OK" eller "DOMAIN_OCCUPIED".

Dokumentationen till XML-RPC for PHP hittar du här.

Om du bygger något med Loopias API har du just nu chansen att vinna en iPad.

tisdag 2 mars 2010

Anpassa koden för cacheminnen

Ska redovisa en gammal labb-rest i en kurs i Datorteknik på fredag. Labben handlar om cacheminnen så jag är ganska insnöad på det ämnet just nu.

Något jag lärt mig av labben är att väldigt små skillnader i kod kan ge enorma skillnader i körtid. Jämför t.ex. dessa två olika sätt att addera två matriser:

for(j=0; j < MATRIX_SIZE; ++j){
   for(i=0; i < MATRIX_SIZE; ++i){
      res[i][j] = a[i][j]+b[i][j];
   }
}

for(i=0; i < MATRIX_SIZE; ++i){
   for(j=0; j < MATRIX_SIZE; ++j){
      res[i][j] = a[i][j]+b[i][j];
   }
}

Enda skillnaden här är att exempel 1 loopar igenom matriserna kolumn för kolumn medan man i exempel 2 går igenom matriserna rad för rad. Båda exemplen ger exakt samma resultat men exempel nummer 2 är överlägset snabbare med vissa cacheminnen. Detta beror på att matriser lagras radvis i RAM-minnet (enligt C-standarden och många andra språkdefinitioner). När ett program refererar till en variabel som inte finns i cacheminnet så hämtas variabeln in från RAM-minnet. Dessutom hämtas närliggande data in eftersom man förmodligen kommer vilja använda närliggande data. I exempel 2 tjänar man mycket på detta eftersom man där läser i den ordning som matriserna ligger lagrade och alltså förhoppningsvis får ganska många träffar efter varje miss. I exempel nummer 1 kan det extra datat som hämtats in ha hunnit skrivas över innan man får användning av det.

Jag kodade ett snabbt test i Java där jag adderar två matriser i storleken 1000x1000 på de två olika sätten. Här är exekveringstiderna:

radvis  kolumnvis
8.0ms   27.0ms
8.0ms   30.0ms
8.0ms   27.0ms
10.0ms  28.0ms
8.0ms   28.0ms
9.0ms   27.0ms
8.0ms   29.0ms
8.0ms   27.0ms
9.0ms   27.0ms
8.0ms   27.0ms
--------------------------------
8.4ms   27.7ms

Att loopa igenom matriserna radvis är alltså i snitt mer än 3 gånger så snabbt som att göra det kolumnvis.

För den som vill lära sig mer har KTH en omfattande PDF om Cacheminnen och adressöversättning.

Posta på Twitter med PHP

Om du vill göra en statusuppdatering på twitter med php så går det alldeles utmärkt med Twitters REST API.

Här är en enkel funktion som använder cURL i php:

<?php
function tweet($twitterUser,$twitterPass,$tweet){
   $url = "http://twitter.com/statuses/update.xml";
   $data = "status=".urlencode(utf8_encode($tweet));
   $ch = curl_init($url);
   curl_setopt($ch, CURLOPT_POST, 1);
   curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
   curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
   curl_setopt($ch, CURLOPT_HEADER, 0);
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
   curl_setopt($ch, CURLOPT_USERPWD, $twitterUser.":".$twitterPass);
   curl_exec($ch);
   $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
   curl_close($ch);
   if ($httpcode == 200) {
     return true;
   }else{
     return false;
   }
}
?>

Och du anropar funktionen såhär:

<?php
   tweet("MittTwitterNamn",
         "MittHemligaTwitterLösen",
         "Missa inte mitt inlägg på http://kodkodkod.se");
?>

Om du inte har stöd för cURL på ditt webbhotell borde det vara ganska enkelt att skriva om funktionen så att den använder fopen istället.

Pusha använder vi detta sätt för att posta heta länkar till @PushaHett på twitter. Observera att denna funktion är lämpligast att använda om du vill posta statusuppdateringar på konton som du ansvarar för. Om du vill låta dina användare posta statusuppdateringar på sina egna twittersidor från din sajt bör du implementera OAuth istället för att fråga användarna efter deras lösenord.