Diario per Applicazioni Web, a.a. 2014/15

Matteo Vaccari > Applicazioni Web

Sommario

Lezione 1, 2015-03-06

Argomenti: HTTP, URI, URL (slides)

Esercizi

Per eseguire questi esercizi usa Linux oppure un Mac. Non Windows. Se non hai una macchina Linux, puoi usare una macchina virtuale: scarica VirtualBox e installaci sopra Ubuntu. Ti consiglio di scaricare la macchina virtuale prefabbricata da http://virtualboxes.org/images/ubuntu/

Installa netcat (nc).

Esercizi svolti in laboratorio

Abbiamo usato nc per interrogare un server che permette di giocare a "hangman" (in Italiano e' il gioco dell'"impiccato" o del "prigioniero"). Consiste nell'indovinare una parola di cui conosciamo soltanto la lunghezza. Possiamo proporre una lettera; se la parola da indovinare contiene questa lettera, verra' mostrata per esempio cosi': ***e**e

L'indirizzo del server: http://aw-hangman.herokuapp.com/

Esegui il seguente esercizio, seguendo le istruzioni passo a passo. Nota: in laboratorio abbiamo usato il tool netcat, ma sulla versione che ho pubblicato su Heroku netcat non funziona. Lo sostituiamo con telnet. (Se vuoi sapere il motivo, e' che formalmente la fine linea va specificata con due byte: CR e LF. Con netcat, il tasto invio manda soltanto LF. Telnet invece li invia entrambi).

Questo server non ha le istruzioni, ma dalle sue risposte possiamo dedurre come interrogarlo.

Lo interrogo con

  telnet aw-hangman.herokuapp.com 80
  GET / HTTP/1.1
  host: aw-hangman.herokuapp.com
  <invio>

Quello che ho scritto sopra significa: eseguo il comando telnet aw-hangman.herokuapp.com 80 sul terminale di Unix, e poi scrivo o copiaincollo le righe seguenti. La scritta <invio> significa che per completare il messaggio devo mandare una riga vuota.

Cosi' facendo ottengo

  HTTP/1.1 200 OK
  Content-Type: application/json; charset=UTF-8
  Content-Length: 32
  Server: Jetty(6.1.26)

  {"index":"/",
  "users":"/users"}

Il formato di questa risposta si chiama JavaScript Object Notation (JSON). Viene spesso utilizzato per realizzare servizi web. Questa risposta mi dice che ci sono due url che posso interrogare: http://aw-hangman.herokuapp.com/ (la url che ho appena interrogato) e http://aw-hangman.herokuapp.com/users.

Se proviamo a interrogare quest'ultima che cosa otteniamo?

  telnet aw-hangman.herokuapp.com 80
  GET /users HTTP/1.1
  host: aw-hangman.herokuapp.com
  <invio>

  HTTP/1.1 405 Method Not Allowed
  Content-Type: application/json; charset=UTF-8
  Content-Length: 103
  Server: Jetty(6.1.26)

  {"status_code":405,"status":"Method not allowed",
  "description":"Use POST on /users to create a user"}

Questo ci dice che il metodo GET non e' ammesso per questa url. Che cosa facciamo allora? Seguiamo il suggerimento. Quello che vogliamo fare a questo punto e' creare un nuovo utente.

  telnet aw-hangman.herokuapp.com 80
  POST / HTTP/1.1
  host: aw-hangman.herokuapp.com
  <invio>

  HTTP/1.1 400 Bad Request
  Content-Type: application/json; charset=UTF-8
  Content-Length: 89
  Server: Jetty(6.1.26)

  {"status_code":400,"status":"Bad Request",
  "description":"Parameter 'name' is required"}

Hmmm. Apparentemente dobbiamo aggiungere un parametro "name". Proviamo:

  POST / HTTP/1.1
  host: aw-hangman.herokuapp.com
  Content-Type: application/x-www-form-urlencoded
  Content-length: 10

  name=pippo

Il formato della richiesta POST e' come sopra. I parametri vengono passati nel body. Otteniamo

  HTTP/1.1 400 Bad Request
  Content-Type: application/json; charset=UTF-8
  Content-Length: 93
  Server: Jetty(6.1.26)

  {"status_code":400,"status":"Bad Request",
  "description":"Parameter 'password' is required"}

Aha! Manca un'altro parametro. Aggiungiamolo:

  POST / HTTP/1.1
  host: aw-hangman.herokuapp.com
  Content-Type: application/x-www-form-urlencoded
  Content-length: 26

  name=pippo&password=secret

Questa volta otteniamo:

  HTTP/1.1 303 See Other
  Content-Type: application/json; charset=UTF-8
  Location: http://aw-hangman.herokuapp.com/users/551350b3
  Content-Length: 71
  Server: Jetty(6.1.26)

  {"status_code":201,"status":"Created",
  "location":"/users/551350b3"}

Come possiamo vedere, un nuovo utente e' stato creato con id 551350b3.

Se seguiamo la nuova url otteniamo

  GET /users/551350b3 HTTP/1.1
  host: aw-hangman.herokuapp.com
  <invio>

  HTTP/1.1 403 Forbidden
  Content-Type: application/json; charset=UTF-8
  Content-Length: 181
  WWW-Authenticate: Basic realm="hangman"      
  Server: Jetty(6.1.26)

  {"status_code":401,"status":"Unauthorized",
  "description":"You don't have the permission to access the requested resource."}

Direi che le ragioni del server sono comprensibili. Abbiamo creato un utente con una password, e ora per accedervi dobbiamo fornire le credenziali. Lo header WWW-Authenticate mi invita ad autenticarmi con il metodo Basic authentication. In pratica devo concatenare le credenziali con il due-punti: pippo:secret e poi codificarle con Base64. Posso usare la funzione btoa di Javascript; basta aprire la console javascript e digitare btoa("pippo:secret") e ottengo cGlwcG86c2VjcmV0. Da qui:

  GET /users/551350b3 HTTP/1.1
  host: aw-hangman.herokuapp.com
  authorization: Basic cGlwcG86c2VjcmV0
  <invio>

  HTTP/1.1 200 OK
  Content-Type: application/json; charset=UTF-8
  Content-Length: 84
  Server: Jetty(6.1.26)

  {"id":"551350b3",
  "prisoners":"/users/551350b3/prisoners",
  "url":"/users/551350b3"}

Da questo momento in poi prosegui l'esercizio da solo. Il risultato della ricerca precedente ci da' una nuova url da esplorare. Apparentemente ogni user ha dei prisoners. Il meccanismo da usare sara' lo stesso: con GET vedremo che la lista dei nostri prisoners e' vuota. Ne potremo creare uno facendo una POST alla url dei prisoners del nostro utente. Se la POST ha successo, ci verra' restituita la URL del nostro prigioniero, che avra' la forma /users/123/prisoners/456. Facendo GET a questa url osserviamo lo stato del gioco. Facendo POST a questa url, potremo tentare di indovinare, una lettera per volta.

Riesci a vincere? :-)

