04 augusti, 2010

XHTML/Javascript: Ej blockerad nedladdning och optimering av websida när Javascript och CSS används

English title: Non-Blocking download and optimization of webpages when using Javascript and CSS

Förord
Är du webbutvecklare/webbdesigner som vill att din websida ska ladda snabbare? Använder du skript som tar lång tid att ladda? Då kanske du finner din lösning här. Fortsätt läs för att få veta mer.


Introduktion
När en webbsida laddas i en webbläsare skickas det ett anrop för varje skript, stilmal och bild som existerar i koden för webbsidan. När skript och stora stilmallar måste hämtas och läsas in tar det tid. Detta blockerar webbläsaren från att fortsätta tills nedladdningen av filerna är klara. Om man då har ett stort skript som måste laddas in först, börjar text och bilder inte synas förrän den har laddat klart.

Lösningen? Komprimera skript och stilmallar för att minska på filstorleken och försök få allt att laddas ned samtidigt. Försök också att ha så får anrop som möjligt. Webbläsarna använder mestadels GET- och POST-anrop och de bör hållas till ett minimum.

Tipsen i denna artikel kan även användas på webbsidor anpassade för mobiltelefoner och t.ex iPad, som är begränsade till hastigheter inom GSM/GPRS/3G/4G som varierar mycket beroende på var man befinner sig geografiskt. De har även i många fall sämre RAM och CPU-kraft som krävs för att rendera webbsidor. Färre anrop förbättrar upplevelsen.


Tester
Jag har utfört tre tester på min egna webbsida med följande uppsättning:
  • Firefox 3.6.8
  • Firebug 1.5.4
  • PHP (957 b)
  • XHTML Strict
  • CSS (883 kB)
  • Javascript (310 kB)
  • Stor bild (2.7 MB)

Att tänka på innan man börjar
Javascript brukar vara stora och ta upp mycket utrymme samt ha tunga beräkningar. Se till att komprimera alla skript så de tar upp så lite utrymme som möjligt. Då går det snabbare att ladda in dem.

Min fil med Javascript är bara komprimerad till hälften och innehåller många olika skript och bibliotek som har infogats i en enda fil istället för att använda flera.

Istället för:
<script type="text/javascript" src="skript1.js"></script>
<script type="text/javascript" src="skript2.js"></script>
<script type="text/javascript" src="skript3.js"></script>
<script type="text/javascript" src="skript4.js"></script>
<script type="text/javascript" src="skript5.js"></script>

Använder jag:
<script type="text/javascript" src="alla_skript.js"></script>

Detsamma gäller också för alla mina stilmallar. Använd inte "@import" i CSS för att ladda in externa stilmallar. Det skapar extra anrop i vissa webbläsare. Ni bör avstå från det. Det bästa är att sammanfoga alla stilmallar till en enda stilmall.


Komprimering
Många webbapplikationer, webbtjänster och skript erbjuder möjligheten att komprimera (minska) filernas storlek. Här är ett exempel på en sökning

För er som vill testa på att komprimera skript med PHP kan läsa följande artikel http://www.devirtuoso.com/2009/07/how-to-compress-cssjavascript-files-with-php/


Laddningstider och blockering av laddning
http://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/ är en av de bästa artiklarna jag har läst som beskriver vad ämnet handlar om och vad man kan använda för att lösa problemen.

En lösning som jag har börjat använda är enkel att använda och effektiv. Metoden kallas "Script DOM Element" och går ut på att man med hjälp av lite Javascript, inuti head-sektionen på en webbsida, kan skapa en skript-tagg dynamiskt när webbsidan börjar laddas. Man anger att källan (eng. source, xhtml. src) ska peka mot skriptfilen.

Då skapar man parallell nedladdning i webbläsaren, det vill säga att skriptet laddas ned samtidigt som resten av sidan.

