Cantando con Sinatra - The Encore

Bentornati a Singing with Sinatra! In questa terza ed ultima parte estenderemo l'app "Recall" che abbiamo creato nella lezione precedente. Aggiungiamo un feed RSS all'app con l'utilissimo gioiello Builder, che rende la creazione di file XML in Ruby un pezzo di torta. Impareremo quanto è facile Sinatra fa sfuggire l'HTML dall'input dell'utente per prevenire gli attacchi XSS e miglioreremo il codice di gestione degli errori.


Gli utenti sono cattivi, m'kay

La regola generale quando si creano app Web è paranoica. Paranoico che ognuno dei tuoi utenti vuole ottenerti distruggendo il tuo sito o attaccando altri utenti attraverso di esso. Nella tua app, prova ad aggiungere una nuova nota con il seguente contenuto:

 woops 

Attualmente i nostri utenti sono liberi di inserire qualsiasi HTML a loro piace. Ciò lascia aperta l'app agli attacchi XSS in cui un utente può immettere JavaScript malizioso per attaccare o indirizzare erroneamente altri utenti del sito. Quindi la prima cosa che dobbiamo fare è escpe tutti i contenuti inviati dagli utenti in modo che il codice precedente venga convertito in entità HTML, in questo modo:

 woops 

Per fare ciò, aggiungi il seguente blocco di codice al tuo recall.rb file, ad esempio sotto il DataMapper.auto_upgrade! linea:

 gli helper includono Rack :: Utils alias_method: h,: escape_html end

Ciò include un insieme di metodi forniti da Rack. Ora abbiamo accesso a a h () metodo per sfuggire HTML.

Per evitare l'HTML nella pagina iniziale, apri il views / home.erb visualizza il file e modifica il <%= note.content %> linea (attorno alla linea 11) a:

 <%=h note.content %>

In alternativa avremmo potuto scrivere questo come <%= h(note.content) %>, ma lo stile sopra è molto più comune nella comunità di Ruby. Aggiorna la pagina e l'HTML inviato dovrebbe ora essere sfuggito e non eseguito dal browser:

XSS su altre pagine

Fai clic sul link "modifica" per la nota con il codice XSS e potresti pensare che sia sicuro: è tutto seduto in una textarea e quindi non è in esecuzione. Ma cosa succede se abbiamo aggiunto una nuova nota con il seguente contenuto:

  

Dai un'occhiata alla sua pagina di modifica e puoi vedere che abbiamo chiuso la textarea e quindi l'avviso JavaScript è stato eseguito. Quindi è chiaro che dobbiamo sfuggire al contenuto della nota su ogni pagina in cui viene visualizzata.

Dentro il tuo views / edit.erb visualizzare il file, sfuggire al contenuto all'interno del textarea facendolo scorrere attraverso il h metodo (riga 4):

 

E fai lo stesso con te views / delete.erb file sulla linea 2:

 

Sei sicuro di voler eliminare la seguente nota: "<%=h @note.content %>"?

Eccolo lì: siamo al sicuro da XSS. Ricordati di sfuggire a tutti i dati inviati dall'utente durante la creazione di altre app Web in futuro!

Ci si potrebbe chiedere "che dire di iniezioni SQL?" Bene, DataMapper gestisce questo per noi solo quando usiamo i metodi di DataMapper per ottenere dati dal database (ad esempio non eseguendo SQL raw).


RSS Feed the Masses

Una parte importante di qualsiasi sito web dinamico è una qualche forma di feed RSS, e la nostra app di richiamo non farà eccezione! Per fortuna è così incredibilmente facile da creare feed grazie alla gemma Builder. Installalo con:

 gem install builder

A seconda di come è stato configurato RubyGems sul tuo sistema, potrebbe essere necessario il prefisso gem installazione con sudo.

Ora aggiungi un nuovo percorso al tuo recall.rb file di applicazione per una richiesta GET a /rss.xml:

 ottieni '/rss.xml' do @notes = Note.all: order =>: id.desc builder: rss end

Assicurati di aggiungere questa rotta da qualche parte sopra il prendi "/: id" percorso, altrimenti una richiesta per rss.xml sarebbe scambiato per un ID post!

Nel percorso stiamo semplicemente richiedendo tutte le note dal database e caricando a rss.builder vedi il file. Nota come in precedenza stavamo usando il motore ERB per visualizzare a .erb file, ora stiamo usando Builder per elaborare un file. Un file Builder è principalmente un normale file Ruby con uno speciale xml oggetto per la creazione di tag XML.

Inizia il tuo views / rss.builder visualizza il file off con quanto segue:

 xml.instruct! : .xml,: version => "1.0" xml.rss: version => "2.0" do xml.channel do end end

Nota molto importante: Al primo secondo del blocco di codice sopra, rimuovi il punto (.) nel testo : .xml. WordPress interferisce con frammenti di codice.

Il costruttore lo analizzerà per essere:

     

