Mi sono imbattuto in un comportamento piuttosto curioso utilizzando DBeaver con un server MySQL locale. All’avvio di DBeaver, il tentativo di connessione falliva con il messaggio: La cosa sorprendente era che bastava aprire un terminale …
Per anni avevo sentito ripetere che Docker è una virtualizzazione leggera. È una frase comoda per iniziare, ma in realtà è fuorviante. L’ho capito nel modo migliore: cercando di capire perché MySQL si comportasse in …
Aprendo Thunderbird mi è comparso un avviso che invitava a intervenire entro il 21 luglio 2026 per continuare a ricevere gli aggiornamenti ESR (Extended Support Release). Partiamo con le istruzioni operative per chi sa già …
Negli ultimi tempi termini come LLM, embedding e database vettoriali sono usciti rapidamente dall’ambito della ricerca e sono diventati argomenti quasi quotidiani. Sono mattoncini coi quali possiamo costruire sistemi capaci di interrogare grandi collezioni di …
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.
Problemi con il caricamento delle pagine web, fastidiosi reflow, scarsa responsività? Arriva AMP (Accelerated Mobile Pages): un framework HTML open source sviluppato in Google che fornisce una modalità semplice di creazione di pagine web che sono veloci, dal caricamento fluido e che danno priorità all’UX prima di tutto, primariamente per i dispositivi mobile.
Se avete notato, vedrete che quando effettuate una ricerca su Google con un dispositivo mobile come lo smartphone, quando cliccate sul link del risultato che volete visitare, può accadere che non compaia subito la pagina target ma venga invocato il sito ampproject.org in modo tale da rendere i risultato in modo veloce e fluido. Questo dipendentemente dal fatto che il sito sia stato attrezzato per utilizzare questo framework per ottimizzare le pagine con AMP.
AMP definisce una serie di tags estesi (che si chiamano componenti) che vengono gestiti da una libreria JavaScript obbligatoria che ogni pagina AMP deve caricare all’inizio della sezione <head> che hanno la proprietà di caricarsi velocemente nel browser e completamente prima di venire visualizzate, fornendo un’ottima esperienza utente (UX).
Per esempio, uno dei problemi che molti siti web mostrano (soprattutto se non ottimizzati per viewport del tipo smartphone) è il caricamento progressivo delle risorse della pagina web e il passaggio attraverso una fase in cui la pagina si ridisegna in continuazione: viene caricato un blocco di testo, poi un’immagine e viene ricalcolato il blocco di testo per aderire al layout… questo lavorio intermedio, piuttosto fastidioso, è detto HTML reflow e AMP fornisce degli strumenti che consentono di visualizzare la pagina solamente al termine delle determinazioni della geometria da parte del motore grafico del browser e del caricamento di tutte le risorse (font, testo, immagini).
Tecnicamente si tratta di ridefinire un set di pagine web dalla estensione .amp.html che utilizzano i componenti amp anziché i tag html standard (per esempio, l’utilizzo di <amp-img> anziché del tag <img>). Il rendering di questi tag è realizzato con la libreria Javascript che si trova nel CDN di ampproject.org.
Ma la potenza di AMP non si ferma ai siti web: può essere applicata in modo verticale a blog, stories, siti di ecommerce, ads e addirittura email!
Scrivendo gli ultimi articoli ho pensato di chiarire la differenza tra linguaggi di scripting e di programmazione. Il criterio di differenza più netto che ho trovato in rete è quello di suddividere in queste due categorie i linguaggi di programmazione allo stesso modo in cui si suddividono i linguaggi tra compilati ed interpretati.
Ma ho trovato parecchie inesattezze su quest’ultima classificazione, per una buona parte dei linguaggi attualmente disponibili.
La verità è che la differenza non è più così netta per tutta una serie di linguaggi molto popolari.
PHP non è un linguaggio interpretato come scritto da più parti, ma ha il suo bel bytecode generato a runtime da un compilatore che fa parte del core Zend. In pratica il compilatore Zend compila il sorgente in bytecode che viene poi interpretato dalla macchina virtuale Zend. Quindi non è veramente del tutto un linguaggio interpretato né del tutto compilato come si può dire del C.
Una risorsa che riporta un chiaro diagramma sul funzionamento di PHP è disponibile qui.
Anche Java viene trasformato in bytecode ed eseguito da una macchina virtuale (Oracle JVM, IcedTea etc).
La stessa cosa si può dire per Python in quanto anche qui ho una macchina virtuale che esegue bytecode (i file .pyc che vengono creati a runtime).
Quindi il codice che scriviamo non viene tradotto ed eseguito instantaneamente come succedeva per il BASIC, bensì viene compilato in bytecode e il bytecode viene interpretato.
C’è una importante differenza tra interpretazine dei bytecode e i programmi compilati puri come il C: il binding delle variabili, ossia il calcolo dell’indirizzo delle variabili, non è eseguito alla compilazione (anzi in realtà al momento dell’esecuzione del linker) ma all’esecuzione della virtual machine, quindi runtime. Nel C invece viene calcolata la posizione di memoria delle variabili in modo relativo nel codice hello.o e poi il linker le rende assolute nel contesto esecutivo del sistema operativo.
Javascript è un linguaggio di scripting poiché viene eseguito dal browser che diventa una sorta di “sistema operativo”. Ma anche questa nozione è superata. Il browser come Chrome in realtà opera una traduzione in bytecode, tramite il motore (engine, un altro modo di chiamare l’interprete) V8, degli script collegati alla pagina web. V8 è l’evoluzione dell’interprete JavaScript dei primi browser in un compilatore bytecode allo stesso modo di Java, PHP e Python. V8 è usato anche da node.js.
Un altro criterio distintivo potrebbe essere l’interazione con l’utente. Generalmente gli script consistono in una serie di comandi automatizzati che implementano la logica di un programma inteso come macchina di Von Neumann (inizializzazione di variabili, decisioni, cicli) e che vengono eseguiti in modalità batch, una modalità in cui il programma parte (spesso da solo, su innesco di uno scheduler come cron), fa una serie di operazioni e il risultato può essere verificato da un operatore a distanza di tempo e anche senza interazione alcuna con il software (ad esempio andando a vedere i log o i risultati in un database). In questa categoria si inseriscono gli script bash, potentissimi programmi (o script?) Unix/Linux.
Ho due applicazioni residenti in due cartelle diverse che condividono uno stesso file (ad esempio il file contiene una stessa istruzione sql che viene emendata).
È chiaro che in partenza una situazione del genere non è nemmeno consigliabile. Però questa è la condizione del software del mio cliente e per non dover fare una profonda ristrutturazione devo metterci una… “patch”. Chiedo venia.
A parte gli scherzi, devo apportare la stessa modifica a due file uguali in due applicazioni diverse.
Estraggo la patch applicata al primo repository
$ git diff [ID COMMIT1] [ID COMMIT2] > diff.patch
Sposto il file ottenuto che contiene la patcj da applicare (diff.patch) nella cartella del secondo repository
$ mv diff.patch [altraCartella]
Applico la patch al secondo repository Git
$ git apply diff.patch
Se la patch è applicabile Git lo farà.
Se i file sono diversi non lo farà.
Infatti il contenuto della patch è
marcob@jsbach[15:29:18]:altraCartella$ cat diff.patch
diff --git a/inviti/inv610.db.inc.php b/inviti/inv610.db.inc.php
index 9d9b678..797f68d 100644
--- a/inviti/inv610.db.inc.php
+++ b/inviti/inv610.db.inc.php
@@ -272,7 +272,7 @@ where
and d.id_tipo = f.id
- and f.cod in (1, 2, 4, 5)
+ and f.cod in (1, 2, 3, 4, 5)
Git controlla prima che l’hash del secondo file (di arrivo) sia uguale al file da emendare: se sono diversi, vuol dire che non posso applicare la patch e Git terminerà.
Lavorando coi layout di Groovy on Grails (le cosiddette pagine GSP: Groovy Server Pages) un web designer può efficientemente creare una struttura di inclusione che fattorizza in modo ottimale le parti di html. Cioè può raccogliere a fattore comune tutte le parti di layout che sono comuni a tutte le funzionalità legate al singolo controller e personalizzare le parti dedicate al singolo metodo.
Usiamo la prassi “usa una Convenzione piuttosto che Configurare” (convention over configuration). Questo frammento di progetto è l’esposizione casuale di una citazione (quote) tratta da una tabella, assieme al suo autore.
il layout si chiamerà come il controller: se abbiamo definito un controller QuoteController troveremo un template nei layouts con lo stesso nome quote (utilizzando il programma create-controller Grails creerà già da solo un controller e un layout per default):
grails-app/views/layouts/quote.gsp
All’interno della view dedicata alla citazione poi dovremo convenire di chiamare il blocco div che vogliamo personalizzare con il nome del layout:
Il framework MVC Groovy on Grails si ispira anche onomatopeicamente a Ruby on Rails ed è la composizione di un linguaggio di programmazione per JVM (Groovy) su un framework MVC opzionalmente corredato da Spring (per il controllo della sicurezza) e Hibernate (lo strato ORM verso il database) che è Grails.
Groovy on Grails
Il linguaggio di programmazione è Groovy che però è molto simile a Java, avendo poi rispetto questo una serie di “ammorbidimenti” (vedi l’articolo Programmare in Groovy).
Groovy on Grails
Grails è un framework MVC che può essere associato a Spring per il controllo degli accessi e Hibernate come ORM. È un framework Open Source full-stack per applicazioni web basate su JVM. Adotta Apache Groovy come linguaggio di programmazione e il paradigma “meglio convenzione che configurazione” per consentire un’esperienza di programmazione estremamente produttiva e “aerodinamica”.
Ho scritto alcune guide come Creare un progettoGrails da linea di comando oppure con l’utilizzo della IDE Intellj IDEA di JetBrains. Uso la versione Ultimate che, tra le altre belle cose, ha che è possibile avere contemporaneamente anche un ambiente di sviluppo PHP attraverso l’attivazione di un plugin. In effetti JetBrains produce anche PHPStorm e quindi PHPStorm= IDEA + PHP Plugin.
Devo proteggere la mia applicazione con accesso di login e mi aggancio per questo ad un server CAS. La configurazione che ho fatto (minimale) è di inserire nel file BuildConfig.groovy, nella sezione plugins la riga
compile ":spring-security-cas:2.0.1"
Eseguendo il build:
$ grails s2-quickstart test1 Utente Ruolo Requestmap
SI verifica un errore (e Tomcat non parte)
Exception starting filter CAS Single Sign Out Filter Message: casServerUrlPrefix cannot be null.
Ora la configurazione dei parametri CAS era stata effetuata regolarmente nel file Config.groovy (tutti questi file si trovano nella cartella grails-app/conf del progetto Grails):
In questa situazione CAS viene invocato richiama l’host di ritorno passando il token di sessione, ma il client lato applicazione lo ignora e fa riferimento ad un suo db locale, un database H2.
L’url del db è scritto in DataSource.groovy ed è jdbc:h2:prodDb
ERROR pool.ConnectionPool
Mancano i driver che vanno aggiunti al file conf/BuildConfig.groovy:
dependencies {
// specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes e.g.
runtime 'mysql:mysql-connector-java:5.1.27'
// runtime 'org.postgresql:postgresql:9.3-1100-jdbc41'
test "org.grails:grails-datastore-test-support:1.0-grails-2.3"
}
La riga relativa a mysql di default è commentata: decommentarla. Eventualmente aggiungere il parametro di Oracle se invece si usa Oracle: si devono aggiungere i jar di odbc e ora sotto lib
marcob@jsbach[11:35:58]:betablog$ ll lib/
totale 3140
drwxrwxr-x 2 marcob marcob 4096 ago 26 18:07 ./
drwxrwxr-x 15 marcob marcob 4096 ago 26 18:05 ../
-rw-rw-r-- 1 marcob marcob 1555682 ago 5 16:47 ojdbc14.jar
-rw-rw-r-- 1 marcob marcob 1646178 ago 5 16:47 orai18n.jar
Groovy è un linguaggio debolmente tipizzato (consente la definizione implicita delle variabili e l’aritmetica implicita – in cui il tipo del risultato è dato da una serie di regole quando i tipi degli addendi non sono congruenti, come illustrato tra poco) derivato da Java con in più le performance e la flessibilità di un linguaggio di scripting come Python ad esempio. È un derivato del Java con una nuova sintassi tale da renderlo molto familiare ai programmatori Java. Vi posso assicurare che dopo un po’ che lo si usa si ha la sensazione che non sia poi così tanto diverso da PHP.
Le caratteristiche di Groovy risolvono una serie di problematiche di produttività ed eleganza con un buon compromesso sulla tipizzazione.
Essenzialmente è un linguaggio “Java light” che rispetto a Java ha alcuni rilassamenti:
Si possono definire i tipi delle variabili “in corsa” senza specificarne il tipo che viene dedotto al momento della compilazione (come avviene anche per PHP 7) def pi = 3.14 e pi sarà un Float
La tipizzazione non è rigida come con Java, praticamente scompare la differenza tra classe e tipo primitivo: int a=1 equivale a Integer a = 1
non sono necessari i ; alla fine dell’istruzione
overload degli operatori per gli oggetti. Ad esempio si potrà scrivere indifferentemente l’operatore == tra due int o due Integer, o addirittura tra un int e un Integer:
int a=2 // in Java sarebbe un tipo primitivo
print(a)
Integer b=2 // in Java sarebbe una classe
print(a.equals(b)) // stampa true
print(a==b) // stampa true
Per testare costrutti Groovy si può utilizzare la consolle che si può far partire da IDE selezionando Tools > Groovy console:
Uso della console per scivere programmi Groovy…
e cliccando sulla freccetta bianca in alto a sinistra si compila ed esegue il programma:
… ed esecuzione su console di programmi Groovy.
Tips on Groovy programming
Ci sono alcune pratiche utili che consentono di sviluppare in modo coerente.
Io sono partito da questa esigenza: voglio che ogni modello (o dominio come viene chiamato in Grails) debba possedere uno standard, ad esempio voglio che tutti i modelli siano corredati dai campi ch riportano lo username del creatore dell’oggetto, e i timestamp di creazione e modifica.d
Grails ha il notevole supporto ai template per ogni oggetto del paradigma MVC: dai model, ai controller alle alle view.
Per installare nella propria applicazione i template si ricorre al comando
Ho sperimentato per prima la prima modalità IDE, creando l’applicazione Test1:C
Creare un nuovo progetto con Intelij IDEA
Il wizard richiede di impostare la versione di Grails da utilizzare, la Java Virtual Machine che eseguirà la compilazione e il tipo dell’applicazione (se app o se plugin)
Per questa applicazione uso Java 1.8 come JVM e Grails 2.3.11 per allinearmi con le versioni utilizzate dal Cliente. Alla fine mi viene chiesto il nome dell’applicazione:
Parte il build dell’applicazione utilizzando la versione di Grails 2.3.1 selezionata:
/usr/lib/jvm/jdk1.8.0_111/bin/java -Dgrails.home=/home/marcob/.sdkman/candidates/grails/2.3.11 -Dtools.jar=/usr/lib/jvm/jdk1.8.0_111/lib/tools.jar -Dgroovy.starter.conf=/home/marcob/.sdkman/candidates/grails/2.3.11/conf/groovy-starter.conf -Dbase.dir=/home/marcob/IdeaProjects/Test1 -Dfile.encoding=UTF-8 -classpath /home/marcob/.sdkman/candidates/grails/2.3.11/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-2.1.9.jar:/home/marcob/.sdkman/candidates/grails/2.3.11/dist/grails-bootstrap-2.3.11.jar org.codehaus.groovy.grails.cli.support.GrailsStarter --main org.codehaus.groovy.grails.cli.GrailsScriptRunner --conf /home/marcob/.sdkman/candidates/grails/2.3.11/conf/groovy-starter.conf "idea-print-project-settings -plain-output"
|Loading Grails 2.3.11
|Configuring classpath
.
|Environment set to development
....
|Installing zip scaffolding-2.0.3.zip…
...
|Installed plugin scaffolding-2.0.3
..........
---=== IDEA Grails build settings ===---
#
Tue Aug 13 12:12:01 CEST 2019
grails.work.dir=/home/marcob/.grails/2.3.11
grails.project.work.dir=target/work
....
Compile.0=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-2.1.9.jar
Compile.1=/home/marcob/.sdkman/candidates/grails/2.3.11/dist/grails-plugin-rest-2.3.11.jar
....
Runtime.0=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-2.1.9.jar
Runtime.1=/home/marcob/.sdkman/candidates/grails/2.3.11/dist/grails-plugin-rest-2.3.11.jar
....
Test.0=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/javax.servlet/javax.servlet-api/jars/javax.servlet-api-3.0.1.jar
Test.1=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-2.1.9.jar
....
Provided.0=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/javax.servlet/javax.servlet-api/jars/javax.servlet-api-3.0.1.jar
Build.0=/home/marcob/.sdkman/candidates/grails/2.3.11/lib/xalan/serializer/jars/serializer-2.7.1.jar
Build.1=/home/marcob/.sdkman/candidates/grails/2.3.11/dist/grails-bootstrap-2.3.11.jar
....
---=== End IDEA Grails build settings ===---
Ometto per comodità la maggior parte del log che non presenta messaggi d’errore.
Alla fine l’applicazione è creata e viene popolato l’albero dei file all’interno della directory
Si seleziona innanzitutto il tipo di progetto: Applicazione o plugin: la stessa possibilità c’è nella IDE ma per iniziare partiamo dalle Applicazioni.
Si sceglie il nome dell’applicazione e la versione di Grails: purtropppo le versioni selezionabili non sono vecchie come quelle che usa il mio Cliente quindi scelgo la 3.2.13 (che ho anche a bordo della mia macchina):
Come si vede, ho installato le versioni 2.1.5, 2.2.3, 2.3.11, 3.2.13 e quest’ultima è quella preselezionata nella sessione di shell in cui ho lanciato il comando
Tra le features, lascio le scelte standard: Hibernate5 in particolare è lo strato software che permette di astrarre dal livello database (ORM, Object Relationship Mapping).
Premendo il pulsante Generate viene scaricato il package myapp.zip:
Lo scarico e unzippo dentro a ~/IdeaProjects/Test1. Dopo di che lo apro con la IDE e questa si deve riscaricare tutti i package dipendenti, ad esempio Gradle (che è il tool per fare il build della applicazione che manca nel file zip)
Download https://services.gradle.org/distributions/gradle-3.4.1-bin.zip (70,31 MB)
Download https://services.gradle.org/distributions/gradle-3.4.1-bin.zip finished succeeded, took 57 s 539 ms
Gradle Daemon started in 840 ms
Download https://repo.grails.org/grails/core/org/grails/grails-gradle-plugin/3.2.13/grails-gradle-plugin-3.2.13.pom
...
Creazione da linea di comando
L’ultimo modo per creare l’applicazione è da linea di comando. Essa assomiglia molto alla modalità php artisan per creare le applicazioni in PHP/Laravel. Innanzitutto si scarica SDKMan:
Dopo di che si può sviluppare con l’IDE che si vuole.
Build and Go!
Una volta terminata la fase di startup dell’applicazione è il momento di programmare e costruire l’applicazione vera e propria. Ma a questo punto siamo già davanti ad una applicazione di base funzionante. Usiamo gradle (come artisan) e facciamo il build aprendo un terminale da dentro la directory del progetto
marcob@jsbach[16:12:15]:myapp$ ./gradlew bootRun
Starting a Gradle Daemon (subsequent builds will be faster)
:compileJava NO-SOURCE
:compileGroovy
:buildProperties
:processResources
:classes
:findMainClass
:bootRun
Grails application running at http://localhost:8080 in environment: development
Building 85% > :bootRun
Quando lo script ha raggiunto la fase “running”, puntare il browser all’indirizzo http://localhost:8080
Come si presenta l’applicazione Groovy on Grails una volta avviata
Attenzione! c’è un comando in più da dare: prima di avviare il build, rendere eseguibile lo script gradlew:
marcob@jsbach[16:11:48]:myapp$ chmod a+x gradlew
Importazione in Intellij IDEA
Gradle Daemon started in 1 s 722 ms
CONFIGURE SUCCESSFUL
Total time: 10.864 secs
Manifest.writeTo(Writer) has been deprecated and is scheduled to be removed in Gradle 4.0. Please use Manifest.writeTo(Object) instead.
wget è un programma Linux che effettua il download di una risorsa web agendo allo stesso modo dei crawlers dei motori di ricerca e la deposita in un file in locale nel computer in cui si è lanciato il comando.
Digitando
$ wget https://www.mysite.org
scarico in locale la pagina indice:
marcob@jsbach:mysite$ ll
totale 20
drwxr-xr-x 2 marcob marcob 4096 ago 7 15:01 ./
drwxr-xr-x 4 marcob marcob 4096 ago 7 15:00 ../
-rw-r--r-- 1 marcob marcob 11395 ago 7 15:01 index.html
Se c’è una gerarchia di directory posso scaricare anche quella, cioè scaricare tutti i file le directory e le sottodirectory, con l’opzione -r (recursive) e specificando il numero di livelli di gerarchia a cui voglio scendere al massimo; per esempio se voglio fermarmi al terzo:
$ wget -r -l3 https://www.mysite.org
Spesso però i siti vengono impostati in modo da bloccare questo tipo di download massivo, ma wget è molto potente: possiamo inviare nella richiesta anche degli header HTTP personalizzati con l’opzione ‐‐header, ad esempio fingendoci un browser vero:
$ wget -r -l3 --no-parent --header="Accept: text/html" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0" https://www.mysite.org
Tuttavia il web server può sempre bloccare lo user agent quando il numero di pagine richieste al secondo è troppo elevata. Con queste opzioni ci presentiamo al server come se fossimo un browser Firefox per sistema operativo Mac OS X. Inoltre specificando ‐‐no-parent evitiamo di fare il download anche delle eventuali cartelle soprastanti quella di cui vogliamo fare il download.
Come ultimo esempio, potendo inviare header HTTP a piacere, possiamo inviare l’header di autenticazione per siti protetti con autenticazione. Ovviamente dobbiamo avere un account in quel sito e possiamo evitare di mandare in chiaro la password in http utilizzando l’opzione ‐‐ask-password:
$ wget -r -l10 --no-parent --user=myuser --ask-password --header="Accept: text/html" --user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0" http://www.mysite.org
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