Da studiare

Materiali per approfondimenti facoltativi

Lezione 2, 20/03/2015

Argomenti: HTML, Web Standards, Character Encodings (slides)

Da studiare

Approfondimenti facoltativi

Esercizi

Costruire alcune form HTML che ci permettano di giocare a hangman in maniera più agevole. Nota bene: la interfaccia utente che costruiamo qui è molto rozza, ma è il massimo che possiamo fare senza programmare.

Primo passo: creare un utente

Scrivi un file hangman.html che contenga:


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Hangman Client</title>
  </head>
  <body>
    <h1>Hangman!</h1>

    <form action="http://aw-hangman.herokuapp.com/users" method="post">
      <p><label for="name">Name</label><input type="text" name="name" value=""/></p>
      <p><label for="password">Password</label><input type="text" name="password" value=""/></p>
      <p><input type="submit" value="Create user"/></p>
    </form>
  </body>
</html>

Apri il file in un browser, riempi i campi e premi "Create user". Se tutto funziona correttamente, avremo inviato una POST al server, il quale risponderà con una redirezione. Nel browser vedremo qualcosa tipo:

Messaggio di Unauthorized da parte dell applicazione

Che cosa è successo? È successo che il browser ha cercato di fare la redirezione a http://aw-hangman.herokuapp.com/users/54ea83d1, ma non ha fornito il parametro "password" sulla query string. Di conseguenza, l'applicazione ha risposto che non siamo autorizzati a visitare questa url. Ma la creazione dell'utente ha avuto successo! Ce ne accorgiamo perché la url nella barra degli indirizzi del browser è cambiata. Ora c'è l'id dell'utente appena creato.