Quindi abbiamo iniziato creando la struttura per un file XML valido. Ora aggiungiamo i tag per il titolo del feed, la descrizione e un link al sito principale. Aggiungi il seguente all'interno del xml.channel do bloccare:

 xml.title "Ricorda" xml.description "perché sei troppo occupato per ricordare" xml.link request.url

Nota come stiamo ottenendo l'attuale URL dal richiesta oggetto. Possiamo codificarlo manualmente, ma l'idea è che puoi caricare l'app ovunque senza dover modificare parti oscure del codice.

C'è un problema però, il link è ora impostato su (per esempio) http: // localhost: 9393 / rss.xml. Idealmente vorremmo che il link fosse sulla home page e non sul feed. Il richiesta l'oggetto ha anche un PATH_INFO metodo che è impostato sulla stringa del percorso corrente; quindi nel nostro caso, /rss.xml.

Sapendo questo, ora possiamo usare quello di Ruby chomp metodo per rimuovere il percorso dalla fine dell'URL. Cambiare il xml.link request.url linea a:

 xml.link request.url.chomp request.path_info

Il link nel nostro file XML è ora impostato su http: // localhost: 9393. Ora possiamo scorrere ogni nota e creare un nuovo elemento XML per questo:

 @ notes.each do | note | xml.item do xml.title h note.content xml.link "# request.url.chomp request.path_info / # note.id" xml.guid "# request.url.chomp request.path_info / # note.id "xml.pubDate Time.parse (note.created_at.to_s) .rfc822 xml.description h note.content end end

Nota che sulle righe 3 e 7 usciamo dal contenuto della nota h, proprio come abbiamo fatto nelle viste principali. È un po 'strano visualizzare lo stesso contenuto per entrambi titolo e il descrizione tag, ma stiamo seguendo la guida di Twitter qui, e non ci sono altri dati che possiamo mettere lì.

Sulla linea 6 stiamo convertendo la nota created_at tempo per RFC822, il formato richiesto per i tempi nei feed RSS.

Ora provalo in un browser! Vai a /rss.xml e le tue note dovrebbero essere visualizzate correttamente.


DRY Do not Repeat Yourself

C'è un piccolo problema con la nostra implementazione. Nella nostra vista RSS abbiamo il titolo e la descrizione del sito. Li abbiamo anche nel views / layout.erb file per la parte principale del sito. Ma ora se volessimo cambiare il nome o la descrizione del sito, ci sono due posti diversi che dobbiamo aggiornare. Una soluzione migliore sarebbe quella di impostare il titolo e la descrizione in uno posizionare, quindi fare riferimento a loro da lì.

Dentro il recall.rb file dell'applicazione, aggiungere direttamente le due righe seguenti all'inizio del file dopo il richiedere dichiarazioni, per definire due costanti:

 SITE_TITLE = "Richiama" SITE_DESCRIPTION = "perché sei troppo impegnato per ricordare"

Ora di nuovo dentro views / rss.builder cambia le linee 4 e 5 a:

 xml.title SITE_TITLE xml.description SITE_DESCRIPTION

E dentro views / layout.erb cambiare il </code> etichetta sulla linea 5 a:</p> <pre> <title><%= "#@title | #SITE_TITLE" %>

E cambia il h1 e h2 tag title sulle righe 12 e 13 per:

 

<%= SITE_TITLE %>

<%= SITE_DESCRIPTION %>

Dovremmo anche includere un link al feed RSS nel capo della pagina in modo che i browser possano visualizzare un pulsante RSS nella barra degli indirizzi. Aggiungi quanto segue direttamente prima del etichetta:

 

Messaggi Flash Errori e successi

Abbiamo bisogno di un modo per informare l'utente quando qualcosa è andato storto - o giusto, come un messaggio di conferma quando viene aggiunta una nuova nota, una nota rimossa ecc..

Il modo più comune e logico per raggiungere questo obiettivo è tramite "messaggi flash": un breve messaggio aggiunto alla sessione del browser dell'utente, che viene visualizzato e cancellato nella pagina successiva visualizzata. E ci sono solo un paio di RubyGems per aiutarti a raggiungere questo obiettivo! Immettere quanto segue nel terminale per installare Rack Flash e Sinatra Redirect con le gemme Flash:

 gem installare rack-flash sinatra-redirect-with-flash

A seconda di come è stato configurato RubyGems sul tuo sistema, potrebbe essere necessario il prefisso gem installazione con sudo.

Richiedi le gemme e attiva la loro funzionalità aggiungendo quanto segue nella parte superiore della tua recall.rb file dell'applicazione:

 richiede 'rack-flash' richiede l'opzione 'sinatra / redirect_with_flash': le sessioni usano Rack :: Flash,: sweep => true

L'aggiunta di un nuovo messaggio flash è semplice come flash [: error] = "Qualcosa è andato storto!". Mostriamo un errore sulla home page quando non ci sono note nel database.