Viktigt! Lösningen med "Script DOM Element" varierar mellan olika webbläsare och jag rekommenderar att ni läser artikeln jag nämnde ovanför och tar en titt på bilden här http://stevesouders.com/efws/images/0405-load-scripts-decision-tree-04.gif Det har att göra med om skriptet ligger på samma domän som webbsidan eller inte, samt om inbäddade skript är beroende av skriptet eller inte.


Förklaring av mitt test
Jag utförde tre tester i webbläsaren Firefox version 3.6.8 och till min hjälp använde jag Firebug version 1.5.4 för att ta tid och för eventuell felsökning. I Firebug kan man se fina grafer över nedladdningen av webbsidan. Man kan se vilka delar i koden som tar längst respektive kortast tid att utföra. Mer information om tester hittar ni under rubriken Scenario.

Exempel på graf i Firebug

(Klicka på bilden för att förstora)

I graferna kan man se vilket svar man fick från webbservern (Response, GET, POST, 404, 200, 304, m.fl) samt tiden. Det bästa är om stilmallar, skript och bilder kan laddas ned parallellt (samtidigt).

Viktigt
Innan varje test utförs måste man rensa webbläsarens cache och Internetfiler för den aktuella dagen. Annars får man cachade filer vilket ger ett vilseledande resultat. I Firefox görs det via menyn:
  1. Verktyg
  2. Rensa ut tidigare historik
  3. Välj Idag
  4. Kryssa för Besökta sidor och filhämtningshistorik samt Cache.



Så glöm inte rensa webbläsarens cache!!

Testerna påverkas även av din Internethastighet. En annan notering är att kommentarerna i min kod i testet är på engelska för att hjälpa engelska besökare som läser artikeln.


Scenario
Preparera en webbsida med lite text, en stor bild som helst ska vara minst 1MB, en stor stilmall och ett stort Javascript. Då ser man skillnanden tydligt och millisekunder är svårt att mäta eftersom det kan skilja ganska många från test till test. Ni kan referera till min kod som jag visar under testerna här nedanför.

I vanliga fall laddas objekten och resurserna i följande ordning.
  1. CSS och Javascript laddas parallellt
  2. Bilder laddas sist, ibland även text.
Men som jag sagt tidigare i artikeln, blockerar oftast Javascriptet resten av webbsidan från att börja ladda.


Test 1
En vanlig implementation av en webbsida.

Följande kod användes i testet:
<?php
//
// Test 1
// File: test.php
//
header("Cache-Control: no-cache");
header("Pragma: no-cache");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Testa laddning av Javascript</title>
<link rel="stylesheet" type="text/css" media="all" href="css2.css" />
<script type="text/javascript" src="javascript2.php"></script>
</head>
<body>
<p>Text som visas på hemsidan snabbt.</p>
<p><img src="stor_bild.jpg" alt="stor bild" width="700" /></p>
<p>Sista texten</p>
</body>
</html>

Resultat - Test 1

(Klicka på bilden för att förstora och se resultatet)

Beskrivning av resultatet
Vi ser att stilmallen och Javascriptet laddas parallellt medan bilden laddas senare (sist). På vissa webbsidor kan skripten vara sega och då tar det längre tid för hela webbsidan att laddas. Detta vill vi ändra på.


Test 2
Nu använder vi tekniken "Script DOM Element" för både stilmallen och Javascriptet. Tänk på att även använda taggen "noscript" om en besökare har en webbläsare med Javascript avstängt. Då måste de fortfarande kunna använda stilmallen som annars slutar fungera eftersom den inte kan laddas korrekt.

Följande kod användes i testet:
<?php
//
// Test 2
// File: test2.php
//
header("Cache-Control: no-cache");
header("Pragma: no-cache");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />

<title>Testa laddning av Javascript</title>
<noscript>
<link rel="stylesheet" type="text/css" media="all" href="css2.css" />
</noscript>
<script type="text/javascript">
/* <![CDATA[ */

// If you are using both CSS and JS DOM load, then you need to put the CSS link also inside a noscript tag

