-
Notifications
You must be signed in to change notification settings - Fork 0
Home
L'abbreviazione degli URL o abbreviazione degli indirizzi web (in inglese: URL shortening) è una tecnica utilizzata nell'ambito del Web per abbreviare lunghi indirizzi web (URL) in link di pochi caratteri. Questa possibilità è offerta gratuitamente da numerosi servizi web, tra cui i più popolari sono bit.ly, goo.gl, ... , tinyurl.com. Tali siti convertono i link di partenza in indirizzi che riportano il dominio del servizio, ed una sequenza di caratteri che codifica il link.
(fonte: Wikipedia)
Perché utilizzarli?
Sono più eleganti: al di là del risparmio di spazio, che è un reale problema solo per Twitter dato il limite dei 140 caratteri, non si può negare che un link abbreviato sia anche più bello da vedere e meno invasivo. Inoltre molti servizi di URL shortnening permettono di creare dei Vanity URL, ovvero URL abbreviate personalizzate che riflettono il nome del brand.
Incoraggiano la condivisione: immaginate una URL come quella descritta nel punto sopra, lunga diverse decine (se non un centinaio) di caratteri e contenente pattern complessi. Sarà praticamente impossibile da memorizzare. Le URL accorciate sono più facili da scrivere, copiare e condividere, quindi incoraggiano la diffusione virale.
Tracciano i click: alcuni software di URL shortening mettono a disposizione alcune statistiche di base relativamente al link condiviso, perlopiù numero di clicks, referrer di provenienza, dati su browser e sistema operativo e suddivisione per area geografica, in aggiunta ad altri dati che possono essere diversi da un software all’altro oppure disponibili solo previa creazione di un account. È un ottimo modo per monitorare l’efficacia di link distribuiti.
(fonte: tsw.it)
###2. Analisi dei requisiti
R1. Funzione di redirect: Se l'utente inserisce un URL abbreviato (es. shortify.com/aa42d) il sistema deve reindirizzare immediatamente l'utente alla pagina associata all'url abbreviato (es. www.google.it).
R1.1. Se l'URL abbreviato non è associato ad alcuna pagina allora l'utente deve essere reindirizzato alla pagina principale di Shortify.
R1.2. La funzione di redirect deve essere eseguita in tempi estremamente stretti per garantire una navigazione fluida all'utente.
R2. Funzione di inserimento di un nuovo URL da abbreviare: L'utente, dalla pagina principale di Shortify, deve poter inserire un URL che il sistema dovrà abbreviare automaticamente e dovrà presentare l'URL abbreviato all'utente.
R3. Funzione di inserimento di un nuovo URL da abbreviare con un alias personalizzato: L'utente, dalla pagina principale di Shortify, deve poter inserire un URL da abbreviare e un alias personalizzato da poter usare come URL abbreviato.
R3.1 All'utente non è concesso inserire profanità come alias.
R4. Funzione di preview per un URL abbreviato: L'utente, dalla pagina principale di Shortify, deve poter visualizzare l'URL reale associato ad un URL abbreviato senza essere reindirizzato.
R5. Funzione di calcolo e visualizzazione di statistiche relative all'URL: L'utente, dalla pagina principale di Shortify, deve poter visualizzare alcune statistiche relative all'URL come ad esempio: data di creazione dell'URL abbreviato, numero, località e browser utilizzato degli utenti che hanno cliccato sul link, data dell'ultimo click.
Vincolo 1: Il client deve essere progettato come Single Page Application (SPA). L'interfaccia utente deve risultare semplice, essenziale e intuitiva da utilizzare.
Vincolo 2: E' essenziale che i servizi offerti, in particolare la funzione di redirect, siano erogati con la massima velocità possibile. L'URL shortener non deve rallentare o bloccare la navigazione dell'utente sul web.
###3. Architettura del sistema e componenti utilizzati
Il sistema è stato costruito seguendo l'architettura three-tiers. Il web client è stato progettato come una Single Page Application scritta in AngularJS. L'applicazione è stata montata su un docker container contenente l'immagine di NGINX. L'application server e le API sono state progettate in Java8 e, per questo, ho utilizzato il framework Spark per la costruzione di un servizio RESTful. L'application server è stato montato su un docker container contenente un immagine di Java8. Ho scelto di utilizzare Redis come database key-value per la memorizzazione degli URL e delle statistiche. La connessione tra database e application server viene gestita utilizzando Jedis. Anche il database è contenuto in un docker container. I tre container vengono installati ed eseguiti utilizzando Docker compose.
ATTENZIONE: Il client è programmato per cercare l'application server su location.hostname + ":4567", cioè sullo stesso host del web server. Per questo motivo, è indispensabile che l'application server sia in esecuzione sulla stessa macchina del web server, uno in ascolto sulla porta 4567 e l'altro sulla porta 8085.
###4. Database e modello dei dati
####4.1. Dati da modellare####
URL originale (es. https://www.youtube.com/watch?v=dQw4w9WgXcQ)
URL abbreviato (es. http://shortify.com/af43b)
URL con alias personalizzato (es. http://shortify.com/alias)
Notiamo che un URL abbreviato o un alias può essere associato ad un solo URL originale, quindi possiamo modellare questi tre dati come coppie di stringhe chiave-valore, dove la chiave è rappresentata dall'abbreviazione o dall'alias.
Esempio: <af43b,http://www.facebook.com> <alias,http://www.google.it>
Statistiche (per URL): Data creazione link, Data e posizione geografica dell'ultimo click, Grafico dei browsers utilizzati dagli utenti, Grafico della posizione geografica degli utenti.
Per creare queste statistiche dobbiamo analizzare i dati degli utenti che utilizzano il link, creiamo quindi un entità Click che possiede i seguenti campi:
- URL associato al click
- ip dell'utente che ha cliccato sul link
- la sua posizione geografica
- data del click
- browser utilizzato
Siccome le statistiche devono essere ricalcolate ogni qual volta un utente clicca nuovamente sul link, non le memorizzeremo in database ma le ricalcoleremo ogni volta che un utente le richiederà. Memorizzeremo invece i Click degli utenti, utilizzeremo quindi una Lista di Click dove il primo elemento contiene i dati dell'utente che ha richiesto la creazione del link mentre l'ultimo elemento rappresenta l'ultimo click effettuato (i nuovi click verranno sempre aggiunti in coda).
Dato che i click devono essere organizzati in base all'URL associato, possiamo raffinare ancora questa modellazione: eliminiamo dall'oggetto click il campo "URL associato al click" e memorizziamo i click come coppie chiave-valore dove la chiave è l'URL associato (o meglio, l'URL abbreviato) e il valore è la lista di click: <abbreviazione o alias, Lista di Click>
Esempio: <af43b,List<Click>>
<alias,List<Click>>
####4.2. Analisi del database utilizzato####
Graph-oriented DBs Non ci sono grafi o relazioni complesse da memorizzare, pertanto questo tipo di DB non è adatto.
Column-oriented DBs Questo tipo di DB memorizza e categorizza i dati per colonna, permettendo di effettuare rapidamente operazioni su particolari colonne senza dover leggere interamente ogni riga, ad esempio, calcolare la media di tutti i valori di una specifica colonna. Non è adatto per il nostro scopo.
Document-oriented DBs Questo tipo di DB permette di memorizzare dati in forma di documento strutturato e permettono di effettuare query sugli elementi del documento. Siccome i nostri dati sono strutturalmente semplici non abbiamo bisogno di questo tipo di DB.
Key-Value DBs Dato che le uniche operazioni richieste dal nostro servizio sono quelle di lettura e scrittura di coppie chiave-valore per intero, senza operazioni sui valori, un database key-value è la scelta più appropriata.
Redis
Redis, scritto in C da Salvatore Sanfilippo, è un key-value store open source residente in memoria. Esso si basa infatti su una struttura a dizionario: ogni valore immagazzinato è abbinato ad una chiave univoca che ne permette il recupero.
Le motivazioni che mi hanno spinto a scegliere Redis:
- Performance elevate in lettura/scrittura. Redis, come memcached, memorizza e mantiene i dati in memoria RAM permettendo di effettuare operazioni di lettura e scrittura con elevatissima velocità, salvando i dati su disco in un secondo momento tramite snapshot ad intervalli regolari. Questo tuttavia porta lo svantaggio della perdita di dati residenti in RAM in caso di failure della macchina.
- Varietà di tipi di dato. Redis permette di memorizzare diversi tipi di dato tra cui delle liste, questa feature ci è molto utile per memorizzare le liste di Clicks.
####4.3. Salvataggio dei dati in memoria####
Per ogni URL creato inseriremo due record in database. Supponiamo che http://www.google.it
sia l'url originale da accorgiare e http://shortify.com/45dfa
sia l'url abbreviato. Allora in database salveremo:
<"45dfa", "http://www.google.it">
Complessità lettura/scrittura: O(1).
<"45dfa:clicks", Lista di Clicks>
dove la lista di Clicks in realtà è una lista di stringhe JSON che possono essere facilmente convertite in oggetti di tipo Click dall'application server. Complessità lettura intera lista: O(n), complessità scrittura elemento in coda: O(1).
###5. Application server###
Per informazioni relative a classi e metodi implementati, consultare la JavaDoc.
####5.1. Diagrammi delle dipendenze####
####5.2. Servizi implementati####
Servizi REST
L'application server comunica con il client attraverso HTTP. Vengono impostate due routes in ascolto:
-
metodo GET http://localhost:4567/:shorturl utilizzata dal client per richiedere un redirect all'URL originale associato a :shorturl. L'application server restituisce un JSON contenente l'URL richiesto. (es. http://localhost:4567/45fa2)
- Se all'interno di :shorturl è contenuto il simbolo + al termine, allora l'application server restituirà un JSON contenente sia l'URL originale associato più tutte le statistiche calcolate dal server. Il client provvederà a mostrare la preview del link e le statistiche all'utente. (es. http://localhost:4567/45fa2+)
-
metodo POST http://localhost:4567/inserturl utilizzata dal client per richiedere un URL abbreviato, l'URL da accorciare viene passato al server all'interno del request body in formato JSON. Se l'utente ha richiesto un alias personalizzato, anche questo viene passato nel request body. L'application server restituisce al client un JSON contenente l'abbreviazione associata all'URL, il client poi provvede a costruire l'URL completo e a mostrarlo all'utente.
In tutti i casi dalle request vengono anche estrapolate informazioni relative all'useragent e all'ip dell'utente per l'inserimento dei click in database.
Generazione URL abbreviato
L'abbreviazione viene generata generando un hash dell'URL originale con l'algoritmo SHA-1. L'hash viene convertito in esadecimale e vengono conservate solo i primi cinque caratteri. In caso di collisione, si procede a conservare anziché i primi cinque caratteri, la stringa dal secondo al sesto carattere e cosi via, avanzando sempre di una posizione.
IMPORTANTE: Quando un utente richiede l'abbreviazione di URL già presente in database il server non ne crea uno nuovo ma bensì restituisce quello già presente in database, questo perché grazie all'hash dell'URL è possibile individuare subito se è già memorizzato. Questo non vale per gli alias personalizzati.
Inserimento alias personalizzati
L'utente può scegliere di utilizzare un proprio alias al posto dell'abbreviazione generata con l'hash, in questo caso l'alias viene memorizzato in database nel campo chiave come se fosse un abbreviazione generata da un hash.
Filtro profanità
Gli alias personalizzati inseriti dall'utente vengono filtrati prima dell'inserimento. Al momento il server filtra gli alias basandosi su una lista di parole proibite in lingua inglese. Il codice e la lista in formato testo è implementata nel package utilities e nella cartella resources.
Analisi statistiche
Le statistiche vengono calcolate quando richieste dall'utente attraverso l'apposito metodo HTTP GET. L'applicazione provvede a caricare dal database l'intera lista dei click e crea un nuovo oggetto Statistics che si occupa di calcolare le informazioni richieste. Queste informazioni vengono poi convertite in JSON e restituite al client.
Populator
Il populator viene invocato ad avvio del server e provvede ad inserire all'interno del database, se non già presenti, un certo numero di URL e click casuali. Il codice è implementato nel package utilities.
Geolocalizzazione utente
La geolocalizzazione dell'utente avviene analizzando l'ip attraverso il servizio Maxmind. Il codice è implementato nel package utilities.
####5.3. Test automatizzati####
I test sono stati effettuati con JUnit sulle classi Services.class e URLServices.class che racchiudono i servizi principali offerti dall'applicazione. E' possibile trovare una descrizione dei vari test effettuati consultando la JavaDoc.
Per eseguire i test consultare la guida d'installazione presente nel file README.md
###6. Web server e client###
Il client è stato progettato come una Single Page Application scritta in AngularJS con il supporto di Yeoman e Grunt nella fase di sviluppo. Il client, oltre a fornire l'interfaccia grafica all'utente, si occupa anche di assicurarsi che i dati inseriti (URL da abbreviare e alias personalizzato) siano validi. In caso di un errore nei dati inseriti viene presentato all'utente un messaggio di errore e viene bloccato l'inoltro dei dati all'application server finché i dati non sono stati corretti.
###7. Analisi del costo delle operazioni###
Abbreviazione di un URL
- L'utente inserisce l'URL da accorciare e preme il pulsante "Shortify!". L'application server riceve i dati.
- L'applicazione calcola l'hash dell'URL e controlla se è già presente in database. (EXISTS : O(1))
3a. Se non esiste, inserisce l'URL in database (SET : O(1))
3b. Se esiste, recupera l'URL dal database (GET : O(1))
- Inserisce in database un Click contenente i dati dell'utente. (RPUSH : O(1))
- Restituisce l'abbreviazione al client.
Abbreviazione di un URL con alias personalizzato
- L'utente inserisce l'URL da accorciare e l'alias personalizzato e preme il pulsante "Shortify!". L'application server riceve i dati.
2.L'applicazione prova a inserire l'alias in database. (SETNX : O(1))
- Se l'operazione è andata a buon fine, inserisce in database un Click contenente i dati dell'utente. (RPUSH : O(1))
- L'applicazione restituisce al client l'alias se l'operazione è andata a buon fine o un messaggio di errore se l'alias era già presente in database.
Richiesta di redirect
- L'utente inserisce un url abbreviato, il client inoltra la richiesta di redirect all'application server.
- L'applicazione controlla se l'utente sta richiedendo l'url originale o le statistiche. L'utente sta richiedendo l'url originale.
- L'applicazione recupera dal database l'URL. (GET : O(1))
- Se l'URL è presente in database, inserisce in database un Click contenente i dati dell'utente. (RPUSH : O(1))
- L'applicazione restituisce al client l'URL o un messaggio di errore se non è presente in database.
Richiesta di statistiche
- L'utente inserisce un url abbreviato seguito dal simbolo +, il client inoltra la richiesta di redirect all'application server.
- L'applicazione controlla se l'utente sta richiedendo l'url originale o le statistiche. L'utente sta richiedendo le statistiche dell'url.
3.L'applicazione recupera l'URL originale dal database. (GET : O(1))
- L'applicazione recupera dal database la lista dei Click (lunghezza n) e calcola le statistiche. (LRANGE : O(n))
- L'applicazione restituisce al client l'URL e le relative statistiche.
Documentazione
1. Introduzione
2. Analisi dei requisiti
3. Architettura del sistema
4. Database e modello dati
4.1. Dati da modellare
4.2. Analisi del database utilizzato
4.3. Salvataggio dei dati in memoria
5. Application server
5.1. Diagrammi delle dipendenze
5.2. Servizi implementati
5.3. Test automatizzati
6. Web server e client
7. Analisi del costo delle operazioni