Eigener HTML-Validator

Um HTML-Dokumente auf ihre W3C-Konformität hin zu überprüfen, ist der offizielle W3C-Validationsdienst häufig die erste Wahl. Eine häufige Benutzung bringt jedoch Probleme mit sich. Daher soll nachfolgend kurz erleutert werden, wie man sich mit Hilfe von Chef einen eigenen Validator installiert und diese mit dem w3c_validators-Gem verwendet.

Motivation

Es ist durchaus wünschenswert, sämtliche HTML-Dokumente auf ihre W3C-Konformität hin zu überprüfen, bevor sie online gestellt werden (warum?). Das kann zum Teil automatisiert (z.B. mit RSpec) oder auch manuell passieren. Zu diesem Zweck gibt es vom W3C-Konsortium den offiziellen Markup Validator.

Hier bei Nix-wie-weg® verwenden wir RSpec und Capybara um alle unsere Seiten automatisch auf valides HTML5 zu testen. Dabei ist es in der Vergangenheit häufig zu Problemen mit Timeouts des offiziellen W3C-Validators gekommen, mit der Folge, dass unsere Tests nicht mehr durchliefen. Außerdem lassen dass die Betreiber dieses Dienstes auch keine unbegrenzte Anzahl von Anfragen eines einzelnen Anschlusses zu.

Nachdem funktionierende Tests ein integraler Bestandteil unseres Workflows sind, war es dringend notwendig, eine bessere Lösung zu finden: Wir haben unseren eigenen Validations-Dienst aufgesetzt.

Installation eines eigenes Validierungs-Webdienstes

Kontext

Der Validator, wie er z.B. auch auf der offiziellen Webseite verwendet wird, besteht eigentlich aus zwei Diensten: einem CGI-Skript, das die Validierung von HTML bis einschließlich Version 4 übernimmt und einem separaten Dienst Validator.nu. Letzterer ist für uns von wesentlichem Interesse, da man damit HTML5 validieren kann. Es handelt sich dabei um eine Java-Anwendung auf Jetty-Basis, die später dann zusätzlich zum Webserver gestartet werden muss.

Vorab sei noch gesagt, dass das Debian-Paket leider hoffnungslos veraltet und deswegen für unsere Zwecke nicht tauglich ist.

Installation mit Chef

Wir werden die Installation anhand eines Chef-Rezepts durchführen. Die Schritte kann man aber natürlich auch manuell durchführen; sie sollten selbsterklärend sein. Wir verwenden hier ausschließlich Debian, aber der Package-Provider von Chef sollte auch mit anderen Distributionen klarkommen. Die Paket-Namen sind dort aber unter Umständen abweichend.

# cookbooks/w3c_validator/recipes/default.rb
required_packages = %w(mercurial subversion libsgmls-perl perl-doc zlib1g zlib1g-dev g++ fcgiwrap)

required_packages.each do |p|
  package p
end

Die beiden Dienste sollen nach /usr/local/w3c-validator und /usr/local/validator.nu installiert werden:

# cookbooks/w3c_validator/recipes/default.rb
validator_nu_home = '/usr/local/validator.nu/'
w3c_validator_home = '/usr/local/w3c-validator'

[validator_nu_home, w3c_validator_home].each do |dir|
  directory dir do
    owner 'www-data'
    group 'www-data'
    mode 00755
    recursive true
  end
end

Die aktuelle Version des Validators gibt es auf Github. Chef hat leider bisher noch keinen Deploy-Provider für Mercurial, der das Klonen eines Mercurial-Repositorys für uns übernimmt. Deshalb klonen wir das Repository manuell:

# cookbooks/w3c_validator/recipes/default.rb
execute "clone validator.nu" do
  command <<-EOH
    hg clone https://bitbucket.org/validator/build build
    cd validatur.nu
    # Ja, wirklich zweimal
    python build/build.py all
    python build/build.py all
  EOH
  user 'www-data'
  cwd validator_nu_home
  environment ({ 'JAVA_HOME' => '/usr/lib/jvm/java-6-sun' })
  creates "#{validator_nu_home}/build"
end

Wir benötigen zunächst noch ein Init-Skript, um den Dienst einfach starten und stoppen zu können.

# cookbooks/w3c-validator/files/default/validator-nu-rc
# Debian Init-Skript für Validator.nu

