Premessa: recentemente Google ha registrato il tld dev e pertanto non è più possibile utilizzarlo per sviluppare in locale, dato che i browser non lo risolvono più in locale. Il presente post è stato modificato sostituendo dev con local, ovviamente il tutto funziona al meglio.
(aggiunto il 11-1-2018)

L'obiettivo è semplice: sviluppare sulla propria macchina senza dover ogni volta configurare apache per ogni nuovo sito. In soldoni ci proponiamo di:

  • mettere tutti i siti di sviluppo in una cartella, diciamo "~/Sites" o "/var/www/"
  • la cartella del sito in sviluppo si chiama come il dominio
  • i domini di sviluppo terminano con ".local"
  • nella cartella del sito deve essere possibile mettere altro materiale non pubblico, quindi il sito vero e proprio sarà nella sottocartella www

La situazione tipica del sito in sviluppo www.example.com è definita come segue:

  • directory del progetto: /Users/max/Sites/example.local (/var/www/example.local su Linux)
  • directory del sito: /Users/max/Sites/example.local/www (/var/www/example.local/www)
  • inoltre il tutto funziona per qualsiasi cartella contenuta in example.local, ad esempio www2 sarà accessibile attraverso www2.example.local (/var/www/example.local/www2)
  • se si accede senza specificare l'host (il primo pezzo del domain name, www per intenderci) verranno serviti di default i file della directory www (/var/www/example.local/www)

inoltre per le pagine spicce e le prove potremo usare la directory /Users/max/Sites/localhost (/var/www/html su Linux) che funzionerà da default virtual host.

