Svart bälte i mod_rewrite

Idag blir det lite åsikter, lite praktisk teknik, och en gammal PDF. För säkerhets skull ska jag väl säga att åsikterna här på bloggen bara är mina egna och inte nödvändigt delas av organisationer, myndigheter och liknande som jag jobbar med/för/åt/mot.

Det verkar som om riksdagens webbplats har ändrat URL:er¹ för all information som fanns i rixlex-databasen (som numera inte officiellt kallas för rixlex, utan bara ”Dokument” — mycket märkligt att bara slänga bort ett så inarbetat varumärke). Tidigare var exempelvis URL:en för upphovsrättslagen följande:


http://rixlex.riksdagen.se/htbin/thw/?%24%7BOOHTML%7D=SFST_DOK&%24%7BSNHTML%7D=SFSR_ERR&%24%7BBASE%7D=SFST&BET=1960%3A729&%24%7BTRIPSHOW%7D=format%3DTHW

Nu är den följande:


http://www.riksdagen.se/webbnav/index.aspx?nid=3911&bet=1960:729

Jag är inte imponerad. Okej, den nya håller sig åtminstone inom 72 tecken, vilket gör det åtminstone tänkbart att det går att skicka länken via mail utan att den radbryts sönder. Lycka till med att hålla den i huvudet, läsa upp den över telefon eller skriva ner den på en servett, dock. Den är fortfarande teknikspecifik (.aspx) och avslöjar onödiga detaljer om det bakomliggande systemet.

(Inom parantes är det intressant att om man går via den ”rätta” vägen, dvs www.riksdagen.se -> dokument -> Lagar -> Svensk författningssamling -> sök på ”Beteckning”, så får man samma resurs, men följande URL:


http://www.riksdagen.se/webbnav/index.aspx?nid=3911&dok_id=SFS1960:729&rm=1960&bet=1960:729

92 tecken pga upprepad information. Varför anges parametrarna dok_id och rm när de inte innehåller någon information som inte finns i bet?)

Om man försöker gå till den gamla adressen får man, tillsammans med den nya rätta länken, ett felmeddelande som går ut på att besökaren uppmanas att meddela webmastern för den webbplats länken han klickade på fanns om att länken ändrats. Ett tips: Det kommer aldrig att hända. Jag gissar att det är gjort i avsikt om att fasa ut de gamla länkarna. Ett tips till: Det kommer inte att gå. Har man en gång publicerat något på en URL är man skyldig att underhålla den URL:en tills universum imploderar.

Jag skrev om det här för ett drygt år sedan i ett sammanhang, men lade visst aldrig ut PM:en. Här är den:
Om indirekta gränssnitt till myndigheters webbtjänster

Jag försöker efterleva den här regeln själv. När jag för några veckor sedan flyttade den här bloggen till ett nytt webhotell passade jag på att se över mina gamla URL:er. Jag började med att kolla vilka URL:er som efterfrågades, genom att gå igenom webserverloggar:

$ cat access_log | awk '{ print $7, $9 }' | sort|uniq -c | sort -n
[...]
 639 /SyndicationService.asmx/GetRss 404
 702 / 200
 845 /favicon.ico 404
 891 /feed/ 200
 915 /feed/feed 304
 929 /wp-content/staffan.jpg 200
 946 /feed/atom/ 200
1082 /wp-content/themes/journalized/layout.css 200
1083 /wp-content/themes/journalized/skins/sand-skin.css 200
1087 /wp-content/themes/journalized/style.css 200
1218 /misc/quickies_of_the_day.html 200
1426 /feed/atom 304
1661 /feed/ 304
1960 /unfiled/forhandstitt-pa-lagennu-20.html/feed/ 200
2233 /feed 200
2490 /feed/atom/ 304
3261 /robots.txt 404
3969 /wp-comments-post-blahonga.php 200
4039 /wp-comments-post-blahonga.php 403

Ok, mycket vanliga accesser där. Låt oss filtrera ut alla 404:or, dvs anrop efter sidor där servern svarat ”finns inte”

