German (Deutsch) translation by Valentina (you can also view the original English article)
Willkommen zurück beim Singen mit Sinatra! In diesem dritten und letzten Teil werden wir die "Recall"-App erweitern, die wir in der vorherigen Lektion erstellt haben. Wir werden der App einen RSS-Feed mit dem unglaublich nützlichen Builder-gem hinzufügen, mit dem das Erstellen von XML-Dateien in Ruby zum Kinderspiel wird. Wir werden lernen, wie einfach Sinatra es macht, HTML aus Benutzereingaben zu entfernen, um XSS-Angriffe zu verhindern, und wir werden einige der Fehlerbehandlungscodes verbessern.
Benutzer sind schlecht, m'kay
Die allgemeine Regel beim Erstellen von Web-Apps lautet, paranoid zu sein. Paranoid, dass jeder Ihrer Benutzer darauf aus ist, Sie zu erreichen, indem er Ihre Website zerstört oder andere Benutzer dadurch angreift. Versuchen Sie in Ihrer App, eine neue Notiz mit folgendem Inhalt hinzuzufügen:
1 |
</article>woops <script>alert("zomg haxz");</script> |






Derzeit können unsere Benutzer beliebiges HTML eingeben. Dadurch bleibt die App für XSS-Angriffe offen, bei denen ein Benutzer möglicherweise bösartiges JavaScript eingibt, um andere Benutzer der Website anzugreifen oder fehlzuleiten. Das erste, was wir tun müssen, ist, alle vom Benutzer eingereichten Inhalte zu maskieren, damit der obige Code wie folgt in HTML-Entitäten konvertiert wird:
1 |
</article>woops <script>alert("zomg haxz");</script> |
Fügen Sie dazu der recall.rb
den folgenden Codeblock hinzu, z. B. unter DataMapper.auto_upgrade!
Linie:
1 |
helpers do |
2 |
include Rack::Utils |
3 |
alias_method :h, :escape_html |
4 |
end
|
Dies umfasst eine Reihe von Methoden, die von Rack bereitgestellt werden. Wir haben jetzt Zugriff auf eine h()
-Methode, um HTML zu umgehen.
Um HTML auf der Startseite zu umgehen, öffnen Sie die Ansichtsdatei views/home.erb
und ändern Sie die Zeile <%=note.content%>
(um Zeile 11) in:
1 |
<%=h note.content %> |
Alternativ hätten wir dies als <%=h(note.content) %>
schreiben können, aber der obige Stil ist in der Ruby-Community viel häufiger. Aktualisieren Sie die Seite und der übermittelte HTML-Code sollte jetzt maskiert und nicht vom Browser ausgeführt werden:



