Chanter avec Sinatra - The Encore

Bienvenue à Chanter avec Sinatra! Dans cette troisième et dernière partie, nous étendrons l'application "Recall" que nous avons construite dans la leçon précédente. Nous allons ajouter un flux RSS à l'application avec la gemme Builder incroyablement utile, ce qui rend la création de fichiers XML dans Ruby un jeu d'enfant. Nous allons apprendre à quel point Sinatra permet d'échapper facilement au code HTML des entrées utilisateur pour empêcher les attaques XSS, et nous améliorerons certains des codes de traitement des erreurs..


Les utilisateurs sont mauvais, m'kay

La règle générale lors de la création d'applications Web est d'être paranoïaque. Paranoïde, chacun de vos utilisateurs cherche à vous attirer en détruisant votre site ou en attaquant d'autres utilisateurs à travers celui-ci. Dans votre application, essayez d'ajouter une nouvelle note avec le contenu suivant:

 fous 

Actuellement, nos utilisateurs sont libres d'entrer le code HTML qu'ils souhaitent. Cela laisse l'application ouverte aux attaques XSS où un utilisateur peut entrer du code JavaScript malicieux pour attaquer ou mal diriger d'autres utilisateurs du site. Donc, la première chose à faire est d'extraire tout le contenu soumis par l'utilisateur pour que le code ci-dessus soit converti en entités HTML, comme ceci:

 fous 

Pour ce faire, ajoutez le bloc de code suivant à votre rappel.rb fichier, par exemple sous le DataMapper.auto_upgrade! ligne:

 les aides incluent Rack :: Utils alias_method: h,: escape_html end

Cela inclut un ensemble de méthodes fournies par Rack. Nous avons maintenant accès à un h () méthode pour échapper à HTML.

Pour échapper au HTML sur la page d’accueil, ouvrez le views / home.erb voir le fichier et changer le <%= note.content %> ligne (ligne 11) à:

 <%=h note.content %>

Sinon, nous aurions pu écrire ceci comme <%= h(note.content) %>, mais le style ci-dessus est beaucoup plus commun dans la communauté Ruby. Actualisez la page et le code HTML soumis doit maintenant être échappé et non exécuté par le navigateur:

XSS sur les autres pages

Cliquez sur le lien "modifier" pour la note avec le code XSS, et vous penserez peut-être que c'est sûr - tout se trouve dans une zone de texte et ne s'exécute pas. Mais que se passe-t-il si nous ajoutons une nouvelle note avec le contenu suivant:

  

Jetez un coup d’œil à sa page d’édition et vous pouvez voir que nous avons fermé la zone de texte et que l’alerte JavaScript est exécutée. Nous devons donc clairement échapper au contenu de la note sur chaque page où elle est affichée..

À l'intérieur de votre views / edit.erb afficher le fichier, échapper au contenu de la zone de texte en le lançant à travers le h méthode (ligne 4):

 

Et fais de même dans ton views / delete.erb déposer sur la ligne 2:

 

Êtes-vous sûr de vouloir supprimer la note suivante: "<%=h @note.content %>"?

Voilà, nous sommes à l'abri de XSS. N'oubliez pas d'échapper à toutes les données soumises par les utilisateurs lors de la création d'autres applications Web à l'avenir.!

Vous vous demandez peut-être "qu’en est-il des injections SQL?" Eh bien, DataMapper le gère pour nous tant que nous utilisons les méthodes de DataMapper pour extraire des données de la base de données (c’est-à-dire ne pas exécuter de SQL brut).


RSS Feed the Masses

Une partie importante de tout site Web dynamique est une forme de flux RSS, et notre application Recall ne fera pas exception à la règle! Heureusement c'est incroyablement facile à créer des flux grâce à la gem Builder. Installez-le avec:

 gem install constructeur

Selon la configuration de RubyGems sur votre système, vous devrez peut-être préfixer bijou installer avec sudo.

Maintenant, ajoutez un nouvel itinéraire à votre rappel.rb fichier de demande pour une demande GET à /rss.xml:

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

Assurez-vous d'ajouter cette route quelque part au dessus de la get '/: id' route, sinon une demande de rss.xml serait confondu avec un post ID!

Dans l’itinéraire, nous demandons simplement toutes les notes de la base de données et nous chargeons un fichier. rss.builder Voir la fiche. Notez comment nous utilisions auparavant le moteur ERB pour afficher un .erb fichier, nous utilisons maintenant Builder pour traiter un fichier. Un fichier Builder est principalement un fichier Ruby normal avec une xml objet pour créer des balises XML.

Commencez votre views / rss.builder afficher le fichier avec les éléments suivants:

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

Note très importante: Sur la première seconde du bloc de code ci-dessus, supprimez le point (.) dans le texte : .xml. WordPress interfère avec des extraits de code.