### BEGIN INIT INFO
# Provides:          validator-nu
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Should-Start:
# Should-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: HTML5 validation service
# Description:       HTML5 validation service
### END INIT INFO


. /lib/lsb/init-functions

DIR="/usr/local/validator.nu"
CONTROL_PORT=8887
PIDFILE="/var/run/validator.pid"

DIETIME=10 # Time to wait for the server to die, in seconds
           # If this value is set too low you might not
           # let some servers to die gracefully and
           # 'restart' will not work


# Include defaults if available
if [ -f /etc/default/$NAME ] ; then
    . /etc/default/$NAME
fi

DAEMONUSER="www-data"

case $1 in
  start)
    log_daemon_msg "Starting the process" "$NAME"
    if start-stop-daemon --start\
        --chuid $DAEMONUSER --chdir $DIR --background\
        --exec $DIR/build/build.py -- --control-port=$CONTROL_PORT run
    then
      log_end_msg 0
    else
      log_end_msg 1
    fi
   ;;
  stop)
    log_daemon_msg "Stopping the process" "$NAME"
    netcat localhost $CONTROL_PORT
    ;;
  restart)
    $0 stop && sleep 2 && $0 start
    ;;
  *)
    echo "Usage: $0 {start|stop|restart}"
    exit 2
    ;;
  esac
exit 0
# cookbooks/w3c_validator/recipes/default.rb
cookbook_file "/etc/init.d/validator-nu" do
  source 'validator-nu-rc'
  owner 'root'
  group 'root'
  mode 00755
end

service 'validator-nu' do
  action %w(enable start)
end

Es ist wichtig an dieser Stelle den Parameter –control-port zu setzen, da der Dienst sich sonst nicht in den Hintergrund forkt und sich dazu auch nicht per Start-Stop-Daemon oder nohup dazu überreden lässt. Wir nehmen hier an, dass der Dienst später unter dem Benutzer ‘www-data’ laufen soll.

Es steht noch die Einrichtung des W3C-Validators aus. OpenSP wird als Abhängigkeit benötigt, ist jedoch bei Debian nicht in einer aktuellen Version verfügbar.

# cookbooks/w3c_validator/recipes/default.rb
remote_file "#{w3c_validator_home}/validator.tar.gz" do
  source 'http://validator.w3.org/validator.tar.gz'
  mode 00644
  checksum '741cb3eb7b4e61e57d0caa47114553af2dcecacb7ccc957890df54bcebb464ea'
  action :create_if_missing
end

execute 'extract validator' do
  command "tar xzf validator.tar.gz"
  cwd w3c_validator_home
  creates "#{w3c_validator_home}/validator-1.3"
end

remote_file "#{w3c_validator_home}/OpenSP-1.5.2.tar.gz" do
  source 'http://downloads.sourceforge.net/project/openjade/opensp/1.5.2/OpenSP-1.5.2.tar.gz'
  mode 00644
  checksum '57f4898498a368918b0d49c826aa434bb5b703d2c3b169beb348016ab25617ce'
  action :create_if_missing
end

execute 'extract OpenSP' do
  command 'tar xzf OpenSP-1.5.2.tar.gz'
  cwd w3c_validator_home
  creates "#{w3c_validator_home}/OpenSP-1.5.2"
end

execute 'compile and install OpenSP' do
  command './configure --disable-doc-build && make -j2 && make install'
  cwd "#{w3c_validator_home}/OpenSP-1.5.2"
  creates "/usr/local/bin/osx"
end

Anschließend muss noch die Konfigurationsdatei an unsere Pfade angepasst werden:

<Paths>
  Base = /usr/local/w3c-validator/validator-1.3
  Templates = $Base/share/templates

  <SGML>
    Library = /usr/share/sgml
  </SGML>
</Paths>

Allow Debug = yes
Enable Debug = no
Allow Private IPs = yes
Enable SOAP = yes
Max Recursion = 0

<Protocols>
  Allow = data,http,https
</Protocols>

Maintainer = webmaster@nix-wie-weg.de
Home Page = http://<%= @vhost_name %>/w3c-markup-validator/
Element Ref URI = http://www.htmlhelp.com/reference/html40/

<Types>
  Include types.conf
</Types>

<Charsets>
  Include charset.cfg
</Charsets>