XSS auf den anderen Seiten
Klicken Sie auf den Link "Bearbeiten" für die Notiz mit dem XSS-Code, und Sie denken möglicherweise, dass sie sicher ist - alles befindet sich in einem Textbereich und wird daher nicht ausgeführt. Was aber, wenn wir eine neue Notiz mit folgendem Inhalt hinzufügen:
1 |
</textarea> <script>alert("haha")</script> |
Werfen Sie einen Blick auf die Bearbeitungsseite, und Sie können sehen, dass wir den Textbereich geschlossen haben und die JavaScript-Warnung ausgeführt wird. Es ist also klar, dass wir den Inhalt der Notiz auf jeder Seite, auf der sie angezeigt wird, umgehen müssen.
Entfliehen Sie in Ihrer Ansichtsdatei views/edit.erb
dem Inhalt im textarea
, indem Sie ihn mit der Methode h
ausführen (Zeile 4):
1 |
<textarea name="content"><%=h @note.content %></textarea> |
Und machen Sie dasselbe in Ihrer Datei views/delete.erb
in Zeile 2:
1 |
<p>Are you sure you want to delete the following note: <em>"<%=h @note.content %>"</em>?</p> |
Da haben Sie es - wir sind jetzt vor XSS sicher. Denken Sie daran, beim Erstellen anderer Webanwendungen in Zukunft alle vom Benutzer übermittelten Daten zu umgehen!
Sie fragen sich vielleicht: "Was ist mit SQL-Injektionen?" Nun, DataMapper erledigt das für uns genauso lange, wie wir DataMappers Methoden zum Abrufen von Daten aus der Datenbank verwenden (dh kein Raw-SQL ausführen).
RSS Füttern die Massen
Ein wichtiger Bestandteil jeder dynamischen Website ist eine Art RSS-Feed, und unsere Recall-App wird keine Ausnahme sein! Zum Glück ist es dank des Builder-gem unglaublich einfach, Feeds zu erstellen. Installieren Sie es mit:
1 |
gem install builder |
Abhängig davon, wie Sie RubyGems auf Ihrem System eingerichtet haben, müssen Sie der gem install
möglicherweise sudo
voranstellen.
Fügen Sie nun Ihrer recall.rb
-Anwendungsdatei eine neue Route für eine GET-Anforderung an /rss.xml
hinzu:
1 |
get '/rss.xml' do |
2 |
@notes = Note.all :order => :id.desc |
3 |
builder :rss |
4 |
end
|
Stellen Sie sicher, dass Sie diese Route irgendwo über der Route get'/:id'
hinzufügen, da sonst eine Anfrage nach rss.xml
mit einer Post-ID verwechselt wird!
In der Route fordern wir einfach alle Notizen aus der Datenbank an und laden eine rss.builder
-Ansichtsdatei. Beachten Sie, wie wir früher die ERB-Engine zum Anzeigen einer .erb
-Datei verwendet haben. Jetzt verwenden wir Builder zum Verarbeiten einer Datei. Eine Builder-Datei ist meist eine normale Ruby-Datei mit einem speziellen XML-Objekt zum Erstellen von xml
-Tags.
Starten Sie Ihre Ansichtsdatei views/rss.builder
wie folgt:
1 |
xml.instruct! :.xml, :version => "1.0" |
2 |
xml.rss :version => "2.0" do |
3 |
xml.channel do |
4 |
|
5 |
end
|
6 |
end
|
Sehr wichtiger Hinweis: Entfernen Sie in der ersten Sekunde des obigen Codeblocks den Punkt (.
) Im Text :.xml
. WordPress stört Code-Schnipsel.
Der Builder analysiert dies wie folgt:
1 |
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
<rss version="2.0"> |
3 |
<channel>
|
4 |
|
5 |
</channel>
|
6 |
</rss>
|
Wir haben also zunächst die Struktur für eine gültige XML-Datei erstellt. Fügen wir nun Tags für den Feed-Titel, die Beschreibung und einen Link zurück zur Hauptseite hinzu. Fügen Sie im Block xml.channel do
Folgendes hinzu:
1 |
xml.title "Recall" |
2 |
xml.description "'cause you're too busy to remember" |
3 |
xml.link request.url |
Beachten Sie, wie wir die aktuelle URL vom request
-Objekt erhalten. Wir könnten dies manuell codieren, aber die Idee ist, dass Sie die App überall hochladen können, ohne obskure Codeteile ändern zu müssen.
Es gibt jedoch ein Problem: Der Link ist jetzt (zum Beispiel) auf http://localhost:9393/rss.xml
eingestellt. Idealerweise möchten wir, dass der Link zur Startseite und nicht zurück zum Feed führt. Das request
-Objekt verfügt auch über eine path_info
-Methode, die auf die aktuelle Routenzeichenfolge festgelegt ist. also in unserem Fall /rss.xml
.
In diesem Wissen können wir jetzt Rubys chomp
-Methode verwenden, um den Pfad vom Ende der URL zu entfernen. Ändern Sie die Zeile xml.link request.url
in:
1 |
xml.link request.url.chomp request.path_info |
Der Link in unserer XML-Datei ist jetzt auf http://localhost:9393
festgelegt. Wir können jetzt jede Notiz durchlaufen und ein neues XML-Element dafür erstellen:
1 |
@notes.each do |note| |
2 |
xml.item do |
3 |
xml.title h note.content |
4 |
xml.link "#{request.url.chomp request.path_info}/#{note.id}" |
5 |
xml.guid "#{request.url.chomp request.path_info}/#{note.id}" |
6 |
xml.pubDate Time.parse(note.created_at.to_s).rfc822 |
7 |
xml.description h note.content |
8 |
end
|
9 |
end
|
Beachten Sie, dass wir in den Zeilen 3 und 7 den Inhalt der Notiz mit h
umgehen, genau wie in den Hauptansichten. Es ist etwas seltsam, sowohl für den title
als auch für die description
-Tags denselben Inhalt anzuzeigen, aber wir folgen hier dem Beispiel von Twitter, und es gibt keine anderen Daten, die wir dort ablegen können.
In Zeile 6 konvertieren wir die create_at
-Zeit der Notiz in RFC822, das erforderliche Format für Zeiten in RSS-Feeds.
Probieren Sie es jetzt in einem Browser aus! Gehen Sie zu /rss.xml
und Ihre Notizen sollten korrekt angezeigt werden.
TROCKEN Wiederholen Sie sich nicht
Bei unserer Implementierung gibt es ein kleines Problem. In unserer RSS-Ansicht haben wir den Site-Titel und die Beschreibung. Wir haben sie auch in der Datei views/layout.erb
für den Hauptteil der Site. Wenn wir nun den Namen oder die Beschreibung der Site ändern möchten, müssen wir zwei verschiedene Stellen aktualisieren. Eine bessere Lösung wäre, den Titel und die Beschreibung an einer Stelle festzulegen und von dort aus auf sie zu verweisen.
Fügen Sie in der Anwendungsdatei recall.rb
direkt nach den require
-Anweisungen die folgenden zwei Zeilen am Anfang der Datei hinzu, um zwei Konstanten zu definieren:
1 |
SITE_TITLE = "Recall" |
2 |
SITE_DESCRIPTION = "'cause you're too busy to remember" |
Zurück in den views/rss.builder
ändern Sie die Zeilen 4 und 5 in:
1 |
xml.title SITE_TITLE |
2 |
xml.description SITE_DESCRIPTION |
Und in den views/layout.erb
ändern Sie das <title>
-Tag in Zeile 5 in:
1 |
<title><%= "#{@title} | #{SITE_TITLE}" %></title> |
Ändern Sie die Titel-Tags h1
und h2
in den Zeilen 12 und 13 in:
1 |
<h1><a href="/"><%= SITE_TITLE %></a></h1> |
2 |
<h2><%= SITE_DESCRIPTION %></h2> |
Wir sollten auch einen Link zum RSS-Feed in den head
der Seite einfügen, damit Browser eine RSS-Schaltfläche in der Adressleiste anzeigen können. Fügen Sie Folgendes direkt vor dem </head>
-Tag hinzu:
1 |
<link href="/rss.xml" rel="alternate" type="application/rss+xml"> |
Flash-Meldungen Fehler und Erfolge
Wir brauchen eine Möglichkeit, den Benutzer zu informieren, wenn etwas schief gelaufen ist - oder richtig, z. B. eine Bestätigungsnachricht, wenn eine neue Notiz hinzugefügt, eine Notiz entfernt usw. wird.
Der häufigste und logischste Weg, dies zu erreichen, sind "Flash-Nachrichten" - eine kurze Nachricht, die der Browsersitzung des Benutzers hinzugefügt wird und auf der nächsten Seite angezeigt und gelöscht wird. Und es gibt zufällig ein paar RubyGems, um dies zu erreichen! Geben Sie Folgendes in das Terminal ein, um Rack Flash und Sinatra Redirect mit Flash Gems zu installieren:
1 |
gem install rack-flash sinatra-redirect-with-flash |
Abhängig davon, wie Sie RubyGems auf Ihrem System eingerichtet haben, müssen Sie der gem install
möglicherweise sudo
voranstellen.
Fordern Sie die Edelsteine an und aktivieren Sie ihre Funktionalität, indem Sie oben in Ihrer recall.rb
-Anwendungsdatei Folgendes hinzufügen:
1 |
require 'rack-flash' |
2 |
require 'sinatra/redirect_with_flash' |
3 |
|
4 |
enable :sessions |
5 |
use Rack::Flash, :sweep => true |
Das Hinzufügen einer neuen Flash-Nachricht ist so einfach wie flash[:error] = "Something went wrong!"
. Lassen Sie uns einen Fehler auf der Homepage anzeigen, wenn keine Notizen in der Datenbank vorhanden sind.
Ändern Sie Ihre get '/'
Route in:
1 |
get '/' do |
2 |
@notes = Note.all :order => :id.desc |
3 |
@title = 'All Notes' |
4 |
if @notes.empty? |
5 |
flash[:error] = 'No notes found. Add your first below.' |
6 |
end
|
7 |
erb :home |
8 |
end
|
Sehr einfach. Wenn die Instanzvariable @notes
leer ist, erstellen Sie einen neuen Flash-Fehler. Um diese Flash-Meldungen auf der Seite anzuzeigen, fügen Sie Ihrer Datei views/layout.erb
vor <%= yield %>
Folgendes hinzu:
1 |
<% if flash[:notice] %> |
2 |
<p class="notice"><%= flash[:notice] %> |
3 |
<% end %>
|
4 |
|
5 |
<% if flash[:error] %>
|
6 |
<p class="error"><%= flash[:error] %> |
7 |
<% end %>
|
Fügen Sie Ihrer Datei public/style.css
die folgenden Stile hinzu, um Hinweise in Grün und Fehler in Rot anzuzeigen:
1 |
.notice { color: green; } |
2 |
.error { color: red; } |
Jetzt sollte auf Ihrer Homepage die Meldung "Keine Notizen gefunden" angezeigt werden, wenn die Datenbank leer ist:



Lassen Sie uns nun entweder eine Fehler- oder eine Erfolgsmeldung anzeigen, je nachdem, ob der Datenbank eine neue Notiz hinzugefügt werden könnte. Ändern Sie Ihre post '/'
-Route in:
1 |
post '/' do |
2 |
n = Note.new |
3 |
n.content = params[:content] |
4 |
n.created_at = Time.now |
5 |
n.updated_at = Time.now |
6 |
if n.save |
7 |
redirect '/', :notice => 'Note created successfully.' |
8 |
else
|
9 |
redirect '/', :error => 'Failed to save note.' |
10 |
end
|
11 |
end
|
Der Code ist ziemlich logisch. Wenn die Notiz gespeichert werden konnte, leiten Sie sie mit einer Flash-Meldung "Hinweis" auf die Startseite weiter. Andernfalls leiten Sie sie mit einer Flash-Fehlermeldung nach Hause weiter. Hier sehen Sie die alternative Syntax zum Festlegen einer Flash-Nachricht und zum Umleiten der Seite, die vom Juwel Sinatra-Redirect-With-Flash angeboten wird.
Ideal wäre es auch, einen Fehler auf der Seite "Notiz bearbeiten" anzuzeigen, wenn die angeforderte Notiz nicht vorhanden ist. Ändern Sie die Route get'/:id'
in:
1 |
get '/:id' do |
2 |
@note = Note.get params[:id] |
3 |
@title = "Edit note ##{params[:id]}" |
4 |
if @note |
5 |
erb :edit |
6 |
else
|
7 |
redirect '/', :error => "Can't find that note." |
8 |
end
|
9 |
end
|
Und auch auf der PUT-Anforderungsseite zum Aktualisieren einer Notiz. Ändern Sie put '/:id'
in:
1 |
put '/:id' do |
2 |
n = Note.get params[:id] |
3 |
unless n |
4 |
redirect '/', :error => "Can't find that note." |
5 |
end
|
6 |
n.content = params[:content] |
7 |
n.complete = params[:complete] ? 1 : 0 |
8 |
n.updated_at = Time.now |
9 |
if n.save |
10 |
redirect '/', :notice => 'Note updated successfully.' |
11 |
else
|
12 |
redirect '/', :error => 'Error updating note.' |
13 |
end
|
14 |
end
|
Ändern Sie die Route get '/:id/delete'
in:
1 |
get '/:id/delete' do |
2 |
@note = Note.get params[:id] |
3 |
@title = "Confirm deletion of note ##{params[:id]}" |
4 |
if @note |
5 |
erb :edit |
6 |
else
|
7 |
redirect '/', :error => "Can't find that note." |
8 |
end
|
9 |
end
|
Und die entsprechende DELETE-Anforderung delete '/:id'
an:
1 |
delete '/:id' do |
2 |
n = Note.get params[:id] |
3 |
if n.destroy |
4 |
redirect '/', :notice => 'Note deleted successfully.' |
5 |
else
|
6 |
redirect '/', :error => 'Error deleting note.' |
7 |
end
|
8 |
end
|
Ändern Sie abschließend die Route get '/:id/complete'
wie folgt:
1 |
get '/:id/complete' do |
2 |
n = Note.get params[:id] |
3 |
unless n |
4 |
redirect '/', :error => "Can't find that note." |
5 |
end
|
6 |
n.complete = n.complete ? 0 : 1 # flip it |
7 |
n.updated_at = Time.now |
8 |
if n.save |
9 |
redirect '/', :notice => 'Note marked as complete.' |
10 |
else
|
11 |
redirect '/', :error => 'Error marking note as complete.' |
12 |
end
|
13 |
end
|
Und da haben Sie es!
Eine funktionierende, sichere und auf Fehler reagierende Web-App, die in überraschend wenig Code geschrieben wurde! In dieser kurzen Miniserie haben wir gelernt, wie man verschiedene HTTP-Anforderungen mit einer RESTful-Schnittstelle verarbeitet, Formularübermittlungen verarbeitet, potenziell gefährlichen Inhalten entgeht, sich mit einer Datenbank verbindet, mit Benutzersitzungen zusammenarbeitet, um Flash-Nachrichten anzuzeigen, einen dynamischen RSS-Feed zu generieren und wie man Anwendungsfehler ordnungsgemäß behandelt.
Wenn Sie die App weiterentwickeln möchten, sollten Sie sich mit der Benutzerauthentifizierung befassen, z. B. mit dem Juwel der Sinatra-Authentifizierung.
Wenn Sie die App auf einem Webserver bereitstellen möchten, können Sie Ihre Sinatra-Anwendungen, da Sinatra mit Rake erstellt wurde, ganz einfach auf Apache- und Nginx-Servern hosten, indem Sie Passenger installieren.
Alternativ können Sie sich Heroku ansehen, eine Host-Plattform mit Git-Unterstützung, mit der Sie Ihre Ruby-Web-Apps so einfach wie git push heroku
bereitstellen können (kostenlose Konten sind verfügbar!).
Wenn Sie mehr über Sinatra erfahren möchten, lesen Sie die ausführliche Readme-Datei, die Dokumentation-Seiten und das kostenlose Sinatra-Buch.
Hinweis: Die Quelldateien für jeden Teil dieser Miniserie sind zusammen mit der fertigen App auf GitHub verfügbar.