// CSS
var domscript_css = document.createElement('link');
domscript_css.rel = "stylesheet";
domscript_css.type = "text/css";
domscript_css.media = "all";
domscript_css.href = "css2.css";
document.getElementsByTagName('head')[0].appendChild(domscript_css);

// Javascripts
var domscript = document.createElement('script');
domscript.type = "text/javascript";
domscript.src = "javascript2.php";// Contains all Javascript
document.getElementsByTagName('head')[0].appendChild(domscript);
/* ]]> */

</script>
</head>
<body>
<p>Text som visas på hemsidan snabbt.</p>
<p><img src="stor_bild.jpg" alt="stor bild" width="700" /></p>

<p>Sista texten</p>
</body>
</html>

Resultat - Test 2


(Klicka på bilden för att förstora och se resultatet)

Beskrivning av resultatet
Nu ser vi att hela webbsidan laddas samtidigt och stilmallen och Javascriptet laddas ned parallellt. Detta kallas på engelska för "Non-Blocking Javascript" eftersom det gör att resten av webbsidan inte blockeras. Notera att det bara tar så lång tid det tar att läsa in filen test2.php och bilden. Vi har just nu minskat våra laddningstider med en hel sekund (1 sekund) om vi jämför med föregående Test 1.


Test 3
I det sista testet länkar jag till stilmallen precis som vanligt men Javascriptet laddas in dynamiskt. Ett tips är att alltid ladda in stilmallar innan skript.

Följande kod användes i testet:
<?php
//
// Test 3
// File: test3.php
//
header("Cache-Control: no-cache");
header("Pragma: no-cache");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Testa laddning av Javascript</title>
<link rel="stylesheet" type="text/css" media="all" href="css2.css" />
<script type="text/javascript">
/* <![CDATA[ */

// Javascripts
var domscript = document.createElement('script');
domscript.type = "text/javascript";
domscript.src = "javascript2.php";// Contains all Javascript
document.getElementsByTagName('head')[0].appendChild(domscript);
/* ]]> */

</script>
</head>
<body>
<p>Text som visas på hemsidan snabbt.</p>
<p><img src="stor_bild.jpg" alt="stor bild" width="700" /></p>

<p>Sista texten</p>
</body>
</html>

Resultat - Test 3

(Klicka på bilden för att förstora och se resultatet)

Beskrivning av resultatet
I det sista testet var det stilmallen som tog tid att ladda in och blockerar därför resten av webbsidan.


Analys
Testen visade tydligt vilken skillnad det var om man använde tekniken eller inte, eller om man blandade tekniken med vanlig kodning. Tiderna i resultaten kan variera men ni får gärna räkna ut ett genomsnittlig tid för varje test och sedan jämföra dem.

Nu testade jag med en extremt liten webbsida med endast två stycken och en bild. Stilmallen lästes in men användes inte på webbsidan. Om man testar med en större webbsida, tror jag man får bättre resultat och man upptäcker nog även enklare eventuella prestandaförbättringar.

Kontrollera även cache mm
Om det tar tid att ladda in koden skriven i HTML/XHTML varje gång en sida laddas, ska man tänka på följande:
  • Hämtas data från ren HTML?
  • Hämtas data från en databas (XML, MySQL, MS Access, MS SQL Server, ...)?
  • Används cache på servern?
  • Används cache på klienten?
  • Hur länge är ett objekt eller en resurs cachad?
  • Är data komprimerat eller i klartext?
  • Är data krypterad (ex. HTTPS, CHAP)?
Cachad data hjälper till att öka prestandan oerhört mycket och det märks tydligt på användningen av minne och processor på servern. Kryptering och omkodning av data kan också ta tid. Komprimera data om det är möjligt.

Snabbt om data redan finns i cache
Testerna visade att det bara tog några millisekunder att ladda webbsidan om man glömde rensa cache och Internetfiler innan man utförde varje test.