Builder analysera ceci comme étant:

     

Nous avons donc commencé par créer la structure d'un fichier XML valide. Ajoutons maintenant des balises pour le titre du flux, une description et un lien vers le site principal. Ajouter ce qui suit à l'intérieur du xml.channel do bloc:

 xml.title "Recall" xml.description "parce que vous êtes trop occupé pour vous rappeler" xml.link request.url

Remarquez comment nous obtenons l'URL actuelle du demande objet. Nous pourrions le coder manuellement, mais l’idée est que vous pouvez télécharger l’application n’importe où sans avoir à changer de code obscur..

Il y a un problème cependant, le lien est maintenant défini sur (par exemple) http: // localhost: 9393 / rss.xml. Idéalement, nous souhaiterions que le lien mette vers la page d'accueil et non vers le fil. le demande objet a aussi un path_info méthode qui est définie sur la chaîne de route actuelle; donc dans notre cas, /rss.xml.

Sachant cela, nous pouvons maintenant utiliser Ruby's chomp méthode pour supprimer le chemin de la fin de l'URL. Changer la xml.link request.url ligne à:

 xml.link request.url.chomp request.path_info

Le lien dans notre fichier XML est maintenant défini sur http: // localhost: 9393. Nous pouvons maintenant parcourir chaque note et créer un nouvel élément XML:

 @ 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

Notez que sur les lignes 3 et 7, nous échappons au contenu de la note en utilisant h, comme nous l'avons fait dans les vues principales. C’est un peu étrange d’afficher le même contenu à la fois Titre et le la description balises, mais nous suivons l'exemple de Twitter ici, et il n'y a pas d'autres données que nous pouvons mettre là.

Sur la ligne 6, nous convertissons la note créé à time to RFC822, le format requis pour les durées dans les flux RSS.

Maintenant, essayez-le dans un navigateur! Aller à /rss.xml et vos notes devraient s'afficher correctement.


SECS Ne te répète pas

Il y a un problème mineur avec notre mise en œuvre. Dans notre vue RSS, nous avons le titre et la description du site. Nous les avons aussi dans le views / layout.erb fichier pour la partie principale du site. Mais maintenant, si nous voulions changer le nom ou la description du site, nous devons mettre à jour deux endroits différents. Une meilleure solution consisterait à définir le titre et la description dans un placer, puis les référencer à partir de là.

À l'intérieur de rappel.rb fichier d'application, ajoutez les deux lignes suivantes en haut du fichier, directement après la exiger instructions, pour définir deux constantes:

 SITE_TITLE = "Rappelez" SITE_DESCRIPTION = "Parce que vous êtes trop occupé pour vous en rappeler"

Maintenant de retour à l'intérieur views / rss.builder changer les lignes 4 et 5 pour:

 xml.title SITE_TITLE xml.description SITE_DESCRIPTION

Et à l'intérieur views / layout.erb changer la </code> Marquer sur la ligne 5 à:</p> <pre> <title><%= "#@title | #SITE_TITLE" %>

Et changer le h1 et h2 balises de titre sur les lignes 12 et 13 à:

 

<%= SITE_TITLE %>

<%= SITE_DESCRIPTION %>

Nous devrions également inclure un lien vers le flux RSS dans le tête de la page afin que les navigateurs puissent afficher un bouton RSS dans la barre d’adresse. Ajoutez ce qui suit directement avant le étiquette:

 

Messages Flash Erreurs et Succès

Nous avons besoin d’un moyen d’informer l’utilisateur lorsque quelque chose se passe mal ou correctement, comme un message de confirmation lorsqu’une nouvelle note est ajoutée, une note supprimée, etc..

Le moyen le plus courant et le plus logique d’atteindre cet objectif consiste à utiliser des "messages flash" - un message court ajouté à la session du navigateur de l’utilisateur, qui est affiché et effacé à la page suivante qu’ils consultent. Et il se trouve que quelques RubyGems peuvent aider à atteindre cet objectif! Entrez les informations suivantes dans le terminal pour installer le Rack Flash et la redirection Sinatra avec des gemmes Flash:

 gem installer rack-flash sinatra-redirect-with-flash

Selon la configuration de RubyGems sur votre système, vous devrez peut-être préfixer bijou installer avec sudo.

Exigez les gemmes et activez leur fonctionnalité en ajoutant ce qui suit près du sommet de votre rappel.rb dossier de candidature:

 nécessite 'rack-flash' nécessite 'sinatra / redirect_with_flash' activer: les sessions utilisent Rack :: Flash,: sweep => true

Ajouter un nouveau message flash est aussi simple que flash [: error] = "Quelque chose s'est mal passé!". Affiche une erreur sur la page d'accueil lorsqu'il n'y a pas de notes dans la base de données.

