Ho condotto un esperimento di scrittura di software per analizzare i risultati di una esportazione delle mie conversazioni con ChatGPT. Ma ho deciso di aiutarmi nell’analisi con un Large Language Model che sicuramente sa fare molto bene questo lavoro e in più con un LLM in locale, per vedere come funziona.
Negli ultimi giorni ho avuto problemi intermittenti con un mouse Bluetooth Trust (modello 24059-02) connesso al mio HP con Ubuntu. Il mouse ha funzionato regolarmente per un paio di giorni, poi improvvisamente ha smesso di …
Ho installato la versione 24.3.1.347 di Oracle SQL Developer però ho una sorpresa: sembra non sia possibile raggruppare le connessioni in cartelle. Risposta: Eh sì — è una sorpresa che ha colpito parecchi utenti anche …
Quando si esegue uno script Bash ci sono due modalità principali da impiegare, che hanno conseguenze diverse sull’ambiente della shell. 1. Esecuzione diretta (./script.sh) Quindi può solo leggere le variabili di ambiente della shell madre …
Preso dalla curiosità di ripassare l’assembly (tanti anni fa studiai il Motorola 68000) ho scritto una guida ordinata che illustra passo per passo come scrivere un semplice programma Assembly in real-mode (16 bit), confezionarlo come …
Avevamo visto nel post precedente che un problema di configurazione aveveva fatto precipitare improvvisamente la frequenza di rimbalzo.
Per accorgerci di dove stia il problema avevo due strade, una un po’ più complicata e una semplice.
Frequenza di rimabalzo quasi a 0: che è successo?
Come ho già detto prima, il problema è la doppia invocazione di Google Analyitcs che viene scambiata per due pagine visitate, per cui sparivano i rimbalzi come per incanto.
Come prima cosa ho cercato sul web e ho trovato questa risorsa web che dava come suggerimento l’installazione di un plugin che controlla le informazioni scambiate dalla pagina con Analytics.
Il plugin si chiama Google Tag Assistant. L’ho installato su Chrome e in effetti mi ha segnalato che c’era questa doppia invocazione.
Un modo più semplice di accorgersene, senza installare altri plugin, è semplicemente quello di leggersi il sorgente HTML della pagina web (Ctrl-U in Chrome):
<script type='text/javascript' id='google_gtagjs-js-after'>
window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-28350766-1');
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-28350766-1');
</script>
Scorrendo la pagina fino a dove si trovano i codici di Google (si cerca “UA-“) ho individuato il punto in cui c’era il codice di tracciamento ripetuto perché ogni plugin aggiungeva le stesse identiche istruzioni, che sono quelle che Google si aspetta.
Bastava toglierne uno, e quindi dovevo disinstallare uno dei due plugin di Google. Ho tenuto SiteKit e buttato l’altro.
A distanza di due giorni la situazione della frequenza di rimbalzo è tornata alla normalità come testimonia il grafico di Google Analytics:
La frequenza di rimbalzo risale
Aul fatto che la frequenza di rimbalzo sia così alta be’… è un po’ quello che faccio anch’io con i siti web: mordo e fuggo. Ad esempio, il sito in cui ho trovato il suggerimento del Tag Assistant ha avuto da me esattamente un rimbalzo: ho trovato la risposta e me ne sono andato.
È un comportamento che un po’ tutti teniamo qualora si usi il web per trovare risposte alle domande.
Un giorno mi accorgo che da un momento all’altro la frequenza di rimbalzo (bounce rate) del mio sito è passata da attorno al 90% allo 0.
Sì non è un sito che fa tutta sta audience… ma a me serve solo per studiare. E un po’ per farmi conoscere.
Frequenza di rimbalzo: il contesto
Di cosa stiamo parlando? La frequenza di rimbalzo è uno degli indici di bontà calcolati da Google Analytics.
Questo indice misura la percentuale di utenti che visitano una sola pagina del nostro sito, proveniendo da una ricerca di Google.
Come una palla da tennis, atterrano sul nostro sito e se ne vanno.
Non è molto confortante; magari siamo riusciti ad ottenere un buon rank sui motori di ricerca grazie ad una buona ottimizzazione SEO e la nostra pagina è cliccabile nella prima pagina di risultati, però il nostro sito non è in grado di catturare sufficiente attenzione dell’utente per convincerlo a rimanere e a visitare almeno un altra pagina o leggere un altro post.
Più sono le sessioni che generano una sola page view e più alta è la frequenza di rimbalzo. Quindi avere un’alta frequenza di rimbalzo è di per sé un indice indesiderabile.
Sarebbe bello che fosse bassa!
Che bello, improvvisamente la frequenza di rimbalzo è quasi a zero!
Eh… non è detto che sia sempre una buona notizia. Soprattutto quando il calo è improvviso. Una diminuzione graduale è più compatibile con il progressivo aumento dell’interesse che mano a mano cattura persone che passano nel nostro sito sempre più tempo e visitano più di una pagina (in media).
Drop della Frequenza di rimbalzo
Una diminuzione drastica nel giro di qualche giorno generalmente nasconde un problema tecnico abbastanza serio che mette a repentaglio l’attendibilità dei dati elaborati da Analytics.
Troppi plugin per gestire Google Analytics
Ho aggiunto un plugn molto figo per WordPress che si chiama Google Site Kit.
Fornisce 4 dashboard per 4 prodotti di Google come mostrato dalla figura seguente:
Site kit per studiare la frequenza di rimbalzo
Ho inserito nelle impostazioni l’ID per collegare il mio sito a Google Anaytics (UA-XXXX…).
Sfortunatamente molto tempo fa avevo anche installato un plugin Google Analyticator per WordPress che in sostanza faceva lo stesso lavoro (senza però fornirmi i cruscotti consultabili in un solo posto)
Praticamente la compresenza di questi plugin duplicava le chiamate ad Analytics per ogni click, risultando in almento due pagine visitate per ogni click, quindi facendo praticamente sparire i rimbalzi.
Questo ha fatto collassare il bounce rate!
Ah va be’, era facile!
Be’, facile… come al solito il diavolo si nasconde nei dettagli. La parte difficile era capire che era proprio questo il problema 🙂
Riconoscimento e sintesi vocale: In questo articolo parlerò di due bellissime funzionalità di Python che non è nemmeno difficile testare: il riconoscimento vocale (speech recognizing) e la sintesi vocale.
Ho preso ispirazione dal sito copyassignment.com e mi sono un attimo studiato le librerie.
Analisi vocale (riconoscimento)
Il primo aspetto comprende i metodi con i quali viene analizzato il segnale audio.
Il parlato è un processo stocastico particolare nell’insieme dei suoni che ci circondano che ha delle caratteristiche peculiari. Vengono individuate all’interno del segnale audio cioè le caratteristiche come la frequenza, la presenza di determinati pattern:
segnali periodici, come le vocali o le mute “m”, “n”;
segnali aperiodici, come il suono “f” o “s” che assomigliano al rumore bianco
segnali “esplosivi” come le lettere “p”, “t” ecc.
e la suddivisione del segnale del parlato in fonemi. In sostanza analisi significa estrarre l’informazione dal parlato (analogico), ovvero riconoscere i fonemi e ricomporli per associarli a parole di una frase per poterla maneggiare come informazione di testo (digitale).
L’analisi vocale è basata sulla libreria Python speech_recognition che mette a disposizione una serie di API per l’analisi.
Sintesi vocale
La sintesi, viceversa, è la costruzione del segnale sonoro (analogico) del parlato a partire da una stringa di testo (digitale). Come assemblare gruppi di consonanti e di vocali per produrre i fonemi che compongono a loro volta le parole. La sintesi vocale è fornita dalla libreria pyttsx3.
pyttsx3 è una libreria di conversione “da testo a parlato” scritta in Python. A differenza di librerie alternative, può funzionare senza essere collegati a Internet ed è compatibile sia con Python 2 che con Python 3.
Prima di avventurarci su riconoscimento e sintesi vocale
Potreste anche direttamente andare nel sito di copyassignment.com se volete, ma qui posso risolvervi un paio di piccoli grattacapi dovuti alla mancanza di configurazione necessaria per eseguire questo programma.
Si tratta di farsi una minima idea del complesso di librerie che ci è nessario per passare al microfono al programma e dal programma agli altoparlanti.
Vi predigerisco già la pappa e vi illustro qual è lo stack su cui si basa il programma che in sostanza ci servirà per spegnere il computer:
Procedendo in quest’ordine però è molto probabile che vi troverete qualche gatta da pelare, come vediamo tra poco. Con (x) nello stack sopra ho contrassegnato le installazioni non citate da CopyAssignment.com
Installazione di pyaudio
marcob@jsbach:~$ pip install pyaudio
Se vi dovesse uscire questo errore
Building wheels for collected packages: pyaudio
Building wheel for pyaudio (setup.py)
ERROR: Command errored out with exit status 1:
...
significa che non è installato l’header file portaudio.h, che è contenuto nel package portaudio19-dev. Dobbiamo pertanto installarlo preventivamente.
PortAudio è una libreria per I/O audio portabile progettata per la gestione multipiattaforma dell’audio. Dell’audio in generale, intendiamo. non solo la voce umana.
È la libreria che permette di generare suoni e di leggere dalla periferica del microfono e convertire il segnale analogico in digitale. È la libreria a più basso livello di cui abbiamo bisogno.
Usa un meccanismo di callback per richiedere l’elaborazione audio. L’audio può essere generato in svariati formati, inclusa virgola mobile a 32 bit, e sarà convertito internamente nel formato nativo.
Riprovando a questo punto, a me è terminata con successo l’installazione di pyaudio; posso procedere con il package dell’analisi vocale.
Anche questo è andato bene. Questo è il sorgente del programma che potete trovare in CopyAssignment e che ho salvato in un file sd.py, modificando in italiano le stringhe di colloquio con il computer: lo posiamo avviare
# Importing required modules
# importing pyttsx3
import pyttsx3
# importing speech_recognition
import speech_recognition as sr
# importing os module
import os
# creating take_commands() function which
# can take some audio, Recognize and return
# if there are not any errors
def take_commands():
# initializing speech_recognition
r = sr.Recognizer()
# opening physical microphone of computer
with sr.Microphone() as source:
print('Ascolto...')
r.pause_threshold = 0.7
# storing audio/sound to audio variable
audio = r.listen(source)
try:
print("Riconoscimento")
# Recognizing audio using google api
Query = r.recognize_google(audio)
print("Questo è ciò che ho capito='", Query, "'")
except Exception as e:
print(e)
print("Prova a ripetere")
# returning none if there are errors
return "None"
# returning audio as text
import time
time.sleep(2)
return Query
# creating Speak() function to giving Speaking power
# to our voice assistant
def Speak(audio):
# initializing pyttsx3 module
engine = pyttsx3.init()
voice_id = 'italian'
engine.setProperty('voice', voice_id)
# anything we pass inside engine.say(),
# will be spoken by our voice assistant
engine.say(audio)
engine.runAndWait()
Speak("Vuoi spegnere il computer, Marco?")
while True:
command = take_commands()
if "no" in command:
Speak("Ok Marco, lascio acceso")
break
if "yes" in command:
# Shutting down
Speak("Spengo")
os.system("shutdown -h now")
break
Speak("Puoi ripetere? grazie")
Salvo il tutto in un file sd.py e provo ad eseguirlo
marcob@jsbach:~$ python3 sd.py
OSError: libespeak.so.1: cannot open shared object file: No such file or directory
Questo è un problema che si situa ad un livello intermedio tra portaudio19 e Python (speech_assignment / pyttsx3): non risulta infatti installata nel sistema la libreria per il riconoscimento e la sintesi vocale che saranno invocate dalle librerie Python: espeak.
Installazione di espeak (x)
eSpeak è un sintetizzatore vocale software open source compatto per inglese e altre lingue, per Linux e Windows. eSpeak utilizza un metodo di “sintesi formante”. Ossia costruisce i suoni in base ad un modello fisico dell’apparato fonatorio umano e utilizza la sovrapposizione di segnali sinusoidali, ovvero le serie di Fourier, per determinare il contenuto armonico del segnale.
Ciò consente di fornire molte lingue in dimensioni ridotte. Il parlato prodotto è chiaro e può essere utilizzato ad alta velocità, ma non è così naturale o fluido come sintetizzatori più grandi basati su registrazioni vocali umane. Ricorda un po’ i primi vocoder anni ’80. Se riprodotti su un impianto HiFi anche discreto, il suono risulta un po’ meno “cibernetico”.
marcob@jsbach:~$ sudo apt install espeak-ng-espeak
Lettura elenco dei pacchetti... Fatto
...
Elaborazione dei trigger per doc-base (0.10.9)...
Elaborazione 1 file doc-base aggiunto...
marcob@jsbach:~$
A questo punto manca solo il componente che collega python a espeak:
marcob@jsbach:~$ sudo apt install python3-espeak
A questo punto possiamo testare il programma.
Proprietà della libreria speech_recognition
Di questa libreria noi utilizzeremo in particolare la libreria Google di riconoscimento dei fonemi, il metodo recognize_google della classe Recognizer del package speech_recognition (sr).
L’unico parametro che viene impostato è
pause_threshold
ossia la soglia di silenzio che permette di separare le parole. Viene impostato a 0.7.
La variabile audio contiene un file mp3 che viene scritto dal metodo listen() della classe sr.Microhpone().
Alla fine il metodo magico che trasforma il suono (audio) ìn stringa (Query) è il seguente
Query = r.recognize_google(audio)
La classe è scritta bene, rende l’idea di cosa il programma stia facendo e con quali periferiche stia interagendo.
Proprietà della libreria pyttsx3
Questa componente trasforma la stringa in parlato ed è utilizzata per l’interfaccia human computer di interrogazione delle direttive dell’operatore. Ho selezionato il parlato in italiano (tra i molti disponibili):
Nella gestione dei dati della pandemia da Covid-19, nel corso del tempo è successo che i gestori del flusso dati della Protezione Civile per ragioni di miglioramento della interpretazione edlla situazione, integrassero le colonne con nuove dimensioni. Ad esempio dal 18 gennaio 2021 sono stati introdotti dimensioni come il numero di tamponi rapidi, per distinguerli dai tamponi con test molecolare.
Questa modifica impatta nella base dati locale, che si maniene aggiornata quotidianamente, con l’aggiunta di una colonna ulteriore nel tracciato CSV reperibile nel repository pubblico ufficiale della Protezione Civile ospitato su Github, colonna che deve essere ospitata in una tabella relazionale che rispecchia la struttura del CSV.
Come adeguare il modello MVC al nuovo tracciato? Laravel consente di farlo in modo molto veloce.
Modifica al modello Laravel e alla tabella database
Il nome della migrazione è a piacere e deve ricordare l’operazione che svolgerà. Non serve che sia il nome esatto del campo (sono nomi lunghi e preferisco abbreviare).
Un nuovo file per la migrazione verrà quidi creato sotto la directory
app/database/migrations
-rw-rw-r-- 1 marcob marcob 822 gen 22 10:51 2021_01_22_095107_tab_dati_nazionali_add_tampone_rapido.php
Editiamo il file che si presenta così, con una nuova classe che è la versione camelCased del nome del file di migrazione:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class TabDatiNazionaliAddTamponeRapido extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}
La classe ha due metodi: uno up() per aggiungere il campo e uno che si chiama ovviamente down() per eliminarlo, nel caso in cui fosse necessario un rollback delle operazioni di modifica della struttura.
Dobbiamo quindi aggiungere l’istruzione PHP che verrà convertita in istruzione DDL dall’ORM. Il nome esatto del campo che verrà prelevato dal CSV è tamponi_test_antigenico_rapido ed è modellabile con un integer; lo sisteiamo appena dopo la colonna note_test:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class TabDatiNazionaliAddTamponeRapido extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
Schema::table('dati_nazionali', function (Blueprint $table) {
//
$table->integer('tamponi_test_antigenico_rapido', '0')
->after('note_test')
->default(0)
->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
Schema::table('dati_nazionali', function (Blueprint $table) {
//
$table->dropColumn('tamponi_test_antigenico_rapido');
});
}
}
Ho creato a suo tempo una tabella dati_nazionali nel database che ospita i dati provenienti dai file CSV. Ora devo solo aggiungere una nuova colonna.
A questo punto è arrivato il momento di lanciare la migrazione
Come si può vedere, il campo tamponi_test_antigenico_rapido è un intero ed è satato inserito dopo il campo note_test come richiesto.
Finito!
E se qualcosa va storto?
Si può sempre tornare indietro facendo un rollback: artisan eseguirà il metodo down() della classe che viene tradotto con un alter table remove column.
$ php artisan migrate:rollback
Gestione della view e dell’importatore Laravel
In questo caso ho scelto di non mostrare nella tabella anche il nuovo dato, ma se si volesse è sufficiente modificare la view
@foreach($array['data'] as $item)
<tr>
<td>{{$item->data}}</a></td>
...
<td>{{$item->tamponi_test_molecolare}}</a></td>
<td>{{$item->tamponi_test_antigenico_rapido}}</a></td>
...
Nle mio caso non l’ho integrato per pigrizia (mi interessa maggiormanete che il processo di importazione giri senza problemi ogni giorno), ma appena posso lo integro nella vista.
Questo script non ho dovuto modificarlo perché l’ho già scritto in modo dinamico: il parser estrae i dati leggendo le colonne del csv e cercando le colonne omonime nella tabella, per cui è già tutto ok.
Questo articolo parla dello scaffolding di Grails, ossia della funzionalità di generare le pagine web standard in base alle tabelle del database. In particolare affrontiamo gli aspetti della generazione dello scaffold e dell’ordine di comparsa dei campi della tabella nella form HTML. La versione di Grails di cui parlo non è nuovissima, ma è quella su cui sto lavorando su comissione.
Le impalcature servono anche nel software 😉
Cos’è uno scaffold
Lo scaffold è l’impalcatura, cioè una rappresentazione grezza, anche se visavamente decorosa, delle tabelle DBMS come form HTML, che viene generata automaticamente dal framework, assieme alle implementazioni HTTP dei verbi CRUD che definiscono le operazioni su queste tabelle.
Per chi come me ha iniziato a scrivere applicazioni web nel 2000 si ricorderà che si doveva fare tutto a mano: mappare i campi della tabella in opportuni controlli (HTML), scrivere il controllo di omogeneità lato server e lato client (javascript/jQuery) . Ad esempio: range di valori, correttezza dell’input, ecc. E poi implementare le operazioni CRUD attraverso la compilazione di comandi SQL parametrici.
Dopo un po’ ci si è resi conto che questo lavoro era anche terribilmente noioso perché meccanico e ripetitivo, per cui presto si è messo a punto lo strumento dello scaffolding che automatizzava questa parte di lavoro, producendo automaticamente il codice nel linguaggio dell’application server e la parte grafica in HTML/CSS a partire dalla definizione in DDL della tabella DBMS per la realizzazione delle 4 operazioni.
Scaffolding con Grails 1: static vs dynamic
Lo scaffolding con Grails è possibile in due modi.
Dynamic scaffolding.
Il primo veloce ed economico è lo scaffolding dinamico che significa che gli HTML delle pagine che realizzano le operazioni vengono generati al volo dal framework come anche le funzionalità CRUD del controller e viene tenuto tutto in memoria RAM dell’Application server. È sufficente aggiungere la variabile static scaffolding inizializzata a true nella dichiarazione del controller:
Come alternativa si può anche inizializzare la variabile scaffold con il nome del dominio (tabella) che si vuole implementare.
Con questa modalità però non ho nessuna azione che venga cablata nel controller, né la produzione di alcuna vista. Se arresto il server perdo tutto.
È un buon punto di partenza ma non offre alcuna flessibilità.
Static scaffolding.
Lo scaffolding statico invece produce codice (controller / view) che poi può essere personalizzato.
In IDE del tipo Groovy/Grails toolsuite esisteva un comando disponibile a menu che produceva lo scaffold per il controller (quindi le azioni standard) e per le viste (le pagine html standard per ogni verbo). Venegoo generati anche i codici Groovy per tutti questi elementi.
Io però uso IntelliJ IDEA di JetBrains che non ha questo comando. Tuttavia è solo un lack della GUI perché da linea di comando si può fare tranquillamente:
$ grails generate-controller rewards.OrderItem
ad esempio genererà il codice per il Controller con i metodi standard index(), show(), creste(), save(), update(), delete(), notFound() e inoltre produrrà una cartella sotto views con il nome del dominio e le viste standard: _form.gsp, create.gsp, edit.gsp, index.gsp e show.gsp.
Grails Scaffolding #2: decidere l’ordine di apparizione dei campi della form HTML
Parte dell’utilizzo dei database nelle applicazioni web è rivolto alla posssibilità di fornire una rappresentazione grafica della tabelle db (assieme alle relazioni) come moduli (form) html. Infatti ciò che si fa è mappare ogni record in una maschera (form) con controlli (input) che ne riportano i valori.
Se si usa la funzionalità di scaffolding di Grails per costtuire le views (le schermate), è possibile decidere in quale ordine debbano essere visualizzati i campi di una form CRUD per la gestione di una tabella DBMS (ovvero di un domain Grails).
Ovviamente se non si usa lo scaffolidng è tutto a carico del progettista, ma è anche possibile dire a Grails come generare gli scaffold.
Nella dichiarazione del domain, nella dichiarazione dei constraints, scriveremo i campi nell’ordine in cui vogliamo che compaiano:
Nelle dichiarazioni delle variabili, l’ordine non è importante (a questo riguardo). Per quanto riguarda l’ordine nel quale comparranno nella form, è la varibile static costraints a guidare.
In questo articolo riassumo un po’ di informazioni sulla corretta impostazione di un progetto Groovy on Grails che integra una base dati Oracle, utilizzando la IDE IntelliJ IDEA di JetBrains. Con poco sforzo si può adattare ad altri tipi di DBMS.
Determinare la versione da linea di comando
Mi sono accorto oggi che mentre inizializzavo i miei progetti Groovy on Grails con la versione 2.3.11 (che è quella che mi ha richiesto il Cliente), in realtà quando eseguivo il comando grails veniva impiegata la versione 4.0.5. L’output era il seguente:
Incontri spaziali. Sembra che Giove e Saturno siano prossimi a scontrarsi ma naturalmente è solo una questione di prospettiva: è la loro proiezione sulla volta celeste a farlo.
Quello che accade realmente è che siamo all’incirca tutti e tre, noi, Giove e Saturno, lungo un’unica linea retta di congiunzione.
Giove e Saturno si incontrano sulla sfera celeste sotto un angolo di 1/5 del disco lunare
Non si tratta in realtà di un allineamento perfetto; più precisamente vediamo entrambi i pianeti sotto un angolo solido pari a circa di 1/5 del disco lunare.
Per esempio questa elaborazione della Nasa mostra cosa si intenda per allineamento tra pianeti. Quello qui rappresentato è la sovrapposizione tra Venere e Giove avvenuta il 30 giugno 2015
Allineamento tra Terra, Venere e Giove del 30 giugno 2015
Questo delle congiunzioni o allineamenti è un evento molto raro sulla scala di una vita umana. L’ultima volta che Giove e Saturno si trovarono così vicini sulla volta celeste successe 4 marzo1226. In quell’anno morirono S. Francesco d’Assisi e Federico IIdi Hohenstaufen.
Non per la congiunzione astrale, beninteso, è solo una coincidenza!
Anche un’altra volta avvenne, il 16 luglio 1623, però da terra non si vide nulla perché la congiunzione avvenne vicino al disco solare, verso il tramonto.
La congiunzione tra Giove e Saturno in realtà avviene molto più spesso, circa ogni 20 anni, ma non sempre entro un angolo così piccolo e in condizione di oscurità favorevole. Una lista delle congiunzioni tra i due pianeti, nel passato e nel futuro si trova in questa risorsa. Trovate tutte le congiunzioni, più o meno notevoli, dei due pianti dal 1206 al 2398.
Appunto, l’ultima volta i cui entrambe queste condizioni si verificarono, fu nel 1226.
C’è da dire che questa è la prima volta in assoluto che noi umani possiamo goderci lo spettacolo così da vicino, grazie alla Scienza e alla Tecnologia.
La non disponibiità di un certificato SSL è all’origine del “problema PXIX”
PKIX: una precisazione
Voglio avvisarvi subito che non esiste in sé un “errore PKIX”. È soltanto un modo breve di descrivere una situazione in cui c’è un problema del client Grails (o a qualunque altro client) ad accedere ad un URL protetto da SSL.
Se vi capita di vedere questa sigla PKIX in un messaggio errore di Grails, si fa riferimento alla Public Key Infrastructure X.509, uno standard usato per definire il formato dei certificati a chiave pubblica (PKC) e delle autorità di certificazione (CA).
Significa che è sorto qualche problema nello stabilire la connessione cifrata SSL con lo storage da cui il client scarica il template dell’applicazione.
Mi è capitato in più di un contesto questo problema (ad esempio nella configurazione di un reverse proxy Apache), però il significato di un simile tipo di sintomatologia risulta abbastanza chiaro nello specifico esempio che vi propongo.
Qual è il problema per cui compare questa misteriosa sigla PKIX?
Questo problema sorge appena si avvia il client Grails:
Ciò che succede è che insorge un problema di sicurezza perché non ho tra i certificati SSL installati nella JVM quello di grails.org da cui il client cerca di scaricare il boilerplate dell’applicazione. Da qui l’errore PKIX.
Una soluzione
La soluzione consiste nello scaricare e installare manualmente il certificato dal sito con Chrome o Firefox, e importarlo nel keystore della JVM.
I passaggi sono un po’ diversi a seconda del browser che state usando per accedere all’applicazione Laravel, per cui per vostra comodità vi illustro i passaggi con entrambi i browser. Per i possessori di Mac: mi dispiace. Per gli appassionati di Edge / Microsoft Internet Explorer: mi dispiace.
Passaggio 1
Scaricare il certificato: puntare il browser all’URL
Una volta stabilita la connessione e visualizzato il sito, cliccare sul lucchetto a sinistra nella barra degli indirizzi; cliccare quindi su “Certificato”, su “Dettagli”, su “Esporta” e fare il download in una cartella che conosciamo, ad esempio Downloads. Questo per Chrome.
Nota bene: questa è un’operazione che potete fare con qualunque sito in https, potete così informarvi sull’autorità emittente, sul proprietario del sito, sull’attendibilità e la validità del certificato di chiave pubblica.
Per Firefox: cliccare sul lucchetto, quindi su “Connessione Sicura >”, su “Ulteriori informazioni”, su “Visualizza certificato”; quindi sulla tab repo.grails.org cliccare sul link “PEM (certficato) e selezionare l’opzione “Salva con nome”. Un po’ più laborioso.
Passaggio 2
Importare il certificato appena scaricato nella keystore della JVM: individuare dunque la directory della JVM, nel mio caso:
/usr/lib/java/jdk1.8.0_25/jre/lib/security
Il file dei certificati è cacerts
Lanciare l’importazione
$ sudo keytool -import -alias example
-keystore /usr/lib/java/jdk1.8.0_25/jre/lib/security/cacerts
-file /home/marcob/Downloads/repo.grails.org
Keytools richiede la password del keystore che al primo utilizzo è changeit
[sudo] password di marcob:
Immettere la password del keystore:
Proprietario: CN=repo.grails.org
Emittente: CN=Sectigo RSA Domain Validation Secure Server CA, O=Sectigo Limited, L=Salford, ST=Greater Manchester, C=GB
Numero di serie: 2560fb52bfdc8f6bb2b3366805e3c911
Valido da: Fri Nov 13 01:00:00 CET 2020 a: Wed Dec 15 00:59:59 CET 2021
Impronte digitali certificato:
SHA1: D6:24:89:09:18:F1:39:E8:D6:6F:DF:EA:88:68:5C:BD:DB:2C:DF:F3
SHA256: 65:39:9F:7D:C3:09:D5:58:ED:E3:C3:82:59:98:BA:22:E2:8F:2D:65:6D:1E:F6:C3:51:55:9B:4F:14:9C:ED:A4
Nome algoritmo firma: SHA256withRSA
Algoritmo di chiave pubblica oggetto: Chiave RSA a 2048 bit
Versione: 3
...
Considerare sicuro questo certificato? [no]: si
Il certificato è stato aggiunto al keystore
Una volta completato l’nserimento del certificato nel portachiavi si può avviare Grails dalla cartella del progetto:
$ grails create-app com.djamware.gadgethouse | Application created at /home/marcob/IdeaProjects/java/gadgethouse | Resolving Dependencies. Please wait… | Starting interactive mode… | Enter a command name to run. Use TAB for completion: grails> run-app | Running application… ... Grails application running at http://localhost:8080 in environment: development
In questo articolo parlerò dei problemi che si incontrano nel refactoring di una applicazione. Più precisamente di quanto espresso dal titolo quindi:
Come gestire l’ORM (Object Relationship Mapping) su tabelle già definite.
Devo riscrivere un’applicazione PHP in Groovy on Grails ma il database non deve essere toccato come specifica di progetto.
Le pratiche di convention over configuration di molti framweork MVC (come Groovy on Grails, Laravel o Django) permettono di creare il database semplicemente dalla definizione delle classi, senza scrivere alcuna istruzione DDL.
Tuttavia se il Cliente non vuole che si modifichi il database, abbiamo una serie di strumenti messi a disposizone dal layer di gestione della mappatura tra oggetti e tabelle adottato da Grails (Hibernate) che ci permettono di uscire dallo standard per adeguarci all’esistente.
Importante: se non si vuole sovrascrivere il database, nel file grails-app/conf/dataSource.groovy va impostato il parametro dbCreate come “update“, non come “create-drop” (mi raccomando).
Se c’è già un campo chiave primaria
Ogni tabella ha come standard la colonna ID che di solito è un number(10) (sto usando Oracle). Se nella definizione della classe aggiungiamo la proprietà id, Hibernate mapperà il campo id nella colonna ID senza protestare.
Il campo version
Hibernate aggiunge per ogni tabella anche il campo version per implementare la strategia di “optimistic locking”. Cioè si vuole gestire una storia del record per cui viene di fatto creata una chiave composita che si riferisce ad un particolare record attraverso la fornitura sia dell’id che del numero di versione. In realtà in molte applicazioni a cui ho lavorato questo tipo di meccanismo è delegato al DBMS, nel quale (soprattutto nei database Oracle che ho gestito) viene scritto un trigger che crea un “giornale” della tabella con dentro tutte le oprazioni fatte sul record: dal primo inserimento, alle succesive modifiche fino all’eventuale cancellazione fisica. Per cui, in questo contesto ho ritenuto sovrabbondante l’introduzione dell’oprimistic locking e ho optato per la sua soppressione.
Per evitare di gestire una proprietà version si deve dichiare che non la si vuole nel dictionary mapping dentro alla dichiarazione del domain (il corrispondente Grails del model nel paradiga MVC):
package myapp
class Utente
...
static mapping = {
version false
}
Il campo version non verrà aggiunto alla tabella.
Il nome della tabella non segue uno standard
I nomi delle classi domain e nelle tabelle prassi abbastanza diffusa sceglierli in inglese, al singolare; però io ho una tabella Utenti per cui faccio un compromesso:
package myapp
class Utente
...
static mapping = {
table "utenti"
version false
}
Specificando nel dictionary statico mapping il campo table posso scegliere di associare il nome della tabella del database “intoccabile”.
Relazioni uno a molti
Come esempio ho un tabella articoli che è figlia di una tabella capitoli: ad un capitolo possono essere associati più articoli. Ho di fatto una serie di articoli che appartengono ad un capitolo per cui si usa la variabile belongsTo per specifcare questa relazione:
package myapp
class Articolo {
Integer id
Float prezzo
String descrizione
Integer attivo
static belongsTo = [capitolo: Capitolo]
In questa dichiarazione, il nome a sinistra del : è il nome della relazione (capitolo in minuscolo), e il nome a destra è il nome dell’oggetto padre (Capitolo).
Il nome delle chiavi esterne non segue uno standard
O, meglio, ne segue uno di suo che non è quello di Grails. Nella tabella Articoli per esempio ho una chiave esterna per realizzare la relazione con i Capitoli: idcapitolo che si riferisce all’id della tabella Capitoli.
Seguendo il suo standard, Hibernate crea il nome <tabella>Id per dichiarare le variabili “chiave”. Sfortunamtamente all’epoca nel database hanno seguito la convenzione inversa IDCAPITOLO (con l’id prima del nome tabella).
Mai paura: si dichiara questa deviazione dallo standard nella creazione del domain:
Si faccia attenzione che per mappare il nome della colonna della tabella database ho utilizzato il nome della relazione definita in belongsTo: non capitoloId (che non è definito da nessuna parte) ma capitolo.
Una tabella ha una chiave primaria composita
Come ultimo caso c’è quello di una tabella con una doppia chiave composta dall’unione di due campi idutente e iddipartimento (dovendo gestire il fatto che un utente può lavorare anche in dipartimenti diversi). È una strategia per gestire una relazione molti-a-molti. Di suo Hibernate vorrebbe che io aggiungessi una chiave artificiale ID ma la consegna data me lo impedisce, posso dichiarare la chiave composita e sopprimere la direttiva di creazione dell’ID:
package myapp
class UtenteDipartimento implements Serializable {
Integer attivo
Date dataInizio
Date dataFine
static belongsTo = [utente: Utente, dipartimento: Dipartimento]
static mapping = {
table "utenti_dipartimenti"
dataInizio column: "data_inizio"
dataFine column: "data_fine"
// relazioni
utente column: "idutente"
dipartimento column: "iddipartimento"
id composite: ['utente', 'dipartimento']
version false
}
nel quale sorgente riassumo un po’ tutte le cose dette nell’articolo.
Attenzione: se voglio creare una chiave composita, la classe deve implementare l’interfaccia Serializable.
Oracle tips: in questo post troverete alcuni consigli utili per gestire situazioni anomale che, quindi, normamente non si dovrebbero verificare: la password di system è scaduta oppure Sql*Plus non si avvia. Aggiungo anche un suggerimento su come creare un nuovo schema e degli utenti di supporto per limitare le operazioni sul database da applicazione.
Problem 1: password di system scaduta
Il problema si presenta tipicamente quando non si accede da molto ad un database. È un’eventualità abbastanza remota se si lavora regolarmente sui sistemi. Nel mio caso avevo un vecchio DBMS Oracle di sviluppo in una macchina Windows 10 che non usavo da un po’ ed era scaduta anche la password di system.
Una semplice procedura consente di risolvere il problema. Da linea di comando si digiti
$ sqlplus system SQL*Plus: Release 11.2.0.2.0 Production on Mer Nov 25 11:57:12 2020 ... Enter password: ERROR: ORA-28001: the password has expired Changing password for system New password: Retype new password: Password changed
Stessa procedura per qualsiasi altro utente. In sostanza Oracle avvia automaticamente la procedura di cambio password nel caso sia scaduta. È chiaro che il ststema è bene non venga lasciato inattivo per troppo tempo, perché chiunque potrebbe impossessarsi del DBMS con questa procedura!
Problem 2: indisponibilità di SQL*Plus
Nel tentativo di risolvere il problema sopra, ho cozzato contro un altro problema che veniva prima in ordine di priorità, ossia non riuscivo nemmeno ad avviare SQL*Plus, il tool di amministrazione/sviluppo di Oracle da linea di comando.
SQL*Plus è uno strumento molto grezzo ma è potentissimo, consente di lanciare ogni tio di comando di amministrazione (DDL) ed elaborazione dati (SQL).
Generalmente si preferisce utilizzare tool con una GUI (Graphic User Interface) come TOAD o Oracle SQL Developer ma questo tool è davvero equivalente, se non più potente.
Nel mio caso l’errore che si verificava all’avvio del comando era il seguente:
C:\> sqlplus PSP2-0667: Message file sp1<lang>.msb not found
Il problema all’origine di questo messaggio è la non corretta valorizzazione della variabile di ambiente ORACLE_HOME, ho impostato (nel mio caso) al valore:
Ecco invece una breve lista di comandi per creare uno schema (uno schema è un utente Oracle proprietario di oggetti) e utilizzarlo per un’applicazione web (da sviluppare con qualsiasi tecnologia).
CREATE USER rewards IDENTIFIED BY smartPassw0rd;
GRANT CONNECT, RESOURCE TO rewards;
GRANT CREATE SESSION TO rewards;
CREATE USER rewards_rw IDENTIFIED BY P4ssw0rd;
Una volta creati gli oggetti, ad esempio creata una una tabella books, devo stabilire cosa l’utente può fare in questa tabella; devo concedere (GRANT) dei privilegi all’utente sulla tabella, andando dalla sua modifica, alla distruzione, alla gestione dei dati. Per questo utente mi limito alle sole operazioni CRUD:
GRANT SELECT, INSERT, UPDATE, DELETE ON rewards.books TO rewards_rw;
È buona prassi non dare i massimi privilegi sul database all’utente utilizzato dall’applicativo per connettersi a Oracle mediante il client. In genere è sufficiente un profilo abilitato alle sole operazioni CRUD, come realizzato dal comando sopra.
Si evita così la possibilità che in modo malevolo (sempre che l’applicazione lo consenta – e lo consente se non è progettata abbastanza bene da inibire comandi SQL – la cosiddetta SQL injection) un attaccante, per mezzo dell’applicazione, lanci istruzione SQL verso il server Oracle.
CRUD
Un’ultima considerazione: CRUD è l’elenco delle operazioni su database definite però in un ambito più genarale e astratto di storage. In pratica queste quattro operazioni (4 verbi) sono richieste e possibili anche in un file system, per esempio: creazione, lettura, scrittura, cancellazione di file, e non è inteso specificatamente per un DBMS. Tuttavia nel linguaggio SQL le quattro operazioni prendono dei nomi specifici:
Utilizziamo tecnologie come i cookie per memorizzare e/o accedere alle informazioni del dispositivo. Lo facciamo per migliorare l'esperienza di navigazione e per mostrare annunci personalizzati. Il consenso a queste tecnologie ci consentirà di elaborare dati quali il comportamento di navigazione o gli ID univoci su questo sito. Il mancato consenso o la revoca del consenso possono influire negativamente su alcune caratteristiche e funzioni.
Funzionale
Sempre attivo
L'archiviazione tecnica o l'accesso sono strettamente necessari al fine legittimo di consentire l'uso di un servizio specifico esplicitamente richiesto dall'abbonato o dall'utente, o al solo scopo di effettuare la trasmissione di una comunicazione su una rete di comunicazione elettronica.
Preferenze
L'archiviazione tecnica o l'accesso sono necessari per lo scopo legittimo di memorizzare le preferenze che non sono richieste dall'abbonato o dall'utente.
Statistiche
L'archiviazione tecnica o l'accesso che viene utilizzato esclusivamente per scopi statistici.L'archiviazione tecnica o l'accesso che viene utilizzato esclusivamente per scopi statistici anonimi. Senza un mandato di comparizione, una conformità volontaria da parte del vostro Fornitore di Servizi Internet, o ulteriori registrazioni da parte di terzi, le informazioni memorizzate o recuperate per questo scopo da sole non possono di solito essere utilizzate per l'identificazione.
Marketing
L'archiviazione tecnica o l'accesso sono necessari per creare profili di utenti per inviare pubblicità, o per tracciare l'utente su un sito web o su diversi siti web per scopi di marketing simili.
Commenti recenti