Tänk på antalet besökare
Om man inte har så många skript tror jag inte man behöver tänka på detta, men då måste man istället tänka på hur många besökare som besöker webbsidan. Om väntetiden är för lång när man laddar en webbsida (absolut max 10 sekunder enligt vissa rapporter) förlorar man besökare. För många företag betyder det förlust i intäkter och mindre pengar.

Låsning och blockering
Om ett skript låser sig i en evig loop eller om skriptet måste beräkna tunga data innan webbsidan laddas, blockeras resten av innehållet.

Kommer det fungera på din webbsida?
Kontrollera först noga om metoden ovan i testerna kommer fungera på just din webbsida eller i ditt scenario. Det finns vissa begränsningar som beskrivs i de andra artiklarna jag länkade till.

Testat i andra webbläsare?
Testerna har endast utförts i Firefox för att jag inte har haft tillgång till andra webbläsare, utom Internet Explorer. Jag vet inte riktigt om det finns något insticksprogram tillInternet Explorer


Slutsats
Min slutsats av testerna och analysen är att man först måste tänka på om det lönar sig att använda metoden. Metoden fungerar bra och jag kommer använda den när jag utvecklar mina webbapplikationer.

Lycka till

24 mars, 2010

PHP/MySQL: Konvertera från datetime till sekunder från epoch

English translation:MySQL and PHP Convert datetime to seconds

Undrar du hur man kan omvandla data från en kolumn i MySQL med datetime som datatyp till antalet sekunder från "epoch" (1970-01-01)? Det undrade jag och nu tänker jag dela med mig av lösningen.

Jag gillar att programmera med PHP och utveckla dynamiska hemsidor tillsammans med MySQL som backbone. Just nu för en liten stund sedan letade jag efter en lösning för att omvandla datetime till sekunder för att enklare formatera datum med PHP funktionen date().

Notis:
Jag vet att det finns utmärkta lösningar inbyggda i MySQL men i koden jag använder nu går det inte att lösa på det sättet. Det datum som returneras från databasen är alltid i formatet yy-mm-dd tt:mm:ss

<?php
echo strtotime('2010-03-24 12:26:00') .'<br/>';
echo strtotime('2010-03-24 12:27:00') .'<br/>';

// Resultat
// 1269458760
// 1269458820

// Verifiera att det fungerar
echo date('Y-m-d H:i:s', strtotime('2010-03-24 12:27:00') ) .'<br/>';

// Resultat
// 2010-03-24 12:27:00

// Ett annat format
echo date('l F j, Y, G:i', strtotime('2010-03-24 12:27:00'));

// Resultat
// Wednesday March 24, 2010, 12:27
?>

Men för er som verkligen vill lösa det på MySQL-viset kommer här ett exempel:

// Skriver ut 1269458820
SELECT UNIX_TIMESTAMP('2010-03-24 12:27:00')

Ganska enkelt eller hur? Vill ni veta mer om funktionen strtotime() [1] i PHP så hittar ni information i den grymma dokumentationen på www.php.net [2]

Lycka till med programmeringen

Referenser
[1] www.php.net/manual/en/function.strtotime.php
[2] www.php.net

10 mars, 2010

Nätverk: ARP - Address Resolution Protocol i datanätverk

Hej!

För er som undrar över hur kommunikation fungerar i ett nätverk mellan två enheter (t.ex två datorer) genom en switch och router kan ni läsa mitt svar här nedanför som jag gav till en utländsk student vid Halmstad högskola som jag hade i kursen IP-telefoni och trådlösa nätverk när jag var kursansvarig. Texten är på engelska. Om jag får in några kommentarer som vill ha en svensk översättning så fixar jag det.

Studenten frågade efter hur en PC känner till sin destinations IP-adress och hur datapaketen tar sig dit, utan att man specificerar subnätmasken i ICMP (ping) meddelandet. Allt har att göra med protokollet ARP - Address Resolution Protocol.

Till min hjälp har jag använt programmet Packet Tracer 5.2 för att felsöka kommunikationen. Jag har även bifogat en simpel topologi över nätverket.

--------------------------


(Klicka på bilden för att förstora)
--------------------------

