javascript: class methode als callback handler

Zo af en toe leer je iets over een ontwikkel-taal of -omgeving waarvan je je afvraagt waarom je dat niet een paar jaar eerder wist. Dat overkwam mij toen ik bezig was met een stuk javascript waarbij ik een class had waarvan ik een methode als callback handler wilde gebruiken.

Om het probleem (en de oplossing) te illustreren heb ik een simpel voorbeeld gemaakt van zo’n situatie. Een class die de totale oppervlakte (in pixels) kan berekenen van plaatjes.

function ImageAreaCalculator(counterElement) {
    this.totalArea = 0;
    this.counterElement = counterElement;

    this.addImage = function(src) {
        var img = document.createElement('img');
        img.src = src;
        img.onload = this.onloadImage;
        document.appendChild(img);
    };

    this.addDimensions(width, height) {
        this.totalArea += (width * height);
    };

    this.updateCounter = function(counterElement) {
        counterElement.innerHTML = this.totalArea;
    }; 

    this.onloadImage = function(e) {
        var img = e.target;
        this.addDimensions(img.width, img.height);
        this.updateCounter(this.counterElement);          
    };

}

var counterElement = document.createElement('div');
document.appendChild(counterElement);
var calc = new ImageAreaCalculator(counterElement);
calc.addImage('foo.gif');

Bovenstaande code werkt helaas niet. In de methode onloadImage() zit een aanroep naar ‘this’ en op het moment dat die regel wordt uitgevoerd is ‘this’ niet meer het object ‘calc’ maar het ingeladen plaatje ‘img’.

Een workaround is dat je het object waar je de callback iets mee wilt laten doen injecteert in het img-object. De callback handler haalt dat object vervolgens weer uit het target dat wordt meegegeven in het event-object:

this.addImage = function(src) {
    var img = document.createElement('img');
    img.calc = this; 
    img.src = src;
    img.onload = this.onloadImage;
    document.appendChild(img);
};
this.onloadImage = function(e) {
    var img = e.target;
    var calc = img.calc;
    calc.addDimensions(img.width, img.height);
    calc.updateCounter(calc.counterElement); 
};

Niet echt fraai. Deze opzet vereist namelijk een ongeschreven afspraak tussen de aanroeper en callback dat er een magisch ‘calc’ object wordt gezet in het event-target.

Er is echter een manier om de onload-callback te maken zoals in dit geval gewenst is; dat ‘this’ ook echt verwijst naar de juiste instantie. Daarvoor is de bind() methode die bestaat voor elke functie (ja; ik was ook verbaasd dat een functie automatisch van bepaalde functies wordt voorzien).

Door het aanroepen van bind() op een verwijzing naar een functie creëer je een nieuwe callback die de waarde voor ‘this’ instelt op de meegegeven variabele. Hier een voorbeeld van zo’n callback:

this.addImage = function(src) {
    var img = document.createElement('img');
    var callback = this.onloadImage.bind(this);
    img.src = src;
    img.onload = callback;
    document.appendChild(img);
};
this.onloadImage = function(e) {
    var img = e.target;
    this.addDimensions(img.width, img.height);
    this.updateCounter(this.counterElement);          
};

Op het moment dat onloadImage() wordt aangeroepen heeft ‘this’ de waarde die je zou verwachten en werkt de aanroep naar addDimensions() en updateCounter() dus ook zoals je zou willen.

MySQL negeert index bij onjuiste type in where clause

Het heeft even geduurd sinds het laatste blog maar ik liep al wel een tijdje met een onderwerp om over te bloggen. Het was me namelijk een keer opgevallen dat MySQL een index niet gebruikt als het type van de kolom niet overeenkomt met de waarde waar je op zoekt.

Neem bijvoorbeeld een tabel ‘book’ met een kolom ‘isbn’ en ‘title’:

CREATE TABLE IF NOT EXISTS `book` (
 `isbn` char(13) NOT NULL,
 `title` varchar(255) NOT NULL,
 PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `book` (`isbn`, `title`) VALUES
('9789021400730', 'De Amerikaanse prinses'),
('9789045028163', 'Dit kan niet waar zijn');

Als je in deze tabel een record opzoekt aan de hand van het ISBN doe je dat normaal gesproken met de waarde tussen aanhalingstekens, want je zoekt immers op een string. In dat geval gebruikt MySQL ook netjes de gedefineerde index (de primary key in dit geval):

EXPLAIN SELECT * FROM `book` WHERE isbn = '9789045028163'
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE book const PRIMARY PRIMARY 13 const 1

Maar als je per ongeluk (of omdat je ORM-tool niet goed werkt) zoekt op een integer, wordt de index niet gebruikt:

EXPLAIN SELECT * FROM `book` WHERE isbn = 9789045028163
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE book ALL PRIMARY NULL NULL NULL 2 Using where

Het resultaat van beide queries is hetzelfde; er zal in beide gevallen 1 record worden gevonden (Dit kan niet waar zijn) maar als er gebruik wordt gemaakt van de index (primary key) is dat natuurlijk veel sneller. Zeker als er duizenden records in je tabel zitten.

Stoeien met datum/tijd en tijdzones in MySQL

Als je werkt met datum/tijd velden in MySQL ligt een DATETIME type voor de hand. Om een DATETIME veld te vullen vanuit PHP kun je eenvoudig een iso-formaat als string (‘YYYY-MM-DD HH:II:SS’) gebruiken. In de meeste gevallen voldoet dat prima. Het wordt alleen lastig als je informatie uit verschillende tijdzones komt. Of wat als alle informatie uit 1 tijdzone komt maar een periode overspant waarbinnen de klok werd verzet?

Binnen een code-omgeving (PHP/Java/JavaScript) heb je meestal wel de mogelijkheid om te rekenen met datum/tijd objecten die van zichzelf weten over welke tijdzone het gaat en of het zomertijd was of niet. Meestal werken zulke objecten onder water met een unix-timestamp en wat meta-informatie over tijdzone en zomertijd. Maar hoe zit dat in MySQL?

Je kunt natuurlijk een integer-veld in MySQL maken en daarin de unix timestamp opslaan. Dan weet je zeker dat het het juiste tijdstip is (als het wegvallen van de tijdzone-informatie acceptabel is) en sorteren op die kolom gaat ook goed. Maar een unix timestamp is niet echt lekker leesbaar. Dan liever een echte datum/tijd kolom. Maar gaat dat altijd goed?

MySQL kent 2 mogelijkheden voor het opslaan van een datum/tijd in een enkel veld. Het kan door middel van een DATETIME en een TIMESTAMP kolomtype. Een DATETIME kolom kun je zien als een tekstueel veld. De waarde wordt opgeslagen hoe je het er in stopt en wordt ook weer zo weergegeven als hoe je het er in stopte. Een TIMESTAMP kolom werkt iets anders; bij het invoegen wordt de waarde geconverteerd naar een unix timestamp en bij het weergeven wordt deze waarde weer naar een tekstuele representatie omgezet.

Het verschil in werking zie je als je een tabel aanmaakt met zowel een DATETIME en een TIMESTAMP kolom:

CREATE TABLE `test_date` (
`timestamp_column` timestamp NULL DEFAULT NULL,
`datetime_column` datetime NULL
);

Vervolgens voegen we 3 tijdstippen in die elk een uur na elkaar plaatsvinden. Niet geheel toevallig kiezen we die rond het moment dat de klok een uur terug gaat (in de nacht van 24 oktober op 25 oktober 2015) en wel om 01:30, 02:30 en weer een uur later om 02:30 als de klok dus een uur terug is. Om er zeker van te zijn dat we die tijdstippen aanduiden geven we MySQL alleen waarden in een timestamp-notatie:

INSERT INTO test_date VALUES (FROM_UNIXTIME(1445729400), FROM_UNIXTIME(1445729400));
INSERT INTO test_date VALUES (FROM_UNIXTIME(1445729400 + 3600), FROM_UNIXTIME(1445729400 + 3600));
INSERT INTO test_date VALUES (FROM_UNIXTIME(1445729400 + 7200), FROM_UNIXTIME(1445729400 + 7200));

Als we kijken hoe MySQL dit heeft opgeslagen halen we de waarden weer op:

SELECT timestamp_column, datetime_column FROM test_date;

2015-10-25 01:30:00 2015-10-25 01:30:00
2015-10-25 02:30:00 2015-10-25 02:30:00
2015-10-25 02:30:00 2015-10-25 02:30:00

Dat lijkt er prima uit te zien: de tijdstippen kloppen allemaal. Als we echter de waarden terugrekenen naar unix timestamps (zoals we ze er in gestopt hebben) blijkt dat het niet meer helemaal klopt. Door gebruik te maken van de UNIX_TIMESTAMP() bij een TIMESTAMP kolom wordt de feitelijk opgeslagen waarde gebruikt dus kun je precies zien hoe die is opgeslagen. Bij de DATETIME kolom vindt er opnieuw conversie plaats (maar daarvan wisten we al hoe hij was opgeslagen).

SELECT UNIX_TIMESTAMP(timestamp_column), UNIX_TIMESTAMP(datetime_column) FROM test_date;

1445729400 1445729400
1445736600 1445736600
1445736600 1445736600

Wat is er mis gegaan? Je zou verwachten dat in ieder geval de TIMESTAMP notatie toch wel de juiste unix timestamp zou moeten hebben? Die kan onder water immers met timestamps werken, dachten we.

Het is mis gegaan bij het invoegen; daar wordt gebruik gemaakt van de FROM_UNIXTIME() functie die de ingevoerde unix timestamp omzet naar een ‘tekstuele’ representatie en maakt daarbij gebruik van de huidige tijdzone van de server. Als die hetzelfde is als van het tijdstip dat je probeert in te voegen gaat het goed, maar aangezien we deze query tijdens de zomertijd uitvoeren, wordt bij het terugrekenen van de tekstuele representatie naar een timestamp gewerkt met de zomertijd. En dus met het moment van voor het omzetten van de klok. Vandaar dat de laatste 2 rijen hetzelfde worden opgeslagen.

Om het derde tijdstip (in ieder geval in de TIMESTAMP kolom) juist op te slaan moeten we MySQL tijdelijk een andere tijdzone laten gebruiken:

SET time_zone=’+02:00′;
INSERT INTO test_date (timestamp_column, datetime_column) VALUES (FROM_UNIXTIME(1445729400), FROM_UNIXTIME(1445729400));
INSERT INTO test_date (timestamp_column, datetime_column) VALUES (FROM_UNIXTIME(1445729400 + 3600), FROM_UNIXTIME(1445729400 + 3600));

Alsof de klok een uur terug is gezet (om 03:00):

SET time_zone=’+01:00′;
INSERT INTO test_date (timestamp_column, datetime_column) VALUES (FROM_UNIXTIME(1445729400 + 7200), FROM_UNIXTIME(1445729400 + 7200));

Als we nu de waarden ophalen als tekstuele versie is er niks veranderd en lijken de waarden ook weer gewoon te kloppen:

SELECT timestamp_column, datetime_column FROM test_date;

2015-10-25 01:30:00 2015-10-25 01:30:00
2015-10-25 02:30:00 2015-10-25 02:30:00
2015-10-25 02:30:00 2015-10-25 02:30:00

Maar als we nu de waarden ophalen als unix timestamps zie je ineens het verschil tussen de TIMESTAMP en de DATETIME kolom:

SELECT UNIX_TIMESTAMP(timestamp_column), UNIX_TIMESTAMP(datetime_column) FROM test_date;

1445729400 1445729400
1445733000 1445736600
1445736600 1445736600

Daarmee lijkt het nuttig om voor datum/tijd velden waar rekenwerk mee plaats moet vinden altijd een TIMESTAMP kolom te gebruiken. Er zijn echter situaties waar dat niet helemaal werkt; als je in 1 statement meerdere waarden wilt meegeven worden ze geacht allemaal in dezelfde tijdzone (en zomertijd of niet) te zitten anders werkt het alsnog niet. En vergeet het beperkte bereik van een TIMESTAMP kolom niet; deze accepteert alleen waarden tussen 1970 en 2038.

Hoewel niet echt goed leesbaar zou ik er dan toch voor kiezen om de unix timestamp in een INT kolom te gaan opslaan. Maak er dan wel een UNSIGNED INT van, anders krijg je al snel een overflow.

Gebruik maken van een INT zorgt er in ieder geval voor dat het voor 100% duidelijk is welke datum/tijd waarde het is. En je kunt er veilig op sorteren en mee rekenen, ook binnen MySQL. En voor de leesbaarheid kun je natuurlijk ook met FROM_UNIXTIME() spieken wat je hebt opgeslagen.

BrugOpen.nl – binnen een week van idee naar realisatie

Een week geleden kreeg ik inspiratie voor een leuk dataproject. Ik kwam er achter dat de Nederlandse Databank Wegverkeersgegevens (NDW) een bestand publiceert met daarin de actuele open/dicht status van een aantal bruggen in Noord- en Zuid-Holland.

Meteen had ik het idee om hiervan een historische database aan te leggen om op basis daarvan statistieken te berekenen. Hoe vaak zijn bruggen open? Hoe lang zijn ze dan open? Zijn ze in de spits nu echt zo vaak open? Etc etc.

Het periodiek importeren van de actuele statussen en deze in een database opslaan was zo geregeld. Een redelijk verbeus bestand terwijl er eigenlijk maar weinig informatie in zit; de bruggen die op dit moment open zijn, sinds wannneer ze open zijn (en mogelijk tot wanneer) en de GPS-coördinaten. Geen naam of plaats van de brug. Jammer. Nu bleken er zelfs na een tijdje niet zo heel veel verschillende bruggen in het bestand te hebben gezeten dus het werd een kwestie van eenmalig opzoeken welke bruggen het precies waren. Leuk klusje ook nog. Zo leer je iets van bruggen in plaatsjes waar je nog nooit van had gehoord.

Vervolgens bleek ook dat de actuele status niet zo actueel was als ik dacht. Het bleek een statisch bestandje dat elke 5 minuten werd geactualiseerd. En hoewel dat in principe vaak genoeg zou kunnen zijn, bleek de inhoud niet te gaan over de afgelopen 5 minuten maar een momentopname op het tijdstip van genereren. En aangezien een brug binnen die 5 minuten open en dicht zou kunnen zijn gegaan kan het gebeuren dat dat onopgemerkt bleef in de omliggende momentopnamen.

Dat laatste mag de pret niet drukken. De data wordt geïmporteerd en een eerste versie van de website is klaar: brugopen.nl. Daar staat in ieder geval al een overzicht van de bruggen die op dit moment open zijn en vervolgens een lijstje met alle bruggen die ooit in het bestand hebben gezeten met daarbij vermeld wanneer (en hoe lang) de brug voor het laatst open was.

Een volgende stap was het automatisch publiceren van status­wijzigingen (brug open, brug dicht) via Twitter. Dat bleek ook niet eens zo heel moeilijk, er is veel documentatie en voorbeeldcode beschikbaar om dit binnen korte tijd voor elkaar te krijgen.

Al met al dus binnen een week van idee naar realisatie.

Volg @OpenBrug op Twitter of check brugopen.nl.

Rabo OmniKassa werkt nu ook met SNI

Het blijkt dat mijn blogje ook door de Rabobank wordt gelezen. Schreef ik eerder al dat het een probleem kon op leveren als je automaticResponseUrl op HTTPS staat die alleen werkt met SNI (Server Name Indication), dat probleem blijkt nu opgelost.

In de logs zie ik dat de automatische requests nu worden uitgevoerd door Java 8, waar dat voorheen door Java 6 werd gedaan:

XXX.XXX.XXX.XXX – – [18/Jun/2015:14:42:05 +0200] “POST /?module=SHOP&action=ProcessPayment&orderId=XXX HTTP/1.1” 302 – “-” “Java/1.8.0_25”

Mooi dat het is opgelost.

Root certificaat in je certificate chain?

Als je een certificaat aanvraagt krijg je bij de oplevering daarvan vaak een zip-bestand met daarin het certificaat, één of meer intermediate certificaten en een root-certificaat.

Het is echter raadzaam om het root-certificaat niet in te regelen in de webserver. Dat scheelt namelijk bandbreedte bij het opzetten van de SSL handshake en het voegt niks toe.

Dat zit zo; een browser is standaard voorzien van een verzameling root-certificaten die door de browser worden vertrouwd. Certificaten die zijn uitgegeven met die root-certificaten worden dus ook vertrouwd. En certificaten die zijn uitgegeven door intermediate certificaten die zijn ondertekend met een van de bekende root-certificaten worden ook vertrouwd. Enzovoort. Een certificaat uit een keten waarvan het root-certificaat niet wordt vertrouwd wordt door de browser niet vertrouwd, ook niet als je het meestuurt. Dus waarom zou je het meesturen?

Sterker nog; door het wel mee te sturen zorg je voor extra traffic bij het opzetten van de SSL verbinding. In een setup waar ik aan werkte was er sprake van een keten van in totaal 4 certificaten; het domein-certificaat, 2 intermediate certificaten en 1 rootcertificaat.

Met het volgende commando kon ik zien hoe veel bytes er werden verzonden bij het opzetten van de SSL handshake:

$ echo | openssl s_client -connect www.domein.nl:443 \
2>/dev/null | grep handshake
SSL handshake has read 6107 bytes and written 512 bytes

Volgens OpenSSL waren er dus 6107 bytes ontvangen en 512 bytes verstuurd. Dat lijkt niet veel, maar dat zijn dus al de nodige roundtrips alvorens er ook maar 1 HTTP request kan worden uitgevoerd.

Door het root-certificaat weg te laten uit de certificate chain werd dat al een stuk minder:

$ echo | openssl s_client -connect www.domein.nl:443 \
2>/dev/null | grep handshake
SSL handshake has read 5022 bytes and written 512 bytes

Dat scheelt dus dik 1000 bytes wellicht een aantal roundtrips.

Om te zien hoe veel TCP-pakketjes er worden verstuurd bij het opzetten van een SSL handshake kun je het volgende commando uitvoeren:

# tcpdump -ttttt -i any 'port 443 and host www.domein.nl'

Als je 2 terminal-vensters open hebt op een testclient kun je in de ene de tcpdump starten en in een ander venster kun je een SSL-verbinding opzetten:

openssl s_client -connect www.domein.nl:443

Dit commando blijft ‘hangen’ omdat het na het opzetten van de SSL-connectie wacht op input. Dat geeft je even de tijd om de output van tcpdump te bekijken.

In mijn situatie scheelde dit precies 1 roundtrip en een verwaarloosbare hoeveelheid tijd. Hoewel het in dit geval niet veel oplevert laat ik voortaan de root-certificaten achterwege. Hebben toch geen nut.

Geheugengebruik van integers in Java

De Java-engine van een van onze applicaties bevat een gefacetteerde zoekmachine die zijn data in-memory houdt om snel resultaten te kunnen leveren. De indexen van die zoekmachine bestaat uit een aantal Maps en Sets gevuld met Integers. Dat blijkt echter niet zo’n heel efficiënte manier te zijn als je kijkt naar het geheugengebruik.

Om te testen hoe veel geheugen een Set met Integers gebruikt heb ik deze testcode die een Set met 100.000 integers samenstelt. Door de hoeveelheid gebruikt geheugen daarvoor en daarna te vergelijken valt te meten hoe veel geheugen die Set kost:

package memtest;

import java.util.HashSet;
import java.util.Set;

public class MemTest {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        Set set = new HashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Dit leverde bij mij na een aantal keer draaien de volgende output:

Extra mem: 6655960
Extra mem: 6655960
Extra mem: 6655944
Extra mem: 6727368
Extra mem: 6742968
Extra mem: 6658232

Een Set met 100.000 Integers kost in dit geval dus ongeveer 6MB oftewel gemiddeld 66 bytes per stuk. Hoe komt dat? Een integer is toch maar 4 bytes?

Het korte antwoord is dat als je een Set vult met integers, deze automatisch worden ‘geboxed’ naar Integer objecten en dat een object meer geheugen kost dan een ‘primitive’ integer. Bij een geringe hoeveelheid items in zo’n Set zul je dit niet snel merken maar in ons geval hebben we tientallen sets met tienduizenden integers. Dan loopt het geheugengebruik al snel op.

Een oplossing hiervoor is geen gebruik te maken van een Set (die kan namelijk alleen met objecten overweg) maar bijvoorbeeld gebruik te maken van Trove. Die biedt een keur aan Collections klassen die onder water gebruik maken van primitieve variabelen. Dat is niet alleen sneller maar kost ook nog eens minder geheugen.

Om dit te testen passen we bovenstaande code aan naar het volgende:

package memtest;

import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;

public class MemTestTrove {

    public static long getUsedMem() {

        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();

    }

    public static void main(String[] args) {

        long initialMem = getUsedMem();

        TIntSet set = new TIntHashSet();

        for (int i = 1; i <= 100000; i++) {
            set.add(i);
        }

        long finalMem = getUsedMem();

        System.out.println("Extra mem: " + (finalMem - initialMem));

    }

}

Na deze code een aantal keer te draaien blijkt er nog minder dan een derde van de hoeveelheid geheugen nodig is en ook verrassend constant is:

Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208
Extra mem: 2136208

Dat scheelt! Code die sneller draait en minder geheugen kost. Maar ja; dat levert wel een extra dependency op. En een ‘rare’ interface want een TSet is niet hetzelfde als een Set uit de standaard API. In ons geval konden we de publieke API ‘schoon’ houden door daar gebruik te blijven maken van de standaard Set interface maar onder water worden de Trove classes gebruikt.

 

 

Rabo OmniKassa ondersteunt geen SNI bij automatic­Response­Url

Per 1 april 2015 stopt de Rabobank met de Rabo Internetkassa. Als vervanger daarvan is er de door de Rabobank zelf ontwikkelde OmniKassa. Werkt ongeveer hetzelfde; het initiëren van transacties gebeurt door een client naar de omgeving van de OmniKassa te sturen met een ‘signed’ request met behulp van een geheime sleutel. De OmniKassa controleert of de ondertekening klopt en biedt de client vervolgens de mogelijkheid om af te rekenen. Nadat er is betaald komt men terug op de website en kun je controleren of de betaling is geslaagd.

Nu wil het gebeuren dat de client na het betalen direct weg klikt en niet terug komt op de website. Om in die gevallen toch te zien dat er is betaald kun je bij het initiëren van de transactie een automaticResponseUrl meegeven die de OmniKassa zelf moet aanroepen om de transactiestatus door te geven.

Maar nu komt het; als je webshop draait op HTTPS werken de aanroepen naar de automaticResponseUrl alleen als die zonder SNI (Server Name Indication) werkt. De OmniKassa (althans het deel dat de automatische notificaties uitvoert) draait op Java 6. Dat kan ik in de logs zien bij de transacties waarbij de automatische notificaties wel werkten:

XXX.XXX.XXX.XXX – – [17/Feb/2015:09:32:08 +0100] “POST /?module=SHOP&action=ProcessPayment&orderId=XXX HTTP/1.1” 302 – “-” “Java/1.6.0_45”

Van Java 6 is bekend dat deze geen SNI ondersteunt. In mijn geval kon ik het probleem oplossen door de automaticResponseUrl te laten wijzen naar een ander domein dat wel werkt zonder SNI. Een structurele oplossing kan alleen van de kant van Rabobank komen; door gebruik te maken van een HTTP client die ook SNI ondersteunt voor HTTPS-requests. Het is immers 2015 en in de huidige stand van de techniek ondersteunt 99% van onze bezoekers SNI. De OmniKassa echter nog niet.

Ik begrijp uiteraard dat dat niet zomaar geregeld is maar ik zou in de tussentijd in ieder geval de documentatie van de OmniKassa voorzien van een melding dat voor requests naar de automaticResponseUrl geen SNI kan worden gebruikt.

– Edit: Rabobank heeft een update uitgevoerd waardoor SNI nu wel wordt ondersteund.

Now accepting HTTP/2 connections

This blog now runs on a server that accepts HTTP/2 connections. It is H2O 1.0.0 with a proxy to Apache/PHP.

Since H2O can only proxy to HTTP/1 using plain (no HTTPS) connections, WordPress needs to be told not to redirect to the canonical url. This can be done by adding the following line to wp-config.php: $_SERVER['HTTPS'] = 1;

Building H2O requires quite some knowlegde of Linux. And I wouldn’t recommend using it in production yet. But it surely is fun to experiment with.

Another day – another WordPress Blog

I’m a developer. I know I should be able to build my own blogging software. But over the years I have learnt to stand on shoulders of others. The WordPress community is a great one. And even though a WordPress site is a honeypot for malware and hackers, I prefer to just start blogging than spend more time coding than actually writing.

So here it is. On a fresh VPS. HTTPS and IPv6 in place. Enjoy.