$ cat access_log | awk '{ print $7, $9 }' | sort|uniq -c | sort -n
[...]
  11 /script.aculo.us 404
  13 /PermaLink.aspx?guid=ec7b7df6-3f0a-4653-9409-47adef5b4c20 404
  14 /PermaLink.aspx/b5eb341d-353b-4c95-8e3c-3451a4e5dce3 404
  16 /PermaLink.aspx?guid=e9422f27-b552-44a7-9ac0-1e8a3d03b99b 404
  18 /law/lagen.nu/1974:152 404
  19 /law/more_on_information_law.html 404
  21 /wp-comments-post.php 404
  23 /PermaLink.aspx/2b58e629-47ec-4a13-8a28-0b438a1e5d5b 404
  26 /misc/lagen.nu_now_public.html 404
  30 /default.aspx 404
  41 /PermaLink.aspx?guid=4ffa3efe-aa74-49d9-9f7a-c4f23983dc86 404
 132 /wp-comments-post.php?UNIQUE=253f7b5d921338af34da817c00f42753 404
 211 /index.atom 404
 374 /Trackback,guid,.aspx 404
 489 /misc/bra-bloggar-daliga-floden.html 404
 518 /running/still_burning_those_calories.html 404
 639 /SyndicationService.asmx/GetRss 404
 845 /favicon.ico 404
3261 /robots.txt 404

Hmm. Vissa av de här har aldrig fungerat och ska inte fungera — förmodligen dåligt skrivna sökspindlar och/eller spamrobotar som försöker. Vissa andra saker ska fungera, men gör det inte. Vi kan börja med den här:


http://blog.tomtebo.org/PermaLink.aspx?guid=ec7b7df6-3f0a-4653-9409-47adef5b4c20

Det är en rest från tiden då jag körde dasBlog. Varje blogginlägg fick en permalänk baserat på ett GUID. Enkelt, men inte så snyggt. När jag bytte från dasBlog till blosxom blev istället permalänkarna på den här formen


http://blog.tomtebo.org/misc/blog_updated.html

Mycket snyggare. Permalänken baseras nu på kategori (”misc”) och en URL-anpassad version av inläggsrubriken (”blog_updated”). Men det finns forfarande problem. Exempelvis är det en implementationsdetalj att blosxom bygger på statiska .html-filer, det ska inte behöva synas i URL:en. Och vad händer om jag vill publicera ett nytt inlägg i kategorin ”misc” med samma inläggsrubrik?

När jag bytte från Blosxom till WordPress behöll jag den här permalänkstrukturen. Inte förrän i samband med webhotellsflytten fixade jag om det till dagens moderna och supersmidiga:


http://blog.tomtebo.org/2004/05/04/blog_updated/

Här har jag tagit bort kategorin ur URL:en (ett inlägg kan tillhöra mer än en kategori, och numera använder jag snarast taggar för att klassificera inlägg), för att ersätta det med en datumstämpel. Den säger mer, och som en added bonus är den hackable. Pröva följande:

http://blog.tomtebo.org/2004/05/04/
http://blog.tomtebo.org/2004/05/
http://blog.tomtebo.org/2004/

(Vad mycket mer jag skrev på den tiden, förresten — fyra inlägg på en dag!). Att definera sin permalänkstruktur i wordpress är en enkel inställning – bara att ange en formatsträng i stil med ”/%year%/%monthnum%/%day%/%postname%/”.

Så. Mitt gamla inlägg om att uppgradera bloggprogramvara har funnits på tre helt skilda URL:er. Det är ju dåligt. Men som jag skrev: Har man en gång publicerat något på en URL är man skyldig att underhålla den URLen tills universum imploderar.

mod_rewrite to the rescue. Den här apachemodulen är suverän på att ta en URL som efterfrågas, mangla om den, och vidarebefordra förfrågningen till resten av apache.

Men det funkar bäst när den nya URL:en kan konstrueras från information i den gamla — att redirecta /1960:729 till /1960/729.html (som jag gör på lagen.nu) är en smal sak. Men hur redirectar man /PermaLink.aspx?guid=ec7b7df6-3f0a-4653-9409-47adef5b4c20 till /2004/05/04/blog_updated/?