Cambia il tuo ottenere '/' percorso a:

 get '/' do @notes = Note.all: order =>: id.desc @title = 'All Notes' se @ notes.empty? flash [: errore] = 'Nessuna nota trovata. Aggiungi il tuo primo sotto. ' end erb: home end

Molto semplice. Se la @gli appunti la variabile di istanza è vuota, crea un nuovo errore flash. Per visualizzare questi messaggi flash sulla pagina, aggiungi quanto segue al tuo views / layout.erb file, prima del <%= yield %>:

 <% if flash[:notice] %> 

<%= flash[:notice] %> <% end %> <% if flash[:error] %>

<%= flash[:error] %> <% end %>

E aggiungi i seguenti stili al tuo pubblico / style.css file per visualizzare le notifiche in verde e gli errori in rosso:

 .notice color: green;  .error color: red; 

Ora la tua home page dovrebbe mostrare il messaggio "nessuna nota trovata" quando il database è vuoto:

Ora mostriamo un messaggio di errore o di successo a seconda che una nuova nota possa essere aggiunta al database. Cambia il tuo post '/' percorso a:

 post '/' do n = Note.new n.content = params [: content] n.created_at = Time.now n.updated_at = Time.now se n.save redirect '/',: notice => 'Nota creata con successo .' else redirect '/',: error => 'Impossibile salvare la nota.' fine fine

Il codice è piuttosto logico. Se la nota può essere salvata, reindirizzare alla pagina iniziale, con un messaggio flash 'avviso', altrimenti reindirizzare a casa con un messaggio flash di errore. Qui puoi vedere la sintassi alternativa per impostare un messaggio flash e reindirizzare la pagina offerta dalla gemma Sinatra-Redirect-With-Flash.

Sarebbe anche ideale visualizzare anche un errore nella pagina 'modifica nota' se la nota richiesta non esiste. Cambiare il prendi "/: id" percorso a:

 get '/: id' do @note = Note.get param [: id] @title = "Modifica nota ## params [: id]" se @note erb: modifica altro redirect '/',: error => "Impossibile trovare quella nota." fine fine

E anche sulla pagina di richiesta PUT per quando si aggiorna una nota. Modificare metti '/: id' a:

 metti '/: id' do n = Note.get params [: id] a meno che n redirect '/',: error => "Impossibile trovare quella nota." end n.content = params [: content] n.complete = params [: complete]? 1: 0 n.updated_at = Time.now se n.save reindirizza '/',: notice => 'Nota aggiornata correttamente.' else redirect '/',: error => 'Errore durante l'aggiornamento della nota.' fine fine

Cambiare il prendi '/: id / cancella' percorso a:

 get '/: id / delete' do @note = Note.get params [: id] @title = "Conferma cancellazione della nota ## params [: id]" if @note erb: modifica altro redirect '/', : error => "Impossibile trovare quella nota." fine fine

E la sua richiesta DELETE corrispondente, cancella '/: id' a:

 cancella '/: id' do n = Note.get params [: id] se n.destroy reindirizza '/',: notice => 'Nota cancellata con successo.' else redirect '/',: error => 'Errore durante l'eliminazione della nota.' fine fine

Infine, cambia il ottieni '/: id / completo' instradare a quanto segue:

 get '/: id / complete' do n = Note.get params [: id] a meno che n redirect '/',: error => "Impossibile trovare quella nota." end n.complete = n.complete? 0: 1 # capovolgi n.updated_at = Time.now se n.save reindirizza '/',: notice => 'Nota segnata come completa.' else redirect '/',: error => 'Errore marcatura nota come completa.' fine fine

E il gioco è fatto!

Un'app web funzionante, sicura e reattiva agli errori, scritta in una quantità sorprendentemente piccola di codice! In questa breve mini serie abbiamo imparato come elaborare varie richieste HTTP con un'interfaccia RESTful, gestire invii di moduli, sfuggire a contenuti potenzialmente pericolosi, connettersi a un database, lavorare con sessioni utente per visualizzare messaggi flash, generare un feed RSS dinamico e come gestire con garbo gli errori dell'applicazione.

Se si desidera portare ulteriormente l'app, è consigliabile esaminare l'autenticazione dell'utente, ad esempio con la gemma di autenticazione di Sinatra.

Se vuoi distribuire l'app su un server web, dato che Sinatra è costruito con Rake puoi facilmente ospitare le tue applicazioni Sinatra sui server Apache e Nginx installando Passenger.

In alternativa, controlla Heroku, una piattaforma di hosting basata su Git che semplifica l'implementazione delle tue applicazioni web Ruby git spingere heroku (sono disponibili account gratuiti!)

Se vuoi saperne di più su Sinatra, dai un'occhiata al Readme molto approfondito, alle pagine della documentazione e al libro Sinatra gratuito.

Nota: i file sorgente per ciascuna parte di questa miniserie sono disponibili su GitHub, insieme all'app finita.