Ricorda quello che abbiamo detto alla lezione 1: dopo il post, usualmente abbiamo una redirect!

Per confermare che quello che dico è vero, prova ad aggiungere la password nella query string. Vediamo così il nostro user.

Il nostro user

A questo punto sappiamo l'id del nostro utente e sappiamo la password. La strada è aperta: bisogna scrivere un'altra form per creare un prisoner. Siamo costretti a cablare l'id dell'utente nel codice html, il che significa che se volessimo usare un'altro utente, dovremmo cambiare il codice html. Come dicevo, questa interfaccia utente è rozza, ma è il massimo che possiamo fare dato quello che sappiamo fino ad ora.

Scrivi una nuova form e crea un prisoner. Ricora che in tutte le form che fai da questo momento in poi dovrai aggiungere un campo nascosto che fornisce la password. Ricorda anche di non spaventarti se l'applicazione risponde con un codice 403. I casi sono due: o la tua richiesta non è andata a buon fine, e non e' stato creato niente; oppure il prisoner è stato creato, e lo vedi da come cambia la barra degli indirizzi del browser.

Una volta che hai creato un prisoner, devi trovare la parola! Crea un'altra form che ti permetta di inserire agevolmente le lettere. Anche qui: l'output dell'applicazione sul browser sarà sempre un messaggio di errore. Per vedere lo stato del prigioniero, ricarica la url nel browser aggiungendo la password sulla barra degli indirizzi.