Jo, i små steg. När jag bytte bort dasBlog var jag förutseende nog att spara en lista som listade GUIDs kopplat till inläggsrubriker. Den listan (guid-redirect.map) ser ut ungefär såhär:

104dd495-6cec-4da9-9532-27f6e7c7fc98 /index.php?category_name=music&name=nine_cancels_stockholm_gig_this_saturday
1e2d1f2d-689e-4d2e-81e4-b1a661991e22 /index.php?category_name=programming&name=wiki_as_a_documentation_tool
25552627-a3bb-4088-bdf1-7a59edf5b03e /index.php?category_name=music&name=entombed_turbonegro_show_impressions
4e142c41-5287-434d-8124-f64f74dfc905 /index.php?category_name=music&name=all_music_sites_suck
077618b1-3eeb-419b-a886-f29878f9eac5 /index.php?category_name=music&name=pet_peeve_of_the_day

(Vad mycket mer rock det var på den här bloggen en gång i tiden). Notera att högerledets permalänkstruktur inte stämmer överens med någon av de tre jag använt mig av — den här är i princip en sökning direkt i wordpressdatabasen, på nycklarna category_name och name. Man kan gå till en länk i stil med http://blog.tomtebo.org/index.php?category_name=misc&name=blog_updated och man kommer bli redirectad till rätt ställe, tack vare wordpress-pluginen Permalink redirect.

Hur använder man den här listan med mod_rewrite? Man använder RewriteMap-direktivet. Like so:

RewriteEngine On
RewriteMap guids txt:guid-redirect.map
RewriteCond %{QUERY_STRING} ^guid=(.*)
RewriteRule PermaLink\.aspx ${guids:%1|undefined.guid} [R=301,L]

För den som inte är helt hemma på grundläggande mod_rewrite-magi: Ett RewriteRule-direktiv specar hur en inkommande URL (första argumentet, här PermaLink\.aspx) ska skrivas om till en utgående URL (andra argumentet, här ${guids:%1|undefined.guid}), och hur den omskrivningen ska göras (flaggor inom hakparanteser, här R=301,L). Ett RewriteRule-direktiv tillämpas bara om eventuella föregående RewriteCond-direktiv är uppfyllda.

Så, om och endast om omgivningsvariabeln QUERY_STRING (den delen av URL:en som följer ?) är satt till något som matchar guid= följt av något, ta detta något och ersätt det med resultatet från en uppslagning i guid-redirect.map. (Eller undefined.guid om inget hittas – vilket kan hända om den lista jag sparade i dasBlog->blosxom-bytet inte är fullständig. I så fall har vi tappat information och jag har förstört webben lite grann. Mea culpa.)

Redirecten syns utåt, eftersom flaggan R=301 betyder ”gör en HTTP-redirect med statuskod 301, vilket betyder Moved Permanently”. En sökmotorspindel, bokmärkessystem eller annat som blir uppmärksammad på detta ska i teorin sluta försöka använda den gamla URL:en och gå över till den nya. Inte för att de gör det, men JAG gör iallafall rätt på MIN sida. Pfft!

Men vänta, det kommer mer! Blosxom hade ett system för att mappa bloggpostrubriker till URL:er och WordPress har ett annat. Skillnaden består främst i att blosxom konverterar skiljetecken till underscore, medans wordpress tar bort dem. När jag gjorde migreringen kompenserade jag inte 100% för detta. För vissa poster fanns det på blosxom-tiden inte ens en entydning mappning mellan rubrik och URL. Av den anledningen hade det här inlägget först en URL på formen /misc/showcenter_review.html, och senare på formen /misc/product_review__pinnacle_showcenter.html.

Så genom att gräva fram en gammal blosxomdatabackup och diffa den mot wordpressdatabasen kunde jag konstruera följande nya mappningsfil (slug-redirect.map):

