Advertisement
  1. Code
  2. Ruby

Bernyanyi bersama Sinatra - Encore

Scroll to top
Read Time: 10 min
This post is part of a series called Singing with Sinatra.
Singing With Sinatra: The Recall App

() 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
		&lt;/article&gt;woops &lt;script&gt;alert(&quot;zomg haxz&quot;);&lt;/script&gt;

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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.