Altri esercizi

  1. Scrivere un documento HTML che aperto nel browser abbia questo aspetto:
    Hello, World!
  2. Verificare che il codice dell'esercizio precedente sia HTML valido per mezzo del servizio validator.nu.
  3. Realizzare in HTML e validare con validator.nu:
    Hello, World!
  4. Nell'esercizio precedente, invece di scrivere “Perch&eacute;” provate a scriverlo con il carattere “é” che si trova sulla tastiera. Verificate che cosa succede nei vari casi:
  5. Realizzare in HTML e validare con validator.nu:
    Rendering di una pagina HTML di esempio
    (La url dell'immagine è http://www.ruby-lang.org/images/logo.gif)
  6. Realizzare in HTML e validare con validator.nu:
    Form html
  7. Verificare che mettendo o togliendo la dichiarazione <!DOCTYPE html> in testa al documento, l'aspetto grafico della pagina cambia leggermente.

Esempi di domande d'esame

Lezione 3, 2013-03-25

Argomenti: Cascading Style Sheets (CSS), applicare stili a documenti HTML, creare layout di pagina con CSS (slides).

Esercizi fatti in laboratorio

Scarica il repository degli esercizi. Le istruzioni sono dentro.

Da studiare

Studiare questi articoli:

Esempi di domande d'esame

Non è necessario imparare a memoria tutte le proprietà e i loro possibili valori. E' necessario però conoscere per lo meno le proprietà che usiamo negli esempi visti a lezione e nell'esercizio (vedi più avanti).

Approfondimenti facoltativi

Lezione 4, 2014-04-01

Argomenti: Il linguaggio JavaScript. Programmazione funzionale, closures. Programmazione a oggetti; prototipi; JSON. Manipolazione del DOM. Gestione degli eventi. Test unitari in JavaScript. (slides)

Studiare:

  1. Studiare i capitoli 1-6 di Pro JavaScript Techniques di John Resig. La versione pdf si può comprare per $32 circa.
  2. Guardare la presentazione JavaScript Survival Guide di Giordano Scalzo

Esercizi svolti in laboratorio

Quando si risolvono questi esercizi, ricordare di tenere sempre aperta nel browser la console JavaScript!!!

  1. Risolvere il primo problema del Progetto Euler scrivendo un programma JavaScript. Il risultato deve essere stampato sulla console JS usando `console.log("...")`.
  2. Modificare il programma precedente per avere un'interfaccia utente. Realizziamo una semplice form come la seguente:
    N:
    Quando l'utente inserisce un numero e preme "Euler 1", viene alcolata la somma di tutti gli interi positivi minori del numero inserito, che siano multipli di 3 o di 5. Per risolvere questo esercizio puo' essere utile questo canovaccio:
    <form id='my-form'>
      <input type="text" id="max" value="10">
      <input type="submit" value="Euler 1">
    </form>
    <p id="result"></p>
    
    <script>
    window.onload = function() {
      var form = document.getElementById("my-form")
      var max = document.getElementById("max")
      var result = document.getElementById("result")      
      form.onsubmit = function() {
        result.innerHTML = "Risultato: " + euler1_problem(max.value);
        return false;
      }
    }
    </script>
    
  3. Scrivere una applicazione che abbia due campi di testo etichettati F e C. Quando modifico il numero contenuto nel campo F, il campo C viene aggiornato con il valore della conversione da Fahrenheit a Celsius del valore. E viceversa!
  4. Facoltativo: risolvere Untrusted, un gioco che si risolve modificando programmi JavaScript.
  5. Facoltativo: risolvere i JavaScript Koans

Lezione 5, 2015-04-15

Argomenti: Ajax e jQuery (slides su Ajax e slides su jQuery da pagina 4 a 27 comprese.)

Studiare la documentazione della funzione $.ajax di jQuery

Esercizio svolto in laboratorio

Scaricare il progetto . La maniera consigliata di farlo è tramite il comando git clone https://github.com/xpmatteo/aw-hangman-client

L'obiettivo è di creare un client ajax per il server hangman della prima lezione. Esegui questi passi; a ogni passo verifica di avere ottenuto il risultato desiderato. Carica sempre la pagina html tenendo aperta la console JavaScript.

  1. Per riscaldamento, nascondi l'immagine animata che gira, usando un comando jQuery. Nascondi anche tutta l'interfaccia utente che sta sotto la riga orizzontale. Lascia visibile solo la form di registrazione.
  2. Registrazione. Collega alla form di registrazione uno handler JavaScript che crei un nuovo utente. Quando la chiamata ha successo, nascondi la form di registrazione e mostra l'interfaccia di gioco.
  3. Nuova partita. Collega al bottone che crea una nuova partita uno handler JavaScript. Quando la chiamata ha avuto successo, mostra i dati della partita negli spazi appositi
  4. Indovinare. Collega uno handler JavaScript alla form "Guess" in modo che si possa giocare la partita. Ad ogni guess, aggiorna lo stato visibile della partita.

Devi essere in grado di completare una partita!

Tieni conto che per semplificare le cose, ho tolto la richiesta di autenticazione del server.

Se non sai come iniziare, studia il codice qui sotto:

var player_name;
var player_password;
var player_url;
var current_game_url;

function format_json(json) {
  return JSON.stringify(json, null, 2)
}

function on_error(xhr) {
  console.log("error: " + JSON.stringify(xhr))
  $("#output").text(xhr.responseText)
  $("#spinner").hide()
}

function on_register_new_user_success(data) {
  console.log("success: " + JSON.stringify(data))
  player_url = data.location;
  $("#output").text("New user created: " + format_json(data))
  $("#make-new-user").hide()
  $("#current-game").show()
  $("#spinner").hide()
}
    
function register_new_user() {
  $("#spinner").show()
  player_name = $("input[name='name']").val();
  player_password = $("input[name='password']").val();
  $.ajax({
    type: "POST",
    url: "http://aw-hangman.herokuapp.com/users",
    data: {
      name: player_name,
      password: player_password,
    },
    success: on_register_new_user_success,
    error: on_error
  })
  return false;
}

function on_get_current_game_success(data) {
  console.log("Get current game: " + JSON.stringify(data))
  $("#spinner").hide()
  $("#output").text("New game created: " + JSON.stringify(data, null, 2) )      
  $("#prisoner").text(data.prisoner.word)
  // ... show all the fields...
  $("#guess-form").show()
}

function on_new_game_success(data) {
  console.log("New game created: " + JSON.stringify(data))
  current_game_url = data.location
  $.ajax({
    type: "GET",
    url: "http://aw-hangman.herokuapp.com" + current_game_url,
    success: on_get_current_game_success,
    error: on_error,
  })      
}

function new_game() {
  $("#spinner").show()
  $.ajax({
    type: "POST",
    url: "http://aw-hangman.herokuapp.com" + player_url + "/prisoners",
    success: on_new_game_success,
    error: on_error,
  })    
  return false;  
}

$(document).ready(function() {
  $('#make-new-user').submit(register_new_user)
  $('#new-game').click(new_game)
})

Da studiare