() translation by (you can also view the original English article)
Selamat datang kembali di Bernyanyi bersama Sinatra! Pada bagian ketiga dan terakhir ini kita akan memperluas aplikasi "Recall" yang kita buat dalam pelajaran sebelumnya. Kita akan menambahkan feed RSS ke aplikasi dengan gem Builder yang sangat berguna, yang menjadikan pembuatan file XML di Ruby menjadi sangat mudah. Kita akan belajar betapa mudahnya Sinatra meng-escape HTML dari masukan pengguna untuk mencegah serangan XSS, dan kita akan memperbaiki beberapa kode penanganan kesalahan.
Pengguna itu Buruk, m'kay
Aturan umum saat membuat aplikasi web adalah menjadi paranoid. Paranoid bahwa setiap pengguna Anda bertujuan untuk menyerang Anda dengan menghancurkan situs Anda atau menyerang pengguna lain melaluinya. Di aplikasi Anda, coba tambahkan Note baru dengan konten berikut:
1 |
|
2 |
</article>woops <script>alert("zomg haxz");</script> |






Saat ini pengguna kita bebas memasukkan HTML apa pun yang mereka suka. Ini membuat aplikasi terbuka untuk serangan XSS di mana pengguna dapat memasukkan JavaScript berbahaya untuk menyerang atau salah mengarahkan pengguna situs lainnya. Jadi hal pertama yang perlu kita lakukan adalah meng-escape semua konten yang dikirimkan pengguna sehingga kode di atas akan dikonversi menjadi entitas HTML, seperti:
1 |
|
2 |
</article>woops <script>alert("zomg haxz");</script> |
Untuk melakukan ini, tambahkan blok kode berikut ke file recall.rb
Anda, misalnya di bawah baris DataMapper.auto_upgrade!
:
1 |
|
2 |
helpers do |
3 |
include Rack::Utils |
4 |
alias_method :h, :escape_html |
5 |
end
|
Ini termasuk seperangkat metode yang disediakan oleh Rack. Kita sekarang memiliki akses ke metode h()
untuk meng-escape HTML.
Untuk meng-escape HTML di halaman beranda, buka file view view/home.erb
, dan ubah baris <%=note.content %>
(sekitar baris 11) menjadi:
1 |
|
2 |
<%=h note.content %> |
Atau kita bisa menulis ini sebagai <%= h(note.content) %>
, tetapi gaya di atas jauh lebih umum di komunitas Ruby. Refresh halaman dan HTML yang dikirimkan sekarang harus di-escape, dan tidak dieksekusi oleh browser:



XSS pada Halaman Lainnya
Klik tautan "edit" untuk catatan dengan kode XSS, dan Anda mungkin berpikir itu aman - semuanya ada di dalam textarea, jadi jangan dieksekusi. Tetapi bagaimana jika kita menambahkan catatan baru dengan konten berikut:
1 |
|
2 |
</textarea> <script>alert("haha")</script> |
Lihatlah halaman editnya, dan Anda dapat melihat bahwa kita telah menutup textarea sehingga peringatan JavaScript dieksekusi. Jadi jelas kita perlu meng-escape konten catatan pada setiap halaman di mana ia ditampilkan.
Di dalam file view view/edit.erb
Anda, escape konten di dalam textarea
dengan menjalankannya melalui metode h
(baris 4):
1 |
|
2 |
<textarea name="content"><%=h @note.content %></textarea> |
Dan lakukan hal yang sama pada file views/delete.erb
Anda pada baris 2:
1 |
|
2 |
<p>Are you sure you want to delete the following note: <em>"<%=h @note.content %>"</em>?</p> |
Itu dia - kita sekarang aman dari XSS. Ingatlah untuk meng-escape semua data yang dikirimkan pengguna saat membuat aplikasi web lainnya di masa depan!
Anda mungkin bertanya-tanya "bagaimana dengan injeksi SQL?" Nah, DataMapper menangani itu untuk kita selama kita menggunakan metode DataMapper untuk mendapatkan data dari database (mis. tidak mengeksekusi SQL mentah).
Feed RSS Massal
Bagian penting dari situs web dinamis apa pun adalah bentuk feed RSS, dan aplikasi Recall kita tidak terkecuali! Untungnya sangat mudah untuk membuat feed berkat gem Builder. Instal dengan:
1 |
|
2 |
gem install builder |
Bergantung pada bagaimana Anda mengatur RubyGems di sistem Anda, Anda mungkin perlu awalan gem install
dengan sudo
.
Sekarang tambahkan rute baru ke file aplikasi recall.rb
Anda untuk permintaan GET ke /rss.xml
:
1 |
|
2 |
get '/rss.xml' do |
3 |
@notes = Note.all :order => :id.desc |
4 |
builder :rss |
5 |
end
|
Pastikan Anda menambahkan rute ini di suatu tempat di atas rute get '/:id'
, jika tidak, permintaan untuk rss.xml
akan keliru untuk ID posting!
Dalam rute kita hanya meminta semua catatan dari database, dan memuat file view rss.builder
. Perhatikan bagaimana sebelumnya kita menggunakan mesin ERB untuk menampilkan file .erb
, sekarang kita menggunakan Builder untuk memproses file. Sebagian besar file Builder adalah file Ruby normal dengan obyek xml
khusus untuk membuat tag XML.
Mulai file view views/rss.builder
Anda dengan berikut ini:
1 |
|
2 |
xml.instruct! :.xml, :version => "1.0" |
3 |
xml.rss :version => "2.0" do |
4 |
xml.channel do |
5 |
|
6 |
end
|
7 |
end
|
Catatan Sangat Penting: Pada detik pertama dari blok kode di atas, hapus titik (.
) pada teks :.xml
. WordPress mengganggu cuplikan kode.
Builder akan menguraikan ini menjadi:
1 |
|
2 |
<?xml version="1.0" encoding="UTF-8"?>
|
3 |
<rss version="2.0"> |
4 |
<channel>
|
5 |
|
6 |
</channel>
|
7 |
</rss>
|
Jadi kita mulai dengan membuat struktur untuk file XML yang valid. Sekarang mari kita tambahkan tag untuk judul feed, deskripsi, dan tautan kembali ke situs utama. Tambahkan yang berikut di dalam blok xml.channel do
:
1 |
|
2 |
xml.title "Recall" |
3 |
xml.description "'cause you're too busy to remember" |
4 |
xml.link request.url |
Perhatikan bagaimana kita mendapatkan URL saat ini dari obyek request
. Kita dapat mengkodekan ini secara manual, tetapi idenya adalah Anda dapat mengunggah aplikasi di mana saja tanpa harus mengubah potongan kode yang tidak jelas.
Namun ada satu masalah, tautannya sekarang diatur ke (misalnya) http://localhost:9393/rss.xml
. Idealnya kita ingin tautannya menuju ke beranda, dan tidak kembali ke feed. Obyek request
juga memiliki metode path_info
yang diatur ke string rute saat ini; jadi dalam kasus kita, /rss.xml
.
Mengetahui hal ini, kita sekarang dapat menggunakan metode chomp
Ruby untuk menghapus path dari akhir URL. Ubah baris xml.link request.url
menjadi:
1 |
|
2 |
xml.link request.url.chomp request.path_info |
Tautan dalam file XML kita sekarang disetel ke http://localhost:9393
. Kita sekarang dapat melakukan perulangan setiap catatan dan membuat item XML baru untuknya:
1 |
|
2 |
@notes.each do |note| |
3 |
xml.item do |
4 |
xml.title h note.content |
5 |
xml.link "#{request.url.chomp request.path_info}/#{note.id}" |
6 |
xml.guid "#{request.url.chomp request.path_info}/#{note.id}" |
7 |
xml.pubDate Time.parse(note.created_at.to_s).rfc822 |
8 |
xml.description h note.content |
9 |
end
|
10 |
end
|
Perhatikan bahwa pada baris 3 dan 7 kita meng-escape konten catatan menggunakan h
, seperti yang kita lakukan pada view utama. Agak aneh menampilkan konten yang sama untuk tag title
dan description
, tapi kita mengikuti jejak Twitter di sini, dan tidak ada data lain yang bisa kita taruh di sana.
Pada baris 6 kita mengonversi waktu created_at
dari catatan menjadi RFC822, format yang diperlukan untuk waktu dalam feed RSS.
Sekarang coba di browser! Buka /rss.xml
dan catatan Anda harus ditampilkan dengan benar.
DRY Don't Repeat Yourself
Ada satu masalah kecil dengan implementasi kita. Dalam tampilan RSS kita, kita memiliki judul dan deskripsi situs. Kita juga mendapatkannya di file views/layout.erb
untuk bagian utama situs. Tetapi sekarang jika kita ingin mengubah nama atau deskripsi situs, ada dua tempat berbeda yang perlu kita perbarui. Solusi yang lebih baik adalah dengan mengatur judul dan deskripsi di satu tempat, kemudian merujuk dari sana.
Di dalam file aplikasi recall.rb
, tambahkan dua baris berikut ke atas file, langsung setelah pernyataan require
, untuk mendefinisikan dua konstanta:
1 |
|
2 |
SITE_TITLE = "Recall" |
3 |
SITE_DESCRIPTION = "'cause you're too busy to remember" |
Sekarang kembali ke dalam view/rss.builder
mengubah baris 4 dan 5 menjadi:
1 |
|
2 |
xml.title SITE_TITLE |
3 |
xml.description SITE_DESCRIPTION |
Dan di dalam views/layout.erb
ubah tag <title>
pada baris 5 menjadi:
1 |
|
2 |
<title><%= "#{@title} | #{SITE_TITLE}" %></title> |
Dan ubah tag judul h1
dan h2
pada baris 12 dan 13 menjadi:
1 |
|
2 |
<h1><a href="/"><%= SITE_TITLE %></a></h1> |
3 |
<h2><%= SITE_DESCRIPTION %></h2> |
Kita juga harus menyertakan tautan ke feed RSS di bagian head
halaman sehingga browser dapat menampilkan tombol RSS di bilah alamat. Tambahkan yang berikut ini langsung sebelum tag </head>
:
1 |
|
2 |
<link href="/rss.xml" rel="alternate" type="application/rss+xml"> |
Pesan Kilat Kesalahan dan Keberhasilan
Kita membutuhkan beberapa cara untuk memberi tahu pengguna ketika ada kesalahan - atau benar, seperti pesan konfirmasi ketika catatan baru ditambahkan, catatan dihapus, dll.
Cara paling umum dan logis untuk mencapai ini adalah melalui "pesan kilat" - pesan singkat yang ditambahkan ke sesi browser pengguna, yang ditampilkan dan dihapus pada halaman berikutnya yang mereka lihat. Dan kebetulan ada beberapa RubyGems untuk membantu mencapai ini! Masukkan yang berikut ini ke Terminal untuk menginstal gem Rack Flash dan Sinatra Redirect with Flash:
1 |
|
2 |
gem install rack-flash sinatra-redirect-with-flash |
Bergantung pada bagaimana Anda mengatur RubyGems di sistem Anda, Anda mungkin perlu awalan gem install
dengan sudo
.
Require gem dan aktifkan fungsinya dengan menambahkan yang berikut di dekat bagian atas file aplikasi recall.rb
Anda:
1 |
|
2 |
require 'rack-flash' |
3 |
require 'sinatra/redirect_with_flash' |
4 |
|
5 |
enable :sessions |
6 |
use Rack::Flash, :sweep => true |
Menambahkan pesan kilat baru semudah flash[:error] = "Ada yang salah!"
. Mari kita tampilkan kesalahan di halaman beranda ketika tidak ada catatan di database.
Ubah rute get '/'
Anda menjadi:
1 |
|
2 |
get '/' do |
3 |
@notes = Note.all :order => :id.desc |
4 |
@title = 'All Notes' |
5 |
if @notes.empty? |
6 |
flash[:error] = 'No notes found. Add your first below.' |
7 |
end
|
8 |
erb :home |
9 |
end
|
Sangat sederhana. Jika variabel instance @notes
kosong, buat flash error baru. Untuk menampilkan pesan kilat ini pada halaman, tambahkan berikut ini ke file views/layout.erb
Anda, sebelum <%= yield %>
:
1 |
|
2 |
<% if flash[:notice] %> |
3 |
<p class="notice"><%= flash[:notice] %> |
4 |
<% end %>
|
5 |
|
6 |
<% if flash[:error] %>
|
7 |
<p class="error"><%= flash[:error] %> |
8 |
<% end %>
|
Dan tambahkan gaya berikut ke file public/style.css
Anda untuk menampilkan pemberitahuan berwarna hijau dan kesalahan merah:
1 |
|
2 |
.notice { color: green; } |
3 |
.error { color: red; } |
Sekarang halaman beranda Anda akan menampilkan pesan "tidak ada catatan" ketika database kosong:



Sekarang mari kita tampilkan pesan kesalahan atau sukses tergantung pada apakah catatan baru dapat ditambahkan ke database. Ubah rute post '/'
Anda ke:
1 |
|
2 |
post '/' do |
3 |
n = Note.new |
4 |
n.content = params[:content] |
5 |
n.created_at = Time.now |
6 |
n.updated_at = Time.now |
7 |
if n.save |
8 |
redirect '/', :notice => 'Note created successfully.' |
9 |
else
|
10 |
redirect '/', :error => 'Failed to save note.' |
11 |
end
|
12 |
end
|
Kode ini cukup logis. Jika catatan itu dapat disimpan, arahkan ke halaman beranda, dengan pesan kilat 'pemberitahuan', jika tidak, arahkan ke beranda dengan pesan kesalahan kilat. Di sini Anda dapat melihat sintaks alternatif untuk mengatur pesan kilat dan mengarahkan ulang halaman yang ditawarkan oleh gem Sinatra-Redirect-With-Flash.
Ini juga akan ideal untuk juga menampilkan kesalahan pada halaman 'edit catatan' jika catatan yang diminta tidak ada. Ubah rute get '/:id'
ke:
1 |
|
2 |
get '/:id' do |
3 |
@note = Note.get params[:id] |
4 |
@title = "Edit note ##{params[:id]}" |
5 |
if @note |
6 |
erb :edit |
7 |
else
|
8 |
redirect '/', :error => "Can't find that note." |
9 |
end
|
10 |
end
|
Dan juga pada halaman permintaan PUT untuk saat memperbarui catatan. Ubah put '/:id'
menjadi:
1 |
|
2 |
put '/:id' do |
3 |
n = Note.get params[:id] |
4 |
unless n |
5 |
redirect '/', :error => "Can't find that note." |
6 |
end
|
7 |
n.content = params[:content] |
8 |
n.complete = params[:complete] ? 1 : 0 |
9 |
n.updated_at = Time.now |
10 |
if n.save |
11 |
redirect '/', :notice => 'Note updated successfully.' |
12 |
else
|
13 |
redirect '/', :error => 'Error updating note.' |
14 |
end
|
15 |
end
|
Ubah rute get '/:id/delete'
menjadi:
1 |
|
2 |
get '/:id/delete' do |
3 |
@note = Note.get params[:id] |
4 |
@title = "Confirm deletion of note ##{params[:id]}" |
5 |
if @note |
6 |
erb :edit |
7 |
else
|
8 |
redirect '/', :error => "Can't find that note." |
9 |
end
|
10 |
end
|
Dan permintaan DELETE terkait, delete '/:id'
menjadi:
1 |
|
2 |
delete '/:id' do |
3 |
n = Note.get params[:id] |
4 |
if n.destroy |
5 |
redirect '/', :notice => 'Note deleted successfully.' |
6 |
else
|
7 |
redirect '/', :error => 'Error deleting note.' |
8 |
end
|
9 |
end
|
Terakhir, ubah rute get '/:id/complete'
menjadi yang berikut:
1 |
|
2 |
get '/:id/complete' do |
3 |
n = Note.get params[:id] |
4 |
unless n |
5 |
redirect '/', :error => "Can't find that note." |
6 |
end
|
7 |
n.complete = n.complete ? 0 : 1 # flip it |
8 |
n.updated_at = Time.now |
9 |
if n.save |
10 |
redirect '/', :notice => 'Note marked as complete.' |
11 |
else
|
12 |
redirect '/', :error => 'Error marking note as complete.' |
13 |
end
|
14 |
end
|
Dan Anda sudah selesai!
Aplikasi web yang berfungsi, aman, dan responsif terhadap kesalahan yang ditulis dalam jumlah kode yang sangat sedikit! Melalui mini-seri pendek ini, kita telah mempelajari cara memproses berbagai permintaan HTTP dengan antarmuka RESTful, menangani pengiriman formulir, meng-escape dari konten yang berpotensi berbahaya, terhubung dengan database, bekerja dengan Sesi pengguna untuk menampilkan pesan kilat, menghasilkan feed RSS dinamis dan bagaimana menangani kesalahan aplikasi dengan anggun.
Jika Anda ingin mengambil aplikasinya lebih lanjut, Anda mungkin ingin mengetahui berurusan dengan otentikasi pengguna, seperti dengan gem Sinatra Authentication.
Jika Anda ingin menggunakan aplikasi di server web, karena Sinatra dibangun dengan Rake, Anda dapat dengan mudah meng-host aplikasi Sinatra di server Apache dan Nginx dengan menginstal Passenger.
Atau, periksa Heroku, platform hosting bertenaga Git yang membuat penerapan aplikasi web Ruby Anda semudah git push heroku
(tersedia akun gratis!)
Jika Anda ingin mempelajari lebih lanjut tentang Sinatra, lihat Readme yang sangat mendalam, halaman Dokumentasi, dan Buku Sinatra gratis.
Catatan: file sumber untuk setiap bagian dari seri mini ini tersedia di GitHub, bersama dengan aplikasi yang sudah selesai.