<MIME>
  text/xml              = XML
  image/svg             = XML
  image/svg+xml         = XML
  application/smil      = XML
  application/xml       = XML
  text/html             = TBD
  text/vnd.wap.wml      = XML
  application/xhtml+xml = XML
  application/mathml+xml = XML
</MIME>

<External>
  HTML5 = http://localhost:8888/html5/
</External>

Dateien, die per Include-Direktive eingebunden werden, brauchen nicht geändert zu werden. Der letzte Code-Block ist hier von entscheidender Relevanz: HTML5-Anfragen werden an den HTML5-Validator durchgereicht.

# cookbooks/w3c_validator/recipes/default.rb
w3c_conf = '/usr/local/w3c-validator/validator-1.3/htdocs/config/validator.conf'
template w3c_conf do
  source 'validator.conf.erb'
  owner 'root'
  group 'www-data'
  variables({ vhost_name: node['validator']['vhost_name'] })
  mode 00644
end

Letzendlich benötigen wir noch mehrere Perl-Module, die wir uns in einer aktuellen Version direkt von CPAN holen:

# cookbooks/w3c_validator/recipes/default.rb
execute 'install CPAN libraries' do
  command 'cpan -i Bundle::W3C::Validator XML::LibXML'
end

Nginx-Konfiguration

Bei Nginx ergibt sich das Problem, dass dieser selber keine CGI-Skripte mehr unterstützt. Als Lösung bietet sich hierbei Fcgiwrap an. Dieses Skript bietet eine FastCGI-Schnittstelle zum Webserver und kümmert sich um die korrekte Weitergabe von Fehlern und Umgebungsvariablen.

Installiert haben wir das Paket bereits; sobald der Daemon gestartet ist verläuft die Kommunikation über einen Unix-Domain-Socket (in unserem Fall /var/run/fcgiwrap.socket). Die Konfiguration ist damit einfach:

# /etc/nginx/sites-available/validator.s5.nix-wie-weg.de
server {
  listen 80;
  server_name <%= @vhost_name %>;

  gzip off;

  location /check {
    root /usr/local/w3c-validator/validator-1.3/httpd/cgi-bin/check;
    # Fastcgi socket
    fastcgi_pass  unix:/var/run/fcgiwrap.socket;

    # Fastcgi parameters, include the standard ones
    include /etc/nginx/fastcgi_params;

    # Adjust non standard parameters (SCRIPT_FILENAME)
    fastcgi_param SCRIPT_FILENAME  /usr/lib$fastcgi_script_name;
    fastcgi_param W3C_VALIDATOR_CFG /usr/local/w3c-validator/validator-1.3/htdocs/config/validator.conf;
  }

  location / {
    ssi on;
    root /usr/local/w3c-validator/validator-1.3/htdocs/;

    # W3C-Validator Schlampereien reparieren:
    rewrite ^/(style/(base|results)) /$1.css break;
  }

}

Die Entschprechenden Teile des Chef-Rezepts zum Ablegen der Webserver-Konfigrationsdateien sehen dann so aus:

# cookbooks/w3c_validator/recipes/default.rb
"#{node[:nginx][:dir]}/sites-available/validator.s5.nix-wie-weg.de" do
  source 'validator.s5.nix-wie-weg.de.erb'
  variables({ vhost_name: node['validator']['vhost_name'] })
  notifies :restart, resources(service: 'nginx')
end

nginx_site 'validator.s5.nix-wie-weg.de' do
  enable true
  notifies :run, :immediately
end

Anwendungsbeispiel mit w3c_validators

Mit dem w3c_validators-Gem kann man nun den eigenen Dienst anstelle des offiziellen verwenden. Das funktioniert deutlich schneller und vor allem zuverlässiger. Wir installieren zunächst das Gem:

$ gem install w3c_validators
Successfully installed w3c_validators-1.2
1 gem installed
Installing ri documentation for w3c_validators-1.2...
Building YARD (yri) index for w3c_validators-1.2...
Installing RDoc documentation for w3c_validators-1.2...

Anschließend kann man irb öffnen und loslegen:

require 'w3c_validators'

w3c = W3CValidators::MarkupValidator.new(
        validator_uri: 'http://validator.s5.nix-wie-weg.de/check'
      )
w3c.validate_uri('http://www.nix-wie-weg.de')