In pratica occorre installare e configurare un dns sulla macchina locale che risolva tutte le richieste .local in 127.0.0.1 (oppure l'ip della vm, se il server è là), ma che non venga coinvolto per ogni altra richiesta, in modo che non ci siano intralci a dhcp o altro se ci spostiamo sotto diverse reti, e configurare apache in modo che virtualizzi dinamicamente ogni dominio .local servendo le richieste dalla cartella opportuna.

Partiamo dal DNS

OSX

Con homebrew installiamo dnsmasq e lo configuriamo come spiegato nell'ottimo tutorial  https://passingcuriosity.com/2013/dnsmasq-dev-osx/ :

# brew install dnsmasq
# cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf

quindi modifichiamo il file /usr/local/etc/dnsmasq.conf scommentando e modificando le sole righe:

address=/local/127.0.0.1
listen-address=127.0.0.1

Impostiamo OSX in modo che invii le richieste per il tld local a dnsmasq aggiungendo il file /etc/resolver/local (se la directory /etc/resolver/ non esiste la creiamo come root) contenente il testo

nameserver 127.0.0.1

e avviamo in modo definitivo dnsmasq (come homebrew aveva suggerito durante l'installazione):

# sudo brew services start dnsmasq

Verifichiamo infine che tutto funzioni correttamente:

ping -c 1 www.google.com
ping -c 1 this.is.a.test.local
ping -c 1 iam.the.walrus.local

Linux

Su Linux/Debian o Ubuntu installiamo dnsmasq: 

# sudo ape-get install dnsmasq

copiamo /etc/resolv.conf in /etc/resolv.dnsmasq.conf (i precendenti dns a cui accede la macchina) e in /etc/resolv.conf mettiamo solo:

nameserver 127.0.0.1

in modo che ogni servizio utilizzi dnsmasq. Quindi configuriamo dnsmasq inserendo in /etc/dnsmasq.conf

address=/local/127.0.0.1
resolv-file=/etc/resolv.dnsmasq.conf

La prima riga dice che tutto ciò che finisce con local viene mappato sulla macchina locale (se per programmare usate una VM, qui mettete l'ip della VM, ad esempio 192.168.123.123), la seconda dice di inoltrare le richieste di risoluzione di dominio ai server indicati nel file. I dns indicati in resolv.dnsmasq.conf potrebbero essere i soliti di google:

nameserver 8.8.8.8
nameserver 8.8.4.4

o quelli forniti dal vostro ISP. Purtroppo il dhcp a volte (ciè sempre;) sovrascrive resolv.conf, occorre assicurarsi che ciò non accada: si può ovviare alla cosa modificando il file /etc/dhcp/dhclient.conf e scommentare la riga (provare con un riavvio del servizio dhcp):

prepend domain-name-servers 127.0.0.1;

che fa sì che il file /etc/resolv.conf contega come prima riga il loopback e poi i dns suggeriti dal server dhcp, che è poi quello che ci serve.

Windows

Nel disavventuroso caso la vostra piattaforma di sviluppo contempli un windoze con VM linux per il server, al posto di dnsmasq si può utilizzare DualDHCPDNS installato in windoze, avviato o come servizio o all'occorrenza, e configurato semplicemente così:

[SERVICES]
DNS
[WILD_HOSTS]
*.local=192.168.123.123

dove al posto di 192.168.123.123 avete messo l'IP della macchina virtuale su cui gira il server linux.

Se usate VirtualBox, è opportuno configurare un paio di schede di rete sul guest in modo da poter accedere a Internet dal guest anche sotto reti dhcp (e quindi in situazioni variabili) e accedere al guest da windoze sempre sullo stesso ip (se no occorre andare a cercarlo ogni volta volta che si avvia la vm): in tal senso avremo una scheda in bridge sulla wifi (se usate prevalentemente quella) o wired (a mali estremi anche due schede separate, se usate un po' e un po') e una scheda con rete solo host (ad esempio vboxnet0 con ip impostato in modo che non entri in conflitto con le reti in cui di solito lavoriamo, ad esempio 192.168.123.0/255.255.255.0) con ip fisso sulla vm, ad esempio il 192.168.123.123 dell'esempio precedente.

Ricordatevi di montare la directory /var/www del guest da una condivisione (gestita con VirtualBox) di windoze in modo da poter usare Eclipse sul host, senza dover passare da una macchina all'altra ogni volta. In questo modo sulla vm non occorrerebbe neanche il window manager il che rende la vm più leggera e più veloce da realizzare e manutenere. A questo proposito ricordo che Vagrant serve propio a costruire e foraggiare macchine virtuali per questo scopo.

In alternativa al dns server, chi lavora solo su windows (quindi senza vm Linux) può considerare una soluzione molto semplice a costo nullo!

Verifica del DNS

Per verificare il corretto funzionamento del DNS, da console (sull'host)

# ping www.google.local
PING www.google.local (127.0.0.1) 56(84) bytes of data.
...
# ping www.google.it
PING www.google.it (216.58.205.131) 56(84) bytes of data.
...

dove il primo dei due ping, se usate la vm, deve restituire 192.168.123.123.

Il resto della configurazione riguarda Apache e va fatta sulla vm (per chi la usa).

Apache

Per apache la situazione è un po' complessa ma grazie al tutorial  http://servertopic.com/topic/AL1c-using-virtualdocumentroot-only-if-a-suitable-document-root-exists , con qualche aggiustamento, sono arrivato ad una soluzione ottimale (almeno per ora) che descrivo di seguito. Per Linux e Windoze la cosa, aggiustati i percorsi nel modo opportuno, è identica.

Attenzione ad installare ed attivare tutti i moduli di apache necessari, ovvero mod_rewrite per RewriteEngine e mod_vhost_alias per il comando VirtualDocumentRoot (su OSX e Linux con a2enmod).

Nella configurazione di apache occorre disattivare, se è caricato, il file httpd-vhosts.conf e creare e attivare un file dal nome httpd-autovhosts.conf (o qualcosa che vi aggradi).

Su OSX, creiamo il file httpd-autovhosts.conf in extra e modifichiamo httpd.conf per disattiva httpd-vhosts.conf e caricare il nostro, cambiamo inoltre l'utente di avvio di apache, se già non l'abbiamo fatto, con il nostro utente (max nel mio caso), in modo che abbia tutti i diritti necessari per accedere alla nostra Sites (in Linux c'è già un utente default per apache, vedete un po' come preferite muovervi).

Su Linux Ubuntu non c'è una cartella extra ma conf-available e conf-enabled, posizionare il file httpd-autovhosts.conf in conf-available e poi eseguire i comandi

# a2enconf httpd-autovhosts.conf
# a2disconf httpd-vhosts.conf

Nel nuovo file httpd-autovhosts.conf inseriamo il seguente contenuto (dove ovviamente cambierete «max» con il vostro username, o sostituite il percorso rispetto al vostro sistema operativo):

DocumentRoot /Users/max/Sites/localhost

<Directory /Users/max/Sites/>
    Options -MultiViews +FollowSymLinks
    AllowOverride All 
    Require all granted
</Directory>

<VirtualHost *:80>
    ServerName localhost
    VirtualDocumentRoot /Users/max/Sites/localhost

    DirectoryIndex index.php index.html

    LogFormat "%V %h %l %u %t \"%r\" %>s %b" vcommon
    CustomLog "/var/log/apache2/access_log" vcommon
    ErrorLog "/var/log/apache2/error_log"
</VirtualHost>

<VirtualHost *:80>
    ServerName sub.domain
    ServerAlias *.*.*
    VirtualDocumentRoot /Users/max/Sites/%-2.0.%-1.0/%-3

    DirectoryIndex index.php index.html

    RewriteEngine on

    RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
    RewriteCond /Users/max/Sites/%2.%3 !-d
    RewriteRule (.*)  http://localhost/ $1 [P]

    RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
    RewriteCond /Users/max/Sites/%2.%3/%1 !-d
    RewriteCond /Users/max/Sites/%2.%3/www !-d
    RewriteRule (.*)  http://localhost/ $1 [P]

    RewriteCond %{HTTP_HOST} ^(.*)\.(.*)\.(.*)$ [NC]
    RewriteCond /Users/max/Sites/%2.%3/%1 !-d
    RewriteRule (.*) http://%2.%3/$1 [P]

    LogFormat "%V %h %l %u %t \"%r\" %>s %b" vcommon
    CustomLog "/var/log/apache2/access_log" vcommon
    ErrorLog "/var/log/apache2/error_log"
</VirtualHost>

<VirtualHost *:80>
    ServerName bare.domain
    ServerAlias *.*
    VirtualDocumentRoot /Users/max/Sites/%-2.0.%-1.0/www

    DirectoryIndex index.php

    RewriteEngine on

    RewriteCond %{HTTP_HOST} ^(.*)\.(.*)$ [NC]
    RewriteCond /Users/max/Sites/%1.%2 !-d [OR]
    RewriteCond /Users/max/Sites/%1.%2/www !-d
    RewriteRule (.*)  http://localhost/ $1 [P]

    LogFormat "%V %h %l %u %t \"%r\" %>s %b" vcommon
    CustomLog "/var/log/apache2/access_log" vcommon
    ErrorLog "/var/log/apache2/error_log"
</VirtualHost>

che è una versione leggermente modificata di quello riportato nel blog di cui sopra. Testate apache con il solito apachectl -S e se tutto ok riavviatelo. Create la cartella localhost in Sites e qualche sito di prova: dovreste accedere a www anche omettendolo nell'url e se ne indicate uno diverso cercherà una cartella di tal nome nella directory del sito.

Ma non sono ancora contento: vorrei che *automaticamente* anche il log venisse diretto nella cartella del progetto, nel file access_log (per l'error_log è meglio fare riferimento a quello globale di apache). Per separare le righe di log a seconda del sito ho scritto un semplice programmino in awk (è meglio installare con homebrew e usare gawk invece di quello osx) che splitta i log in base alla prima voce della riga. Mettiamo lo script da qualche parte, ad esempio in una directory bin sotto la propria home, e invochiamolo dalla configurazione di apache.

Lo script awk, che ho chiamato splitlog, è

#!/usr/local/bin/awk -f
{
   if (DR == "") DR = "/Users/max/Sites/"
if (FN == "") FN = "access_log"
print DR FN
split($1, outfile, ".")
outfilename = ""
sep = ""
for (i=0; i < 2 && i < length(outfile); i++) {
outfilename = outfile[length(outfile) -i] sep outfilename
sep = "."
}
outfilepath = DR outfilename "/" FN
print $0 >> outfilepath
print $0 | "cat >&2"
fflush()

Ovviamente cambieremo "max" con il nome utente opportuno o anche tutto il percorso se avete fatto altrimenti (in realtà è possibile configurarlo anche direttamente da apache aggiungendo alla riga di comando del CustomLog -v DR=nuovo/path).

Modifichiamo anche la configurazione di apache per tutti i CustomLog in

CustomLog "|/usr/local/bin/awk -f /Users/max/bin/splitlog" vcommon

dove i percorsi sia di awk e splitlog sono stati verificati a console con il comando which. Testiamo e riavviamo apache.

Speriamo che tutto funzioni al primo colpo... ma non è mai così ;)

Nota: ho aggiunto al logformat di apache un > nell'errore, che diventa %>s, in modo da riportare l'effettivo codice in caso di errore, altrimenti nel log escono tutti 200 a fronte di 404.

Infine, per chi vuole avere un colpo d'occhio su tutti i siti in sviluppo e accedervi con un semplice click, si può impostare la index.php in localhost secondo il codice che segue, ovviamente da modificare a piacimento:

<!doctype html>
<?php
$path = str_replace(basename(__DIR__),'', __DIR__);
$dir = scandir($path);
$hosts = [];
foreach($dir as $d) {
  if(strpos($d, '.local') !== false) {
    #echo "<a href='http://$d/'>$d</a>\n";
    $hosts[$d] = [];
    $subdir = scandir($path . $d);
    foreach($subdir as $sd) {
      $subpath = $path . $d . '/' . $sd;
      if(preg_match('/\\./', $sd) === 0 && is_dir($subpath)) {
        $hosts[$d][] = $sd .'.'. $d;
      }
    }
  }
}
?>
<html>
<head>
  <meta charset="UTF-8">
  <title>local dynamic vhosts</title>
  <style>
    * {font-family: Verdana, Helvetica, Arial, sans-serif}
    a:link {text-decoration: none; color: black;}
    a:visited {text-decoration: none; color: black;}
    a:hover {text-decoration: none; color: blue;}
    a:active {text-decoration: none; color: red;}
    h1 {text-align: center; }
    section {
      column-count: 3;
      column-rule: solid 1px lightgray;
    }
    section {
      padding: 1pc;
    }
    section > ul {
      list-style: none;
      padding: 0;
      margin: 0;
    }
    section > ul > li > ul {
      list-style: none;
      padding: 0 0 0 1.5pc;
      margin: 0 0 0 -1.5pc;
      background-color: white;
      text-align: right;
    }
    section > ul > li {
      margin: 0 0 10px 0;
      background-color: lightgray;
      padding: 0 0 0 1.5pc;
      position: relative;
    }
    section > ul > li:before {
      content: "::";
      position: absolute;
      left: 0;
    }
  </style>
</head>
<body>

<h1>local dynamic virtual hosts</h1>
<section>
  <ul>
    <?php foreach($hosts as $h => $subhosts): ?>
      <li><a href=' http://< ;?= $h ?>/'><?= $h ?></a>
        <ul>
          <?php foreach($subhosts as $sh): ?>
            <li><a href=' http://< ;?= $sh ?>/'><?= $sh ?></li>
          <?php endforeach ?>
        </ul>
      </li>
    <?php endforeach ?>
  </ul>
</section>

</body>
</html>

;)

Spesso capita di incappare in qualche tipo di problema software o hardware e di chiedere aiuto al guru del caso. Ma quali sono le risposte notevoli più probabili che ci vengono propinate o che, a nostra volta, possiamo rifilare al malcapitato? Vediamo un elenco sicuramente non esaustivo delle più irritanti:

  1. Impossibile!
  2. Provi a spegnere e riaccendere
  3. Sul mio funziona
  4. Avrà fatto qualcosa lei
  5. Può farmi un'analisi del problema?
  6. Sì, ma cos'è esattamente che non funziona?

Nella didattica dell'informatica, e forse anche in fase di progettazione di semplici programmi batch, si utilizzano ancora, come strumento ideativo, i flow-chart e questo perché sono più semplici della controparte linguistica, lo pseudocodice.

In effetti il layouting di un algoritmo con i flow-chart ha una grande espressività e permette, a colpo d'occhio, di comprendere un programma di anche 10 blocchi, compresi start e end. Invece il codice scritto in linguaggio di programmazione non consente un paragonabile coup d'œil, se non che per la singola riga, e a volte nemmeno quella (pensiamo a quanto può essere complessa una riga "ben scritta" in C).

Oltre i 10-12 blocchi, però, il flow-chart diventa sempre più (qualcuno direbbe esponenzialmente) complesso tanto che leggerlo richiede uno sforzo non indifferente ad un programmatore esperto, figuriamoci ad uno studente.

I flow-chart nascono con la prima programmazione, negli anni '60, assieme a linguaggi di programmazione, oggi diremmo, di basso livello, e sicuramente anche molto "liberi". Liberi soprattutto di usare qualsiasi strumento per raggiungere il fine preposto: risolvere il problema.

Col tempo ci si è accorti che questo approccio fattivo ma sregolato ha portato alla produzione di software poco manutenibile e tantomeno riusabile fino a disconoscere questo modo di programmare ed etichettarlo come "programmazione a spaghetti" riconoscendo nel GOTO il nemico numero uno. È il gran momento del ritorno alle regole, oppure di crearne di nuove: modulartà, programmazione strutturata, top-down, bottom-up sono alcune delle parole magiche, delle panacee che avrebbero risolto ogni difficoltà, assieme all'esilio definitivo del GOTO.

I nuovi linguaggi di programmazione sono sprovvisti del goto (oppure ce l'hanno, ma nessuno lo reclamizza più di tanto, temendo le infamie del pesiero dominante). Invece i flow-chart restano quello che sono: proni come sempre alla programmazione a spaghetti.

In effetti, se ci pensiamo un attimo, i flow-chart rappresentano in modo perfetto sequenze, salti condizionati e incondizionati. Mancano solo i cicli. E questo perché si possono simulare con salti condizionati e incondizionati. È vero, l'esagono rappresenta il ciclo con numero di ripetizioni predefinito (il for, per intendersi), ma è solo una selezione rimasterizzata, e così anche i cicli precondizionati e postcondizionati.

Cicli non cicli (e sì, quello è un salto incondizionato!)

Semplicemente non c'è un modo per rappresentarli con i blocchi dei flow-chart perché sono istruzioni che ne "avvolgono" altre. In Scratch il concetto è reso in modo splendido (peccato solo che i geni del MIT si siano inventati la ripetizione precondizionata che cicla per falso!):

Nei flow-chart potrebbe funzionare in modo analogo se il corpo della ripetizione fosse fatto da una sola istruzione, ma è un limite ovviamente assurdo.

Se quindi intendiamo utilizzare il flow-chart come strumento didattico per far comprendere un algoritmo, dobbiamo limitare il numero di blocchi e limitarci alle strutture di controllo di sequenza e di selezione a una o due vie: oltre abbiamo la perdita di espressività più la difficoltà dovuta ai limiti di un modello che non ha in sé blocchi altrettanto efficienti per i cicli.

Altra cosa è se si pretende da uno studente il flow-chart come produzione creativa per la risoluzione di un problema: non solo lo studente deve pensare a come risolvere il problema, ma nello stesso momento deve pensare come rappresentarlo in blocchi rispettando i necessari vincoli della programmazione strutturata, ancor di più se sono richiesti dei cicli e, ovviamente, tenendo il tutto sotto un certo numero di blocchi, altrimenti la verifica di correttezza potrebbe essere un altro "problema".

La mia opinione è che i flow-chart vanno usati con cautela, che non si possono pretendere da programmatori inesperti come strumento ideativo e che la scrittura in pseudocodice è molto più espressiva e libera ed essendo uno strumento linguistico, in fase ideativa non pone in sé problemi di rappresentazione delle idee e l'unico problema che si ha di fronte è trovare la soluzione del problema, non "scriverla".

Sitografia:

http://stackoverflow.com/questions/26097082/flowcharts-a-way-of-drawing-gotos