Ders 02 / 13
Yönlendirme
Yönlendirme bir yolu denetleyici, eylem ve kimliğe çevirir. Ders yol parametresinin bir tam sayıya dönüştüğünü, kalıba uymayan yolun reddedildiğini, kalıp sırasının sonucu değiştirdiğini ve değerin yalnız bu istekte kaldığını ölçer.
Yayın Güncelleme
Bu derste
- Kaynak temelli bir yol kalıbının denetleyici, eylem ve kimlik verdiğini ölçer
- Kalıba uymayan yolların reddedildiğini ve kimliğin tam sayıya çevrildiğini gösterir
- Kalıp sırasının hangi kalıbın kazanacağını belirlediğini ölçer
- Yol parametresinin kalışının bu istek olduğunu ve değerin taşınmasının istemciye düştüğünü gösterir
İçindekiler
Önceki ders, şablon adının denetleyici ve eylem adından türediğini gösterdi, ama o adların kendisini bir yönlendirme vermişti. Yönlendirme (routing), gelen yolu hangi denetleyicinin hangi eylemine gideceğine çevirir. Bu dersin sorusu daha dar: yol /urunler/7 olduğunda 7 sayısı nereye gider ve o istek bittikten sonra nerede durur?
Kalış ekseni önceki dersten sürer: bu değer hangi isteğe kadar kalıyor? Bu dersin gözlemi yol parametresi satırıdır. Çekirdeğin kalis.rb dosyası aşağıda yeniden verilir; blok kaynaktır, çıktısı yoktur ve require_relative ile çekilir.
# kalis.rb — kucuk bir istek dongusu ve bir degerin hangi istege kadar kaldigini siniflandiran yardimci
# Flash: bu istekte yazilan deger yalniz bir sonraki istege tasinir.
class Flash
def initialize(gelen)
@gelen = gelen
@yeni = {}
end
def [](anahtar) = @yeni.fetch(anahtar) { @gelen[anahtar] }
def []=(anahtar, deger)
@yeni[anahtar] = deger
end
def birakilan = @yeni
end
# Her istek yeni bir baglam alir: params, flash ve istek deposu her istekte bostur.
class Baglam
attr_reader :uygulama, :params, :oturum, :flash, :istek_deposu
def initialize(uygulama, oturum)
@uygulama = uygulama
@params = {}
@oturum = oturum
@flash = Flash.new(oturum.delete(:flash) || {})
@istek_deposu = {}
end
end
# Uygulamanin deposu butun isteklerde ve butun istemcilerde aynidir;
# oturum istemciye gore secilir.
class Uygulama
attr_reader :depo
def initialize
@oturumlar = Hash.new { |h, istemci| h[istemci] = {} }
@depo = { urunler: {}, sema: { urunler: %i[ad fiyat] }, sablonlar: {},
kuyruk: [], onbellek: {}, sayac: 0 }
end
def istek(istemci)
baglam = Baglam.new(self, @oturumlar[istemci])
sonuc = yield baglam
baglam.oturum[:flash] = baglam.flash.birakilan unless baglam.flash.birakilan.empty?
sonuc
end
end
# Kural: istek 1'de (istemci A) deger yazilir; istek 2 ve 3 (A) ile istek 4 (B) okur.
# B goruyorsa kalici; A'nin ucuncu istegi goruyorsa oturum; yalniz ikinci istek
# goruyorsa sonraki; hicbiri gormuyorsa bu_istek.
def kalis(yaz, oku)
uygulama = Uygulama.new
ilk = uygulama.istek("A") { |b| yaz.call(b); oku.call(b) }
raise ArgumentError, "deger yazildigi istekte okunamadi" if ilk.nil?
gorulen = [["A", 2], ["A", 3], ["B", 4]].map do |istemci, sira|
uygulama.istek(istemci) { |b| oku.call(b) }.nil? ? "--" : "#{istemci}#{sira}"
end
tur = if gorulen[2] != "--" then "kalici"
elsif gorulen[1] != "--" then "oturum"
elsif gorulen[0] != "--" then "sonraki"
else "bu_istek"
end
[tur, ["A1", *gorulen].join(" "), ilk]
end
Kalıp, Eylem ve Kimlik
Çekirdeğin yönlendirmesi tek bir kalıp taşır. Kalıp bir kaynak temelli rotadır: /urunler/ ile başlar, ardından bir kimlik gelir ve hedef urunler#goster olarak yazılmıştır. Hedef iki parçaya ayrılır: # öncesi denetleyici, sonrası eylem. Kimlik, kalıpta adlandırılmış bir parçadır (id) ve eşleşmeden okunur.
Aşağıdaki dosya degerler.rb’den yalnız yönlendirme tanımlarını birebir alır ve kalis‘i kendi iki lambda’sıyla çağırır.
# yonlendirme.rb — yol parametresinin kalisi
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
yaz = ->(b) { b.params.merge!(yonlendir("/urunler/7")) }
oku = ->(b) { b.params[:id] }
p kalis(yaz, oku)
p yonlendir("/urunler/7")
p yonlendir("/urunler/7")[:id].class
["bu_istek", "A1 -- -- --", 7]
{denetleyici: "urunler", eylem: "goster", id: 7}
Integer
İlk satır tablodaki sonucun aynısıdır: yol parametresi yalnız A1’de okundu, A2’de okunmadı. İkinci satır yönlendirmenin ürettiği karma yapıyı gösterir: denetleyici, eylem ve kimlik. Üçüncü satır kimliğin Integer olduğunu söyler. Yoldaki 7 bir dizgidir; yonlendir onu to_i ile çevirmiştir. Böylece denetleyici kimliği sayı olarak alır ve karşılaştırmayı dizgi üzerinden yapmaz. ÇY5. Çevirme \d+ kalıbının eşleştirdiği rakamlarla sınırlıdır; sayı biçiminin başka yazılışları bu ölçüme girmedi.
Uymayan Yol
Yönlendirmenin ikinci işi yolu reddetmektir. Çekirdekteki kalıp \d+ ister ve \z ile biter. İki yol bu iki kuralı sınar.
# reddet.rb — kaliba uymayan yollar
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
p yonlendir("/urunler/abc")
p yonlendir("/urunler/7/")
p yonlendir("/urunler/7?sira=2")
p yonlendir("/urunler/7")
nil
nil
nil
{denetleyici: "urunler", eylem: "goster", id: 7}
/urunler/abc rakam olmadığı için, /urunler/7/ sonunda fazladan bir eğik çizgi olduğu için, /urunler/7?sira=2 ise sonunda bir sorgu dizgisi (query string) taşıdığı için eşleşmedi. Üçüncüsü çekirdeğin bir sınırıdır: yol ile sorgu dizgisinin ayrılması yönlendirmeden önce yapılmalıdır, çünkü kalıp yolun tamamını ister. Üçünde de yonlendir nil verdi, yani çekirdek reddi bir istisna olarak değil, bir yanıt yokluğu olarak bildirir. \z çapası kalıbı yolun sonuna bağlar; o olmasaydı 7/ de eşleşirdi. Kimin nil’i nasıl yorumlayacağı yönlendirmenin değil, onu çağıranın işidir. ÇY6. Reddin biçimi bu çekirdeğe özgüdür; başka bir yönlendirme aynı durumda farklı bir yanıt verebilir ve bu ders onu ölçmedi.
Kalıp Sırası
Tek kalıp varken sıra sorusu çıkmaz. Tablo büyüdüğünde ilk eşleşen kalıp kazanır; çekirdeğin yonlendir’i de tabloyu baştan sona gezer ve ilk eşleşmede döner. Bu davranışı iki kalıpla ölçmek için kendi küçük tablomuzu kurarız. Burada ilk_eslesen adlı yeni bir yardımcı vardır; çekirdekte karşılığı yoktur.
# sira.rb — iki kalipta sira
def ilk_eslesen(tablo, yol)
tablo.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
return [hedef, eslesme[:id]]
end
nil
end
genis = { %r{\A/urunler/(?<id>[^/]+)\z} => "urunler#goster_ad",
%r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
dar = genis.to_a.reverse.to_h
p ilk_eslesen(genis, "/urunler/7")
p ilk_eslesen(dar, "/urunler/7")
p ilk_eslesen(dar, "/urunler/abc")
["urunler#goster_ad", "7"] ["urunler#goster", "7"] ["urunler#goster_ad", "abc"]
İki tablo aynı iki kalıbı taşır, yalnız sırası farklıdır. Geniş kalıp önde olduğunda /urunler/7 sayı yoluna değil, ad yoluna gitti. Dar kalıp önde olduğunda aynı yol sayı yoluna gitti, ve sayı olmayan /urunler/abc geniş kalıba düştü. ÇY7. Bu ölçüm yalnız iki kalıp ve üç yol içindir; daha çok kalıpta çakışma düzeni ayrıca sınanmalıdır. Kalıp tablosunda sıra bir yapılandırmadır, ama sözleşme gibi sessiz değildir: yer değişince sonuç değişir.
İç İçe Yol
Bir kaynağın altında başka bir kaynak olabilir: bir ürünün yorumları gibi. Yol iki kimlik taşır ve kalıpta iki adlandırılmış parça gerekir. Aşağıdaki kalıp bu dersin kendi tanımıdır ve çekirdek tablosuna eklenmez; çekirdeğin yonlendir’i bu yolu tanır mı, önce o sorulur.
# ic_ice.rb — iki kimlikli yol
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
IC_ICE = %r{\A/urunler/(?<id>\d+)/yorumlar/(?<yorum_id>\d+)\z}
yol = "/urunler/7/yorumlar/3"
p yonlendir(yol)
eslesme = IC_ICE.match(yol)
p eslesme.names
p eslesme.named_captures.transform_values(&:to_i)
nil
["id", "yorum_id"]
{"id" => 7, "yorum_id" => 3}
Çekirdeğin tek kalıbı yolu reddetti, çünkü \z kimlikten sonra bitişi ister. İç içe kalıp iki parçayı adıyla verdi. Adlandırılmış parçalar sırayla değil adla okunur; bu yüzden yorum_id’nin id’den sonra ya da önce gelmesi sonucu değiştirmez. Yoldaki iki sayı da dizgidir ve to_i ile tam sayıya çevrildi. ÇY8. Bu kalıp yalnız bu dersin dosyasında durur; çekirdeğin YOLLAR tablosuna eklenseydi sıra konusu yeniden doğardı.
Yolu Geri Üretmek
Yönlendirme yoldan hedefe gider. Uygulamanın başka bir yeri hedeften yola gitmek ister: bir bağlantı yazarken /urunler/7’yi elle yazmak yerine hedefi ve kimliği vermek. Bu yön de yönlendirmenin tanımına bağlıdır. İki yön ayrı yazılırsa ayrışabilir.
# geri.rb — hedeften yol uretmek
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
def yol_uret(hedef, id) = "/#{hedef.split('#').first}/#{id}"
yol = yol_uret("urunler#goster", 7)
p yol
p yonlendir(yol)
p yonlendir(yol_uret("urunler#goster", "abc"))
"/urunler/7"
{denetleyici: "urunler", eylem: "goster", id: 7}
nil
Üretilen yol çekirdeğin kalıbıyla geri çözüldü ve aynı kimliği verdi: gidiş dönüş tutarlı. Ama yol_uret kimliğin ne olduğuna bakmadı. "abc" verilince kalıbın reddedeceği bir yol üretti ve yönlendirme onu nil yaptı. Üretim kalıbı bilmiyor, kalıp üretimi bilmiyor; ikisini aynı tanıma bağlamak yönlendirme katmanının işidir. ÇY9. Bu ölçüm tek bir kalıp ve tek bir hedef içindir; çok kalıplı bir tabloda geri üretimin hangi kalıbı seçeceği ayrıca tanımlanmalıdır.
Kimliğin Sınırları
Kalıp yalnız rakam ister, ama kaç rakam ve hangi değer olduğu konusunda bir şey söylemez. Uç değerleri yoldan geçirip kimliğin ne olduğuna bakmak, Integer çevirmesinin neyi garanti ettiğini gösterir.
# kimlik.rb — kimlik olarak gelen uc degerler
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
["/urunler/007", "/urunler/0", "/urunler/99999999999999999999"].each do |yol|
sonuc = yonlendir(yol)
p [yol, sonuc && sonuc[:id], sonuc && sonuc[:id].class]
end
["/urunler/007", 7, Integer] ["/urunler/0", 0, Integer] ["/urunler/99999999999999999999", 99999999999999999999, Integer]
Baştaki sıfırlar atıldı: 007 yolu 7 kimliğini verdi, yani iki farklı yol aynı kaynağa gider. 0 geçerli bir kimlik sayıldı; kalıp sıfırı reddetmez, yalnız bir kaydın o kimlikle var olup olmadığı sonradan anlaşılır. Çok uzun rakam dizisi de reddedilmedi ve yine bir Integer oldu, çünkü Ruby’de tam sayının sabit bir üst sınırı yoktur. ÇY10. Yönlendirme kimliğin biçimini doğrular, varlığını değil; aynı kaynağa giden birden çok yol adresi, önbellekleme ve bağlantı karşılaştırması açısından ayrıca düşünülmesi gereken bir sonuçtur. Bu yüzden bir bağlantının kimliğini karşılaştıran kod, yolu değil yönlendirmenin verdiği tam sayıyı karşılaştırmalıdır; iki yol metni farklı olsa da çözülen kimlik aynıdır.
Kaynaktan Rota Üretmek
Kaynak temelli rotanın adı, bir kaynak adından bir rota kümesinin türemesinden gelir. Çekirdek yalnız goster kalıbını taşır, ama aynı fikir bir adı alıp birkaç kalıp üretebilir. Aşağıdaki iki yardımcı bu dersin kendi saf Ruby kodudur ve çekirdekte karşılığı yoktur. Kalıp, HTTP yöntemi (GET, POST, DELETE) ile birlikte aranır; aynı yol farklı yöntemle farklı eyleme gidebilir. Yöntem burada Ruby yöntemi değil, isteğin türüdür; adı istek_yontemi konur.
# kaynak.rb — bir kaynak adindan rota uretmek
def kaynak_rotalari(ad)
{
["GET", %r{\A/#{ad}\z}] => "#{ad}#liste",
["GET", %r{\A/#{ad}/(?<id>\d+)\z}] => "#{ad}#goster",
["POST", %r{\A/#{ad}\z}] => "#{ad}#olustur",
["DELETE", %r{\A/#{ad}/(?<id>\d+)\z}] => "#{ad}#sil",
}
end
def rota_bul(tablo, istek_yontemi, yol)
tablo.each do |(fiil, kalip), hedef|
next unless fiil == istek_yontemi
eslesme = kalip.match(yol) or next
return [hedef, eslesme.names.empty? ? nil : eslesme[:id].to_i]
end
nil
end
tablo = kaynak_rotalari("urunler").merge(kaynak_rotalari("siparisler"))
p tablo.size
p rota_bul(tablo, "GET", "/urunler")
p rota_bul(tablo, "GET", "/siparisler/3")
p rota_bul(tablo, "POST", "/urunler")
p rota_bul(tablo, "DELETE", "/urunler/7")
p rota_bul(tablo, "POST", "/urunler/7")
8 ["urunler#liste", nil] ["siparisler#goster", 3] ["urunler#olustur", nil] ["urunler#sil", 7] nil
İki kaynak adı sekiz rota verdi: her ad dört kalıp üretti. /urunler GET ile listeye, POST ile olustura gitti; aynı yol, farklı eylem. /siparisler/3 ikinci kaynağın gosterine gitti ve kimlik 3 oldu. Son satırda /urunler/7 yolu var, ama POST ile tanımlı bir kalıp yok; sonuç nil. Yol tanınır, yöntem tanınmazsa da reddedilir. Bu kümeyi elle yazmak yerine adından üretmek yönlendirmedeki sözleşmedir: kaynak adı verilir, kalıplar türer. ÇY11. Bu dört kalıp bir tercihtir; bir kaynağın kaç eylemi olacağı bu ölçümden çıkmaz.
Sınırlayıcı Ölçüm
Yol parametresi URL’de taşınır, ama değeri sonraki isteğe geçmez. Çekirdeğin sonucu zaten bunu söyledi: A2 kanıt sütununda --. Buradaki ölçüm aynı değerin iki ayrı isteğe nasıl ulaştığını ayırır.
# sinir.rb — parametre URL'den gelir, istekten istege gecmez
require_relative "kalis"
# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
def yonlendir(yol)
YOLLAR.each do |kalip, hedef|
eslesme = kalip.match(yol) or next
denetleyici, eylem = hedef.split("#")
return { denetleyici:, eylem:, id: eslesme[:id].to_i }
end
nil
end
uygulama = Uygulama.new
p uygulama.istek("A") { |b| b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }
p uygulama.istek("A") { |b| b.params[:id] }
p uygulama.istek("A") { |b| b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }
ayni_yol = ->(b) { b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }
p kalis(ayni_yol, ayni_yol)
7 nil 7 ["kalici", "A1 A2 A3 B4", 7]
İlk istek 7 okudu. İkinci istek yolu yeniden istemedi ve params[:id] boş geldi: nil. Üçüncü istek yolu yeniden verdi ve 7‘yi yeniden aldı. Son satır bunun bir tuzağını gösterir. oku lambda’sı yönlendirmeyi kendisi yeniden koşturunca, her istek ve her istemci değeri gördü; kural bunu kalici sayar. Değer saklanmadı, her seferinde yoldan yeniden türedi. Kural davranışı okur, bu yüzden aynı düzenek kullanılışa göre başka bir kalışa düşer. Değeri bir sonraki isteğe taşımak yönlendirmenin değil, istemcinin işidir: bir bağlantı kimliği yeniden yola yazar; bir form ise onu sorgu dizgisiyle ya da istek gövdesiyle taşır. ÇY12. Bu ölçüm bellekteki çekirdekle yapıldı; gerçek bir istemcinin kimliği başka yoldan (başlık, çerez) taşıyıp taşımadığı sınanmadı.
Özet
- Yönlendirme bir yolu denetleyici, eylem ve kimliğe çevirir; kalıpta adlandırılmış parça kimliği verir ve kimlik bir
Integerolur. - Kalıba uymayan yol reddedilir:
/urunler/abcve sonunda eğik çizgi taşıyan/urunler/7/çekirdektenilverir. - Kalıp tablosunda ilk eşleşen kazanır; iki kalıbın sırası değişince aynı yol başka eyleme gider.
- Bu dersin kalışı bu istektir: yol parametresi yalnız yolu taşıyan istekte okunur.
- Değeri sonraki isteğe taşımak istemcinin işidir; yolu her seferinde yeniden vermek değeri kalıcı gösterebilir ama saklamaz.
Sonraki Adım
Yönlendirme eylemin adını verdi, ama eylemi çalıştıran sınıf henüz sahnede değil. Bir sonraki ders denetleyiciyi ve eylemden önce koşan filtreyi ölçer, ve kaydedip başka sayfaya yönlendirdikten sonra bildirim mesajının neden bir kez göründüğüne bakar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.