...
showcenter_review                               product_review__pinnacle_showcenter
smalltalk_risk_and_success_stories              smalltalk__risk_and_success_stories
some_parts_of_microsoft_still_doesn_t_get_it    some_parts_of_microsoft_still_doesn__t_get_it
stockholm_marathon_2004_race_report             stockholm_marathon_2004__race_report
stockholm_marathon_three_weeks_left             stockholm_marathon__three_weeks_left
...

(Åååh, stockholm marathon, nu blir jag nostalgisk – tänk när man tyckte en vanlig marathon var något att vara rädd för). Svårigheten med URL-mappningen nu är att den ska bara göras för vissa URL:er – de som finns i slug-redirect.map. Alla andra ska lämnas orörda. Det fixas lätt genom följande mod_rewrite-konfiguration. Jag låter kommentarerna tala för sig själv.

RewriteMap slugs txt:slug-redirect.map
# This supports the old custom wordpress permalink structure (that was
# created to mimick the older Blosxom permalink structure) -- it's a
# lot of rules, but the only one *really* neccesary is the first one.

# Not really needed per se since the Permalink Redirect thing does this now
# (and in a better way, since it rewrites it directly to
# wordpress-defined permalink structure) -- but it is needed for the
# filename-to-post-slug-adjustment magic further down. The Permalink
# Redirect comes in at a later stage and does an externally visible
# 301 redirect to the /yyyy/mm/dd/post-slug/ style URL, so the ugly
# "index.php?category_name=foo&name=bar" urls are not directly visible
# to the user.
RewriteRule ^(.+)/([^/]+).html(/[0-9]+)?/?$ /index.php?category_name=$1&name=$2&page=$3 [QSA]
RewriteRule ^(.+)/([^/]+).html/trackback/?$ /index.php?category_name=$1&name=$2&tb=1 [QSA]
RewriteRule ^(.+)/([^/]+).html/feed/(feed|rdf|rss|rss2|atom)/?$ /index.php?category_name=$1&name=$2&feed=$3 [QSA]
RewriteRule ^(.+)/([^/]+).html/(feed|rdf|rss|rss2|atom)/?$ /index.php?category_name=$1&name=$2&feed=$3 [QSA]
RewriteRule ^(.+)/([^/]+).html/page/?([0-9]{1,})/?$ /index.php?category_name=$1&name=$2&paged=$3 [QSA]

# Additional adjustment from old Bloxsom filename based URLs to WordPress "post slug" based urls
# This is deep mod_rewrite magic
# First, see if the querystring matches the "normal"
# wordpress way of finding a post through category + name
RewriteCond %{QUERY_STRING} ^category_name=([^\&]+)&name=([^\&]+)
# then see if the name exists in the slug redirect map
RewriteCond ${slugs:%2} ^.+$
# if so then check the querystring again to populate %1 and %2
RewriteCond %{QUERY_STRING} ^category_name=([^\&]+)&name=([^\&]+)
# and rewrite it
RewriteRule ^index\.php$ index.php?category_name=%1&name=${slugs:%2}

RewriteMap kan mer än att bara slå upp saker i textfiler – Jag nämde tidigare Permalink Redirect. Hade den pluginen inte funnits hade det gått att lösa med en RewriteMap av typen prg och ett specialskrivet script som gräver fram den saknade datuminformationen från wordpressdatabasen. Det hade säkert varit lärorikt, men jag slipper ändå gärna tacksamt att behöva skriva det.

Svårare än så är det inte. Viktiga punkter att ta med sig från det här:

  1. Greppa dina loggfiler för att ta reda på vilka URL:er som efterfrågas men inte hittas.
  2. RewriteMap kan lösa alla dina problem så länge som du inte tappat bort information som behövs för att mappa gamla URL:er till nya
  3. Har man en gång publicerat något på en URL är man skyldig att underhålla den URLen tills universum imploderar.

¹) Jo, jag har hört att det numera egentligen inte finns något som heter ”URL”. Det här inlägget är web-PK nog som det är utan att jag blandar in begreppet ”URI”.

4 reaktioner till “Svart bälte i mod_rewrite”

Kommentarer kan inte lämnas på detta inlägg.