19 Apr/11
Kategorier: Exempel
Jag har använt Vim som min primära text-editor de senaste 8-10 åren och skulle nog finna det väldigt svårt att byta till något annat. Trots detta så känner jag mig halvt handikappad då jag använder vanliga standard vi i t.ex FreeBSD eller ArchLinux. Detta beror på att jag förväntar mig att saker som att kunna flytta markören med piltangenterna inte fungerar i insert-mode utan diverse ändringar i min ~/.vimrc.
Många finner det svårt att förstå hur folk kan använda Vim då de är vana att kunna markera, klippa ut och flytta markören med mus-pekaren pga editorer så som notepad och Word. Faktum är att du blir betydligt mer produktiv om du tar som vana att endast använda tangentbordet.
Jag föredrar att vim har diverse simpla funktioner som syntax highlighting, att vim kommer ihåg vart i filen jag senast var, automatiskt intenderar kod (tips: :set paste), att vim konverterar text från ISO till UTF-8 on-the-fly, osv. För att få detta beteende, spara min ~/.vimrc och lägg den i din hemma-mapp.
" #####
" #
" # File: /usr/local/share/vim/vimrc, $HOME/.vimrc
" # Author: Jesper Wallin (jesper@ifconfig.se)
" # Date: 22-09-2011
" #
" ###################
" # VIM default
" ###################
set nocompatible
" ###################
" # Fix backspace
" ###################
set bs=2
" ###################
" # Auto-indenting
" ###################
set ai
" ###################
" # Hilight searches
" ###################
set hlsearch
" ###################
" # Use .viminfo
" ###################
set viminfo='20,\"500
" ###################
" # Command history
" ###################
set history=50
" ###################
" # Show cursor
" ###################
set ruler
" ###################
" # Disable modeline
" ###################
set nomodeline
" ###################
" # Encode UTF-8
" ###################
if v:lang =~ "utf8$" || v:lang =~ "UTF-8$"
set fileencodings=utf-8,latin1
endif
" ###################
" # Don't use Ex mode, use Q for formatting
" ###################
map Q gq
" ###################
" # Syntax Highlighting
" ###################
syntax on
" ###################
" # Tweaks for xterm
" ###################
if &term=="xterm"
set t_RV= " don't check terminal version
set t_Co=8
set t_Sb=^[4%dm
set t_Sf=^[3%dm
endif
" ###################
" # Move to last position
" ###################
au BufReadPost * if line("'\"") > 0 && line("'\"") <= line("$") |
\ exe "normal g'\"" | endif
" ###################
" # Remove trailing whitespaces on PHP files.
" ###################
autocmd BufWritePre *.php :%s/\s\+$//e
Uppdatering: Har lagt till highlight på sök samt att den tar bort mellanslag i slutet på alla rader i PHP-filer.
För att få denna konfiguration att fungera system-wide, döp om filen till vimrc, utan punkten innan. Därefter flyttar du filen till /etc (se man vim för mer information om detta)
Kommentera gärna och berätta vilken editor som är din favorit och varför. Om det råkar vara vim, vad för konfiguration använder du? :-)
(Visa kommentarer)
Permalink
Gå till toppen
03 Jan/11
Kategorier: PHP, Exempel, Prestanda
Varför hämta filer parallellt?
I normala fall när du surfar runt på webben så hanterar webbläsaren alla nerladdningar. Alla moderna webbläsare hämtar de filer (CSS, JavaScript, osv) som finns på en webbplats parallellt. Dvs, alla filer hämtas samtidigt istället för att nerladdningarna ska behöva vänta på varandra innan de kan börja.
Låt säga att du hämtar information från andra webbplatser för att lagra och sedan presentera den på din egna webbplats. Ett exempel kan vara affiliatenätverk, som ofta erbjuder information om produkter via XML-filer. Här kan det vara trevligt att hämta all data parallellt för att snabba upp processen.
Ett annat exempel kan vara en robot som vill hämta tusentals dokument för att sedan indexera. Om detta inte skulle ske parallellt så skulle detta ta enormt mycket längre tid.
PHP och libcurl
PHP (och många andra språk) har stöd för biblioteket libcurl. Med libcurl är det väldigt enkelt att hämta filer genom olika protokoll samtidigt som det är riktigt kraftfullt och flexibelt eftersom du kan konfigurera det på så många olika sätt.
Normalt när du hämtar en fil med libcurl använder du följande:
<?php
// initiate libcurl.
if ($ch = curl_init('http://www.ifconfig.se/'))
{
// configure libcurl.
curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_MAXREDIRS, 32);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// execute libcurl.
$body = curl_exec($ch);
// close nicely.
curl_close($ch);
}
?>
Detta är ganska så mycket kod för något som file_get_contents() kan göra på en rad. Dock kräver file_get_contents() att allow_url_fopen är aktiverat i php.ini, vilket enligt mig är en säkerhetsrisk. Dessutom får du betydligt mer kontroll med libcurl och du kan även konfigurera libcurl att hämta komprimerade filer, vilket i sig spar bandbredd.
Att hämta filer parallellt med libcurl
För att hämta filer parallellt måste man först initiera ett objekt, som vi sedan kan lägga till vanliga curl objekt till. När vi lagt till alla objekt så använder vi oss av curl_multi_exec() för att hämta alla filerna samtidigt.
<?php
// urls to fetch.
$urls = array(
'http://www.ifconfig.se/',
'http://www.facebook.com/',
'http://www.twitter.com/',
'http://www.youtube.com/',
'http://www.google.com/'
);
$total = count($urls);
// initiate the multi-handler.
$cm = curl_multi_init();
// loop through all urls and initiate all objects.
for($i=0;$i<$total;$i++)
{
// initiate a libcurl object for each url.
$ch[$i] = curl_init($urls[$i]);
// configure each object.
curl_setopt($ch[$i], CURLOPT_AUTOREFERER, 1);
curl_setopt($ch[$i], CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch[$i], CURLOPT_MAXREDIRS, 32);
curl_setopt($ch[$i], CURLOPT_HEADER, 0);
curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1);
// add this object to the multi-handler.
curl_multi_add_handle($cm, $ch[$i]);
}
// execute the multi-handler.
while(!isset($threads) || $threads > 0)
{
curl_multi_exec($cm, $threads);
}
// loop through the result and close nicely.
for($i=0;$i<$total;$i++)
{
// get the result and some useful information.
$body = curl_multi_getcontent($ch[$i]);
$info = curl_getinfo($ch[$i]);
// remove the handler and close it.
curl_multi_remove_handle($cm, $ch[$i]);
curl_close($ch[$i]);
// print some information/statistics.
printf("%s: %s (%ssec, %sKB, %s redirects)\n",
$info['http_code'],
$info['url'],
round($info['total_time'], 2),
round($info['size_download'] / 1024, 2),
$info['redirect_count']
);
}
// close the multi-handler.
curl_multi_close($cm);
?>
Detta script kommer köra alla 5 förfrågningar samtidigt och därefter skriva ut lite information om varje URL. Med funktionen curl_getinfo() får vi ut användbar information om själva förfrågan till servern samt lite meta-data så som storlek, kodning, osv. Om du har PHP installerat för CLI, så kan du köra det direkt i shellet.
php ./curl-multi.php
200: http://www.ifconfig.se/ (0.48sec, 56.07KB, 0 redirects)
200: http://www.facebook.com/common/browser.php (1.08sec, 11.24KB, 1 redirects)
200: http://twitter.com/ (1.9sec, 43.29KB, 1 redirects)
200: http://www.youtube.com/ (0.62sec, 79.82KB, 0 redirects)
200: http://www.google.se/ (0.62sec, 10.54KB, 1 redirects)
Avslut
Att använda libcurl gör att man får enormt mycket kontroll över själva filhämtningen samtidigt som det är smidigt att konfigurera. Det stödjer dessutom enormt många protokoll så som HTTP, FTP, SSH/SCP, IMAP, POP3, m.fl.
En annan smidig funktion med libcurl är att oavsett om du hämtar en fil som är komprimerad (gzip, deflate, etc), så kommer alla cURL-funktioner ändå returnera den okomprimerade filen. Detta gör att man enkelt kan ignorera hela processen med att kontrollera om filen är komprimerad eller inte.
Hur använder du själv cURL och framför allt, i vilket språk använder du det i? Personligen har jag främst använt cURL ihop med PHP, då många av de andra språken jag använder har egna funktioner för just det jag behöver.
(Visa kommentarer)
Permalink
Gå till toppen
02 Jan/11
Kategorier: Webb, JavaScript
Introduktion
För en tid sedan kom jag i kontakt med ett ganska så ovanligt problem. Jag skulle ladda ett JavaScript i en lightbox, som i sin tur laddades av ett JavaScript. Dvs, jag skulle skriva ut en <script>-tag med hjälp av JavaScript.
Både Chrome och Firefox slutade att läsa mitt JavaScript så fort jag skrev ut en </script>-tag, då båda webbläsarna helt korrekt antog att JavaScriptet var slut.
<script type="text/javascript">
$(document).ready(function(){
$.lightbox('<script type="text/javascript" src="/some/file.js"></script>');
});
</script>
Koden ovan kommer sluta läsas av webbläsaren efter den första </script>-taggen och därmed tolka scriptet som felaktigt, då det avbryts mitt i en funktion.
Lösningen på problemet
Jag har tidigare sett denna lösning användas på många webbplatser, men jag har alltid varit övertygad om att det har varit ett annonsnätverk eller liknande som försöker rundgå plugins så som AdBlock, NoScript, osv.
<script type="text/javascript">
$(document).ready(function(){
$.lightbox('<scr' + 'ipt type="text/javascript" src="/some/file.js"></scri' + 'pt>');
});
</script>
Genom att helt enkelt bara dela taggen </script> till två strängar och sen sätta ihop dem med hjälp av JavaScriptet, så tolkar webbläsaren det som </scri och pt> istället för </script> och avslutar därmed inte scriptet.
Som en liten notering kan det väl vara värt att nämna att ett JavaScript i en lightbox inte kommer köras oavsett och nu i efterhand kan jag väl lugnt säga att det var en ganska så kass approach på det hela. ;-)
(Visa kommentarer)
Permalink
Gå till toppen
30 Dec/10
Kategorier: Webb, SEO, Google
Vad är Cloaking?
Cloaking är det begrepp Google använder när man inte presenterar samma innehåll till Google som till övriga användare. Google ser allvarligt på detta och du riskerar att bli borttagen helt ur SERP:en om du använder dig av cloaking.
Om du inte vill att Google ska besöka eller indexera vissa delar av din webbplats, så rekommenderar jag istället att du använder dig av robots.txt eller en robots meta-tag.
Hårdare tag mot Cloaking i början av 2011
Google kommer, enligt Matt Cutts, att ta hårdare tag mot cloaking i början av 2011.
För att kontrollera om man behandlar Googlebot annorlunda på något sätt så kommer man nu även granska alla headers och redirects på webbplatsen. Min gissning är också att denna ändring kommer vara bestående om den fungerar bra.
Personligen ser jag inte varför man skulle behöva hantera Googlebot på något annorlunda sätt jämfört med vanliga användare. Om detta är ett måste så kanske det är dags att se över vilka metoder man använder för att få sin sida indexerad. ;-)
(Visa kommentarer)
Permalink
Gå till toppen
26 Dec/10
Kategorier: PostgreSQL, Prestanda
Vad är Fulltext sök?
Fulltext sök är en funktion som erbjuder möjligheten att söka i en tabell och hitta de rader som matchar en specifik fråga. Fulltext låter dig även ranka resultatet efter vilket relevans och likhet frågan har till texten man söker i.
Fulltext sök låter dig göra en mer fördjupad sökning än vanliga funktioner så som LIKE, som enbart söker efter ett ord eller en fras i texten. LIKE är dessutom väldigt prestandakrävande eftersom den inte använder något index, utan måste gå igenom alla rader i tabellen vid varje sökning.
Så som PostgreSQL har implementerat Fulltext, så tar den även hänsyn till vilket språk som används och kan därför också ta hänsyn till hur olika ord är böjda (ringa vs ringer vs ringde). Den ignorerar även så kallade stopwords, ord som används för ofta för att vara användbara vid en sökning (och, är, har, osv). Den klarar även av att hantera synonymer (glad vs lycklig) vilket man också kan använda för att korrigera vanligt förekommande stavfel.
Datatyper och normalisering
PostgreSQL använder datatyperna tsvector och tsquery för att hantera den data som används när du utför en sökning. Typen tsvector innehåller den representation av texten som man ska söka i, medan tsquery innehåller motsvarande data för frågan.
to_tsvector()
För att kunna använda den oformaterade texten i en vanlig kolumn så finns funktionen to_tsvector() som konverterar text till datatypen tsvector. En tsvector innehåller en sorterad lista med alla unika lexemer och vilken position i texten ordet har. Lexemer är normaliserade ord där alla stopwords blivit borttagna och synonymer samt böjelser har blivit sammanslagna.
SELECT to_tsvector('swedish', 'Sex laxar i en vaxad laxask');
to_tsvector
------------------------------------------
'ask':6 'e':4 'lax':2,6 'sex':1 'vaxa':5
to_tsquery()
Funktionen to_tsquery() används för att skapa en lista av de lexemer som man ska söka efter i en tsvector. Denna listan är också sorterad efter & (AND), | (OR) och ! (NOT) som används för att matcha fler/färre rader. Det går även att gruppera dessa operatorer med hjälp av parenteser.
SELECT to_tsquery('swedish', 'laxarna & vaxar & asken');
to_tsquery
-----------------------
'lax' & 'vax' & 'ask'
Man kan även använda funktionen plainto_tsquery() som konverterar oformaterad text till en tsquery, dock kan man inte använda sig av & (AND), | (OR) eller ! (NOT) här.
SELECT plainto_tsquery('swedish', 'laxarna vaxar asken');
plainto_tsquery
-----------------------
'lax' & 'vax' & 'ask'
Gemensamt för funktionerna ovan är att det första argumentet anger vilken konfiguration man ska använda. Du kan lista alla standardkonfigurationerna genom att skriva \dF[+] i psql. Beroende på vilken konfiguration du använder så normaliseras texten olika.
Att utföra en sökning
För att göra en sökning så använder man operatorn @@ och jämför tsvector med tsquery och får då tillbaks true eller false beroende på om dom matchar eller inte.
SELECT to_tsvector('swedish', 'Sex laxar i en vaxad laxask') @@
plainto_tsquery('swedish', 'laxar') AS result;
result
--------
t
Åter igen så hanterar hanterar Fulltext olika böjelser av orden:
SELECT to_tsvector('swedish', 'Sex laxar i en vaxad laxask') @@
plainto_tsquery('swedish', 'laxarna vaxar asken') AS result;
result
--------
t
För att söka i en kolumn, t.ex posts, så anger man helt enkelt kolumnen:
SELECT to_tsvector('swedish', posts) @@
plainto_tsquery('swedish', 'sökord') AS result;
result
--------
t
Ranka resultatet
När man söker och matchar flera hundra eller tusen rader så kan det vara smidigt att sortera efter relevans. För att göra detta använder man funktionerna ts_rank() och ts_rank_cd().
SELECT title, ts_rank_cd(to_tsvector('swedish', content), query, 16) AS rank
FROM posts, to_tsquery('swedish', 'FreeBSD') query
WHERE query @@ to_tsvector('swedish', content) ORDER BY rank DESC;
title | rank
----------------------------------------+-----------
Virtualisera med KVM i Ubuntu 9.10 | 0.0887506
Ports i FreeBSD | 0.0729145
FreeBSD 8.0 och rc.conf | 0.0132641
Båda funktionerna tar hänsyn till hur ofta ordet nämns i texten, textens storlek samt även hur viktig delen av texten är där ordet förekommer. Det som skiljer de båda funktionerna åt, är att ts_rank_cd() även tar hänsyn till hur många ord som finns mellan varje sökord, dvs, hur hög densitet texten har. (cd = cover density)
Du kan även ange hur viktig olika delar av texten du söker i är, samt ange hur längden på texten ska påverka rankingen. För mer information om detta, läs dokumentationen.
Markera sökord i resultatet
Något som sökmotorer ofta gör är att presentera sökresultat där man markerar sökorden med fet stil eller liknande. På så sätt kan man enkelt se hur frågan man ställt är relaterad till texten den matchat. Funktionen ts_headline() gör just detta:
SELECT ts_headline('swedish',title, query), rank FROM
(SELECT title, query, ts_rank_cd(to_tsvector('swedish', title), query, 16) AS rank
FROM posts, to_tsquery('swedish', 'FreeBSD | rc.conf') query
WHERE query @@ to_tsvector('swedish', title) ORDER BY rank DESC) AS foo;
ts_headline | rank
---------------------------------------+----------
<b>FreeBSD</b> 8.0 och <b>rc.conf</b> | 0.1
Ports i <b>FreeBSD</b> | 0.063093
<b>FreeBSD</b> 8.0 och sysctl.conf | 0.05
Anledningen till att man använder en sub-query här är för att ts_headline() självklart måste använda original texten för att markera de ord man söker efter. Om du inte använder en sub-query måste detta göras på alla rader i tabellen, vilket är väldigt prestandakrävande beroende på hur mycket data du har.
Hur texten ska markeras och antalet ord som ska finnas med före och efter sökordet går att konfigurera på en mängd olika sätt, jag kan även här rekommendera att du läser igenom dokumentationen.
Begränsningar
Även om denna implementation av PostgreSQL är riktigt kraftfull så har den självklart en del begränsningar. Något som jag rent spontant kan sakna, är möjligheten att söka på exakta fraser. Man kan självklart använda & (AND), men detta betyder ju inte att orden står i följd. Det andra är mer tekniska begränsningar som har med storlek att göra:
- Storleken på varje lexem får max vara 2KB
- Storleken på tsvector (alla lexemer och dess position i texten) får max vara 1MB
- Max antal lexemer måste vara mindre än 264
- Positionen på en laxem måste vara högre än 0 och mindre än 16,383
- Max antalet position en lexem kan ha är 256
- Antalet noder (lexermer och operatorer) i en tsquery 32,768
Avslut
Jag är personligen kär i hur PostgreSQL har implementerat ett Fulltext sök. Den är oerhört snabb, skalar enormt bra, väldigt kraftfull och går att konfigurera på en mängd olika sätt. Jag använder just denna implementation av Fulltext sök för att hitta relaterade artiklar på inlägg/artiklar jag gör här på bloggen. På så sätt uppdateras det automatiskt och jag får faktiskt artiklar som är relaterade på riktigt och inte som många WordPress plugin gör och använder kommentarer(!?) ihop med kategorier/taggar för att avgöra om innehållet är relaterat eller inte.
Ett annat tips är att skapa en kolumn med datatypen tsvector och köra to_tsvector() vid INSERT och UPDATE istället. På så sätt har du redan normaliserat texten och slipper därför göra detta varje gång du ska söka i tabellen. Du bör även skapa ett GIN-index om du har mycket data för att maximera prestandan.
Notera också att jag har ändrat mina Fulltext konfigurationer en aning, så resultaten ovan kan skilja lite från standardinställningarna i PostgreSQL.
(Visa kommentarer)
Permalink
Gå till toppen