Change ton obtenir '/' Route vers:

 get '/' do @notes = Note.all: order =>: id.desc @title = 'Toutes les notes' if @ notes.empty? flash [: error] = 'Aucune note trouvée. Ajoutez votre premier ci-dessous. fin erb: fin de la maison

Très simple. Si la @Remarques la variable d'instance est vide, créez une nouvelle erreur flash. Pour afficher ces messages flash sur la page, ajoutez ce qui suit à votre views / layout.erb fichier, avant le <%= yield %>:

 <% if flash[:notice] %> 

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

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

Et ajoutez les styles suivants à votre public / style.css fichier pour afficher les notices en vert et les erreurs en rouge:

 .remarque couleur: vert;  .error color: red; 

À présent, votre page d'accueil devrait afficher le message "Aucune note trouvée" lorsque la base de données est vide:

Maintenant, affichons un message d'erreur ou de succès selon qu'une nouvelle note peut être ajoutée à la base de données. Change ton poste '/' Route vers:

 post '/' do n = Note.new n.content = params [: content] n.created_at = Time.now n.updated_at = Time.now si n.save redirect '/:, notice =>' Note créée avec succès ' else redirect '/',: error => 'Echec de l'enregistrement de la note.' fin fin

Le code est assez logique. Si la note peut être sauvegardée, redirigez-la vers la page d'accueil avec un message flash 'notice', sinon, redirigez vers la maison avec un message d'erreur. Ici, vous pouvez voir la syntaxe alternative pour définir un message flash et rediriger la page proposée par le gem Sinatra-Redirect-With-Flash..

L'idéal serait également d'afficher une erreur sur la page 'éditer note' si la note demandée n'existe pas. Changer la get '/: id' Route vers:

 get '/: id' do @note = Note.get params [: id] @title = "Modifier la note ## params [: id]" "si @note erb: éditer une autre redirection" / ',: error => "Vous ne trouvez pas cette note." fin fin

Et aussi sur la page de demande PUT pour mettre à jour une note. Changement mettre '/: id' à:

 put '/: id' do n = Note.get params [: id] sauf si n redirige '/',: error => "Vous ne trouvez pas cette note." end n.content = params [: content] n.complete = params [: complete]? 1: 0 n.updated_at = Time.now if n.save redirect '/',: notice => 'Remarque mise à jour avec succès.' else redirect '/',: error => 'Erreur lors de la mise à jour de la note.' fin fin

Changer la get '/: id / delete' Route vers:

 get '/: id / delete' do @ note = Note.get params [: id] @title = "Confirmer la suppression de la note ## params [: id]" "si @note erb: éditer une autre redirection '/', : error => "Vous ne trouvez pas cette note." fin fin

Et sa demande DELETE correspondante, supprimer '/: id' à:

 delete '/: id' do n = Note.get params [: id] si n.destroy redirect '/',: notice => 'Remarque supprimée avec succès.' else redirect '/',: error => 'Erreur lors de la suppression de la note.' fin fin

Enfin, changez le get '/: id / complete' route vers les sites suivants:

 get '/: id / complete' do n = Note.get paramètres [: id] sauf si n redirige '/',: error => "Vous ne trouvez pas cette note." end n.complete = n.complete? 0: 1 # retournez n.updated_at = Time.now si n.save redirect '/',: notice => 'Note marquée comme complète.' else redirect '/',: error => 'Erreur de marquage de la note comme complète.' fin fin

Et voila!

Une application Web fonctionnelle, sécurisée et sensible aux erreurs, écrite avec une quantité de code étonnamment petite! Au cours de cette courte mini-série, nous avons appris à traiter diverses requêtes HTTP avec une interface RESTful, à gérer les envois de formulaires, à échapper à un contenu potentiellement dangereux, à vous connecter à une base de données, à travailler avec les sessions utilisateur pour afficher des messages flash, à générer un flux RSS dynamique et comment gérer avec élégance les erreurs d'application.

Si vous souhaitez approfondir l'application, vous pouvez vous pencher sur le traitement de l'authentification d'utilisateur, comme avec le gem Authentification de Sinatra..

Si vous souhaitez déployer l'application sur un serveur Web, Sinatra étant construit avec Rake, vous pouvez très facilement héberger vos applications Sinatra sur des serveurs Apache et Nginx en installant Passenger..

Découvrez également Heroku, une plate-forme d’hébergement optimisée par Git qui simplifie le déploiement de vos applications Web Ruby. Git Push Heroku (des comptes gratuits sont disponibles!)

Si vous souhaitez en savoir plus sur Sinatra, consultez le très détaillé Readme, les pages de documentation et le livre gratuit de Sinatra..

Remarque: les fichiers source de chaque partie de cette mini-série sont disponibles sur GitHub, ainsi que l'application finie.