Apa itu middleware Rack di Ruby? Saya tidak dapat menemukan penjelasan yang baik untuk apa yang mereka maksud dengan "middleware".
Apa itu middleware Rack di Ruby? Saya tidak dapat menemukan penjelasan yang baik untuk apa yang mereka maksud dengan "middleware".
Jawaban:
Rack middleware lebih dari "cara untuk memfilter permintaan dan respons" - ini merupakan implementasi dari pola desain pipa untuk server web menggunakan Rack .
Itu sangat bersih memisahkan berbagai tahap memproses permintaan - pemisahan kekhawatiran menjadi tujuan utama dari semua produk perangkat lunak yang dirancang dengan baik.
Sebagai contoh dengan Rack saya dapat melakukan tahapan terpisah dari pipeline melakukan:
Otentikasi : ketika permintaan datang, apakah detail login pengguna benar? Bagaimana cara memvalidasi OAuth ini, Otentikasi Dasar HTTP, nama / kata sandi?
Otorisasi : "apakah pengguna diizinkan untuk melakukan tugas khusus ini?", Yaitu keamanan berbasis peran.
Caching : saya memproses permintaan ini, dapatkah saya mengembalikan hasil yang di-cache?
Dekorasi : bagaimana saya bisa meningkatkan permintaan untuk membuat pemrosesan hilir lebih baik?
Pemantauan Kinerja & Penggunaan : statistik apa yang bisa saya dapatkan dari permintaan dan respons?
Eksekusi : benar-benar menangani permintaan dan memberikan respons.
Mampu memisahkan tahapan yang berbeda (dan secara opsional memasukkan mereka) adalah bantuan besar dalam mengembangkan aplikasi yang terstruktur dengan baik.
Ada juga sistem ramah lingkungan yang berkembang di sekitar Rack Middleware - Anda harus dapat menemukan komponen rak yang sudah dibuat sebelumnya untuk melakukan semua langkah di atas dan banyak lagi. Lihat wiki Rack GitHub untuk daftar middleware .
Middleware adalah istilah mengerikan yang merujuk pada komponen perangkat lunak / pustaka yang membantu tetapi tidak terlibat langsung dalam pelaksanaan beberapa tugas. Contoh yang sangat umum adalah pencatatan, otentikasi, dan komponen pemrosesan horizontal lainnya yang umum . Ini cenderung menjadi hal-hal yang dibutuhkan semua orang di berbagai aplikasi tetapi tidak terlalu banyak orang tertarik (atau seharusnya) membangun diri mereka sendiri.
Komentar tentang hal itu sebagai cara untuk memfilter permintaan mungkin berasal dari episode RailsCast 151: Rack Middleware layar .
Rack middleware berevolusi dari Rack dan ada intro yang bagus di Pengantar Rack middleware .
Ada pengantar middleware di Wikipedia di sini .
Pertama-tama, Rack adalah dua hal:
Rack - Antarmuka Webserver
Dasar-dasar rak adalah konvensi sederhana. Setiap server web yang sesuai dengan rak akan selalu memanggil metode panggilan pada objek yang Anda berikan kepadanya dan melayani hasil dari metode itu. Rack menentukan dengan tepat bagaimana metode panggilan ini harus terlihat, dan apa yang harus dikembalikan. Itu rak.
Mari kita coba sederhana. Saya akan menggunakan WEBrick sebagai server yang sesuai dengan rak, tetapi salah satu dari mereka akan melakukannya. Mari kita buat aplikasi web sederhana yang mengembalikan string JSON. Untuk ini kita akan membuat file bernama config.ru. Config.ru akan secara otomatis dipanggil oleh rackup's command gem rackup yang hanya akan menjalankan konten config.ru di server-compliant-rack. Jadi mari kita tambahkan yang berikut ini ke file config.ru:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
Ketika konvensi menetapkan server kami memiliki metode yang disebut panggilan yang menerima hash lingkungan dan mengembalikan array dengan bentuk [status, header, tubuh] untuk dilayani oleh server web. Mari kita coba dengan menelepon rackup. Server yang memenuhi standar rak, mungkin WEBrick atau Mongrel akan mulai dan segera menunggu permintaan untuk ditayangkan.
$ rackup
[2012-02-19 22:39:26] INFO WEBrick 1.3.1
[2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0]
[2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
Mari kita uji server JSON baru kami dengan menggulung atau mengunjungi url http://localhost:9292/hello.json
dan voila:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
Berhasil. Bagus! Itulah dasar untuk setiap kerangka kerja web, baik itu Rails atau Sinatra. Pada titik tertentu mereka menerapkan metode panggilan, bekerja melalui semua kode kerangka kerja, dan akhirnya mengembalikan respons dalam bentuk [status, header, tubuh] yang khas.
Di Ruby on Rails misalnya, permintaan rak hits ActionDispatch::Routing.Mapper
kelas yang terlihat seperti ini:
module ActionDispatch
module Routing
class Mapper
...
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
def matches?(env)
req = @request.new(env)
...
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
...
end
end
Jadi pada dasarnya cek Rails, tergantung pada hasv env jika ada rute yang cocok. Jika demikian ia meneruskan hash env ke aplikasi untuk menghitung respons, jika tidak maka akan segera merespons dengan 404. Jadi setiap server web yang sesuai dengan konvensi antarmuka rak, dapat melayani aplikasi Rails yang sepenuhnya meledak.
Middleware
Rack juga mendukung pembuatan lapisan middleware. Mereka pada dasarnya mencegat permintaan, melakukan sesuatu dengannya dan meneruskannya. Ini sangat berguna untuk tugas-tugas serbaguna.
Katakanlah kita ingin menambahkan logging ke server JSON kita yang juga mengukur berapa lama permintaan. Kami cukup membuat logger middleware yang melakukan ini:
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
Saat dibuat, ia menyimpan sendiri salinan aplikasi rak yang sebenarnya. Dalam kasus kami itu adalah contoh dari JSONServer kami. Rack secara otomatis memanggil metode panggilan pada middleware dan mengharapkan kembali [status, headers, body]
array, seperti JSONServer kami kembali.
Jadi di middleware ini, titik awal diambil, lalu panggilan aktual ke JSONServer dibuat @app.call(env)
, kemudian logger mengeluarkan entri log dan akhirnya mengembalikan respons sebagai [@status, @headers, @body]
.
Untuk membuat rackup.ru kecil kami menggunakan middleware ini, tambahkan gunakan RackLogger ke dalamnya seperti ini:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
use RackLogger
map '/hello.json' do
run JSONServer.new
end
Mulai ulang server dan voila, ini mengeluarkan log pada setiap permintaan. Rak memungkinkan Anda untuk menambahkan beberapa middlewares yang dipanggil sesuai urutan penambahannya. Ini hanya cara yang bagus untuk menambahkan fungsionalitas tanpa mengubah inti dari aplikasi rak.
Rack - The Gem
Meskipun rak - pertama-tama - adalah sebuah konvensi, rak juga merupakan permata yang menyediakan fungsionalitas luar biasa. Salah satunya kami sudah menggunakan untuk server JSON kami, perintah rackup. Tapi masih ada lagi! Rak permata menyediakan sedikit aplikasi untuk banyak kasus penggunaan, seperti melayani file statis atau bahkan seluruh direktori. Mari kita lihat bagaimana kami menyajikan file sederhana, misalnya file HTML yang sangat mendasar yang terletak di htmls / index.html:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
Kami mungkin ingin menayangkan file ini dari root situs web, jadi mari tambahkan yang berikut ini ke config.ru kami:
map '/' do
run Rack::File.new "htmls/index.html"
end
Jika kami mengunjungi, http://localhost:9292
kami melihat file html kami dirender dengan sempurna. Itu mudah, bukan?
Mari kita tambahkan seluruh direktori file javascript dengan membuat beberapa file javascript di bawah / javascripts dan menambahkan berikut ini ke config.ru:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
Mulai ulang server dan kunjungi http://localhost:9292/javascript
dan Anda akan melihat daftar semua file javascript yang dapat Anda sertakan langsung dari mana saja.
Saya mengalami masalah dalam memahami Rak untuk waktu yang cukup lama. Saya hanya sepenuhnya memahaminya setelah berupaya membuat server web Ruby mini ini sendiri. Saya telah membagikan pembelajaran saya tentang Rack (dalam bentuk cerita) di sini di blog saya: http://gauravchande.com/what-is-rack-in-ruby-rails
Umpan balik lebih dari diterima.
config.ru
contoh runnable minimal
app = Proc.new do |env|
[
200,
{
'Content-Type' => 'text/plain'
},
["main\n"]
]
end
class Middleware
def initialize(app)
@app = app
end
def call(env)
@status, @headers, @body = @app.call(env)
[@status, @headers, @body << "Middleware\n"]
end
end
use(Middleware)
run(app)
Lari rackup
dan kunjungi localhost:9292
. Outputnya adalah:
main
Middleware
Jadi jelas bahwa Middleware
membungkus dan memanggil aplikasi utama. Oleh karena itu, ia dapat memproses lebih dulu permintaan itu, dan mempostingkan tanggapannya dengan cara apa pun.
Seperti dijelaskan di: http://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack , Rails menggunakan Rack middlewares untuk banyak fungsionalitasnya, dan Anda dapat menambahkan Anda sendiri juga dengan config.middleware.use
metode keluarga.
Keuntungan dari penerapan fungsionalitas dalam middleware adalah Anda dapat menggunakannya kembali pada kerangka Rak apa pun, sehingga semua yang utama Ruby, dan bukan hanya Rails.
Rack middleware adalah cara untuk memfilter permintaan dan respons yang masuk ke aplikasi Anda. Komponen middleware terletak di antara klien dan server, memproses permintaan masuk dan tanggapan keluar, tapi itu lebih dari antarmuka yang dapat digunakan untuk berbicara dengan server web. Ini digunakan untuk mengelompokkan dan memesan modul, yang biasanya merupakan kelas Ruby, dan menentukan ketergantungan di antaranya. Modul rak middleware hanya harus: - memiliki konstruktor yang mengambil aplikasi berikutnya dalam stack sebagai parameter - merespons metode "panggilan", yang menggunakan hash lingkungan sebagai parameter. Nilai kembali dari panggilan ini adalah array dari: kode status, hash lingkungan dan badan respons.
Saya telah menggunakan Rack middleware untuk menyelesaikan beberapa masalah:
Itu memberi perbaikan yang cukup elegan dalam kedua kasus.
Rack menyediakan antarmuka minimal antara server web yang mendukung kerangka kerja Ruby dan Ruby.
Menggunakan Rack, Anda dapat menulis Aplikasi Rack.
Rack akan meneruskan hash Environment (hash, terkandung di dalam permintaan HTTP dari klien, yang terdiri dari header mirip CGI) ke Aplikasi Rack Anda yang dapat menggunakan hal-hal yang terdapat dalam hash ini untuk melakukan apa pun yang diinginkan.
Untuk menggunakan Rack, Anda harus memberikan 'aplikasi' - objek yang merespons #call
metode dengan Environment Hash sebagai parameter (biasanya didefinisikan sebagai env
). #call
harus mengembalikan Array dengan tepat tiga nilai:
each
).Anda dapat menulis Aplikasi Rak yang mengembalikan array seperti itu - ini akan dikirim kembali ke klien Anda, oleh Rack, di dalam Respons (ini sebenarnya akan menjadi instance dari Kelas Rack::Response
[klik untuk pergi ke dokumen]).
gem install rack
config.ru
file - Rack tahu untuk mencari ini.Kami akan membuat Aplikasi Rack kecil yang mengembalikan Response (contoh Rack::Response
) siapa Response Tubuh adalah array yang berisi String: "Hello, World!"
.
Kami akan menjalankan server lokal menggunakan perintah rackup
.
Saat mengunjungi port yang relevan di browser kami, kami akan melihat "Halo, Dunia!" diberikan di viewport.
#./message_app.rb
class MessageApp
def call(env)
[200, {}, ['Hello, World!']]
end
end
#./config.ru
require_relative './message_app'
run MessageApp.new
Jalankan server lokal dengan rackup
dan kunjungi localhost: 9292 dan Anda akan melihat 'Halo, Dunia!' diberikan.
Ini bukan penjelasan yang komprehensif, tetapi pada dasarnya yang terjadi di sini adalah bahwa Klien (peramban) mengirimkan Permintaan HTTP ke Rack, melalui server lokal Anda, dan Rack membuat instantiate MessageApp
dan menjalankan call
, meneruskan Environment Hash sebagai parameter ke dalam metode ( yang env
argumen).
Rack mengambil nilai pengembalian (array) dan menggunakannya untuk membuat instance Rack::Response
dan mengirimkannya kembali ke Klien. Peramban menggunakan sihir untuk mencetak 'Halo, Dunia!' ke layar.
Kebetulan, jika Anda ingin melihat seperti apa lingkungan hash, letakkan saja di puts env
bawahnya def call(env)
.
Minimal apa adanya, yang Anda tulis di sini adalah aplikasi Rack!
Di aplikasi Rak kecil kami, kami dapat berinteraksi dengan env
hash (lihat di sini untuk informasi lebih lanjut tentang hash Lingkungan).
Kami akan menerapkan kemampuan bagi pengguna untuk memasukkan string kueri mereka sendiri ke dalam URL, karenanya, string itu akan hadir dalam permintaan HTTP, yang dienkapsulasi sebagai nilai dalam salah satu pasangan kunci / nilai hash Lingkungan.
Aplikasi Rack kami akan mengakses string kueri dari hash Lingkungan dan mengirimkannya kembali ke klien (browser kami, dalam hal ini) melalui Badan dalam Respons.
Dari dokumen Rack pada Hash Lingkungan: "QUERY_STRING: Bagian dari URL permintaan yang mengikuti?, Jika ada. Mungkin kosong, tetapi selalu diperlukan!"
#./message_app.rb
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
Sekarang, rackup
dan kunjungi localhost:9292?hello
( ?hello
menjadi string kueri) dan Anda akan melihat 'halo' ditampilkan di viewport.
Kami akan:
MessageSetter
,env
,MessageSetter
akan memasukkan 'MESSAGE'
kunci ke hasv env, nilainya 'Hello, World!'
jika env['QUERY_STRING']
kosong; env['QUERY_STRING']
jika tidak,@app.call(env)
- @app
menjadi aplikasi berikutnya dalam 'Stack': MessageApp
.Pertama, versi 'tangan panjang':
#./middleware/message_setter.rb
class MessageSetter
def initialize(app)
@app = app
end
def call(env)
if env['QUERY_STRING'].empty?
env['MESSAGE'] = 'Hello, World!'
else
env['MESSAGE'] = env['QUERY_STRING']
end
@app.call(env)
end
end
#./message_app.rb (same as before)
class MessageApp
def call(env)
message = env['QUERY_STRING']
[200, {}, [message]]
end
end
#config.ru
require_relative './message_app'
require_relative './middleware/message_setter'
app = Rack::Builder.new do
use MessageSetter
run MessageApp.new
end
run app
Dari Rack :: Builder docs kita melihatnyaRack::Builder
mengimplementasikan DSL kecil untuk membuat aplikasi Rack secara iteratif. Ini pada dasarnya berarti bahwa Anda dapat membangun 'Stack' yang terdiri dari satu atau lebih Middlewares dan aplikasi 'level bawah' untuk dikirim. Semua permintaan melalui aplikasi tingkat bawah Anda akan diproses terlebih dahulu oleh Middleware Anda.
#use
menentukan middleware untuk digunakan dalam tumpukan. Dibutuhkan middleware sebagai argumen.
Rack Middleware harus:
call
metode yang menggunakan hash Environment sebagai parameter.Dalam kasus kami, 'Middleware' adalah MessageSetter
, 'konstruktor' adalah initialize
metode MessageSetter , 'aplikasi berikutnya' di stack adalah MessageApp
.
Jadi di sini, karena apa yang Rack::Builder
dilakukannya di bawah tenda, para app
argumen MessageSetter
's initialize
metode adalah MessageApp
.
(bawa kepala Anda berkeliling di atas sebelum melanjutkan)
Oleh karena itu, setiap bagian dari Middleware pada dasarnya 'menurunkan' hash Environment yang ada ke aplikasi berikutnya dalam rantai - sehingga Anda memiliki kesempatan untuk bermutasi hash lingkungan di dalam Middleware sebelum meneruskannya ke aplikasi berikutnya di stack.
#run
mengambil argumen yang merupakan objek yang merespons #call
dan mengembalikan Respons Rak (turunan dari Rack::Response
).
Menggunakan Rack::Builder
Anda dapat membangun rantai Middlewares dan permintaan apa pun ke aplikasi Anda akan diproses oleh masing-masing Middleware secara bergantian sebelum akhirnya diproses oleh bagian terakhir di stack (dalam kasus kami, MessageApp
). Ini sangat berguna karena memisahkan berbagai tahapan proses permintaan. Dalam hal 'pemisahan masalah', tidak mungkin lebih bersih!
Anda dapat membuat 'pipa permintaan' yang terdiri dari beberapa Middlewares yang menangani hal-hal seperti:
(di atas poin-poin dari jawaban lain di utas ini)
Anda akan sering melihat ini dalam aplikasi profesional Sinatra. Sinatra menggunakan Rack! Lihat di sini untuk definisi apa IS Sinatra !
Sebagai catatan akhir, kami config.ru
dapat ditulis dalam gaya tulisan tangan pendek, menghasilkan fungsionalitas yang persis sama (dan inilah yang biasanya Anda lihat):
require_relative './message_app'
require_relative './middleware/message_setter'
use MessageSetter
run MessageApp.new
Dan untuk menunjukkan secara lebih eksplisit apa MessageApp
yang dilakukan, inilah versi 'tangan panjang' yang secara eksplisit menunjukkan bahwa #call
menciptakan instance baru Rack::Response
, dengan tiga argumen yang diperlukan.
class MessageApp
def call(env)
Rack::Response.new([env['MESSAGE']], 200, {})
end
end
Rack - Antarmuka dengan Web & Aplikasi Server
Rack adalah paket Ruby yang menyediakan antarmuka untuk server web untuk berkomunikasi dengan aplikasi. Sangat mudah untuk menambahkan komponen middleware antara server web dan aplikasi untuk memodifikasi cara perilaku / respons Anda. Komponen middleware berada di antara klien dan server, memproses permintaan masuk dan tanggapan keluar.
Dengan kata-kata awam, pada dasarnya ini hanya seperangkat pedoman untuk bagaimana server dan aplikasi Rails (atau aplikasi web Ruby lainnya) harus berbicara satu sama lain .
Untuk menggunakan Rack, berikan "aplikasi": objek yang merespons metode panggilan, menggunakan hash lingkungan sebagai parameter, dan mengembalikan Array dengan tiga elemen:
Untuk penjelasan lebih lanjut, Anda dapat mengikuti tautan di bawah ini.
1. https://rack.github.io/
2. https://redpanthers.co/rack-middleware/
3. https://blog.engineyard.com/2015/understanding-rack-apps-and-middleware
4. https://guides.rubyonrails.org/rails_on_rack.html#resources
Di rails, kami memiliki config.ru sebagai file rak, Anda dapat menjalankan file rak apa pun dengan rackup
perintah. Dan port default untuk ini adalah 9292
. Untuk menguji ini, Anda cukup menjalankan rackup
di direktori rails Anda dan lihat hasilnya. Anda juga dapat menetapkan port tempat Anda ingin menjalankannya. Perintah untuk menjalankan file rak pada port tertentu adalah
rackup -p PORT_NUMBER
Rack adalah permata yang menyediakan antarmuka sederhana untuk permintaan / respons HTTP abstrak. Rak berada di antara kerangka kerja web (Rails, Sinatra dll) dan server web (unicorn, puma) sebagai adaptor. Dari gambar di atas ini membuat server unicorn sepenuhnya independen dari mengetahui tentang rails dan rails tidak tahu tentang unicorn. Ini adalah contoh yang baik dari kopling longgar , pemisahan kekhawatiran .
Gambar di atas dari ceramah konferensi rel ini di rak https://youtu.be/3PnUV9QzB0g Saya sarankan menontonnya untuk pemahaman yang lebih dalam.