Question: How does a PC know about the destination IP-address and how to get to it without sending the subnetmask in the ICMP (ping) message?

Answer:
Hi,
I tried a scenario in Packet Tracer just now and here it how it works.
  1. I choose to ping from 192.168.1.10 to 192.168.1.11
  2. ARP constructs a request for target IP-address 192.168.1.11, and sets the broadcast MAC-address to FF:FF:FF:FF:FF:FF
  3. ARP encapsulates PDU into Ethernet frame
  4. Frame goes to switch on the wire
  5. Switch receives frame on port fa0/2
  6. Switch processes frame up to OSi layer 2 "Data link"
  7. It is a broadcast (FF:FF:FF:FF:FF:FF), send out on all switch ports except the receiving port, so this is sending packets to router (192.168.1.1) and PC (192.168.1.11)
  8. Packets are sent out fa0/1 and fa0/3
  9. Router receives ARP request on port fa0/0
  10. The ARP request's target IP address does not match the receiving port's IP address, (192.168.1.11 is not matching routers IP of 192.168.1.1)
  11. The ARP process on the router checks the routing table whether the requested IP address is reachable, and the IP-address is reachable on source port fa0/0

    Router# show ip route
    C 192.168.1.0/24 is directly connected, FastEthernet0/0
    C 192.168.2.0/24 is directly connected, FastEthernet0/1

  12. ARP table on router is updated with souce IP-address (192.168.1.10) and MAC-address
  13. Router discards ARP-request
  14. Now the destination PC receives the ARP request on its ethernet port from the switch as a broadcast (FF:FF:FF:FF:FF:FF)
  15. The ARP request's target IP address matches the receiving port's IP address. (192.168.1.11 is matching 192.168.1.11)
  16. ARP-process on destination PC updates the ARP-table.
  17. ARP-process on destination PC replies to source PC with source port MAC-address, and encapsulates PDU into ethernet frame.
  18. Switch port fa0/3 receives request and looks in its ARP-table.
  19. The frame source MAC-address was found in its ARP-table.
  20. This is a uniucast frame (from PC 192.168.1.11 to PC 192.168.1.10) so switch looks in ARP-table for destination MAC-address
  21. Switch is sending unicast reply out fa0/2 port
  22. PC 192.168.1.10 receives ARP-reply on ethernet port and does now know about the PC 192.168.1.11 MAC-address on OSI layer 2
  23. It saves the information (mapping) MAC-address to IP-address.
  24. PC 0 can now send an ICMP (ping) message to PC 1 using only IP-addresses on OSI layer 3 "Network".

Now, if the ping is going to IP-address 192.168.2.10, then the ICMP-message will reach the router (which MAC-address is also in PC A's ARP-table). The router will check the routing table for destination IP-address network, if it's reachable. It is reachable through port router's fa0/1 to network 192.168.2.0/24

Then the ARP-process will start all over again on the other side, so the router will ask the switch, which in turn will broadcast the ARP out all switchports except receiving port (fa0/1). The request is going out fa0/2 to PC 2 with IP 192.168.2.10 and the IP is correct. An ARP-reply is then sent back to the router. ICMP (ping) can now continue.

I really recommend you to test Packet Tracer to look at TCP/IP and ARP messages going. You can also debug DHCP and some other protocols.

-----------------------------

Innan jag svarade studenten på hans fråga, trodde jag att jag kunde ARP, men efter en djupdykning i protokollet fick jag mer kunskap. Nu vet jag mycket bättre själv hur kommunikationen i ett datanätverk går till. Visst kunde jag detta utantill när jag studerande CCNA vid högskolan och gymnasiet men det är alltid bra att fräscha upp minnet lite.

Lycka till alla

Välkomna till bloggen

Välkomna ska ni vara kära besökare. Jag skriver om IT för att det är kul och för att jag vill dela med mig av information och kunskap. Jag försöker hålla bloggen så kategoriserad som möjligt för att ni enklare ska hitta intressanta länkar och artiklar.