Ders 09 / 34
URI Tasarımı
Kaynak adreslerinin yol düzeni: yol ile sorgu bölümünün işbölümü, koleksiyon ve tekil kaynak adlandırması, sabit dilimle parametre diliminin çakışması, iç içe yolların üst dilimi ve kanonik adres kararı.
İçindekiler
Bir önceki ders kaynakları belirledi: kitap, üye, ödünç kaydı, şube ve tabloya karşılık
gelmeyen üye durumu. O dersin örneklerinde /kitaplar, /oduncler/1 ve
/uyeler/U-1001/durum gibi adresler sorgulanmadan kullanıldı. Oysa her biri bir karardı ve
her kararın alternatifi vardı.
Bu ders o kararları açar. Adresin hangi parçası kaynağın kimliğini taşır, hangi parçası görünümünü belirler; koleksiyon adı nasıl yazılır; bir ödünç kaydına iki farklı yoldan başvurulabildiğinde hangisi geçerli sayılır; sondaki eğik çizgi neyi değiştirir. Bunların hepsi, bir eşleyici tablosu çalıştırıldığında ölçülebilir sonuçlar üretir.
Yolun ve Sorgunun İşbölümü
İnternet Nasıl Çalışır kursunda adresin bölümleri tanıtılmıştı: şema, konak, yol, sorgu ve parça. API tasarımında bu bölümler arasında keskin bir işbölümü vardır.
Yol kaynağın kimliğini taşır. /kitaplar/978-0131103627 belirli bir kitabı gösterir;
o kitap başka bir yolda bulunmaz. Sorgu bölümü kaynağın görünümünü belirler.
/oduncler?durum=acik yeni bir kaynak değildir, ödünç koleksiyonunun süzülmüş bir
görünümüdür. Aynı ayrım sıralama, sayfalama ve alan seçimi için de geçerlidir; bunların
hepsi sonraki derslerin konusudur ve hepsi sorgu bölümünde yaşar.
Bu işbölümünün pratik sonucu şudur: yolda fiil bulunmaz. /oduncIade, /kitapAra,
/uyeSil gibi adresler kaynağı değil işlemi adlandırır. Ne yapılacağını söyleyen şey
yöntemdir; yol yalnızca neye yapılacağını söyler. Bir sonraki ders yöntemlerin bu görevi
nasıl üstlendiğini ayrıntısıyla ele alır.
Koleksiyon adları çoğul yazılır: /kitaplar, /uyeler, /oduncler. Gerekçe estetik
değildir. /kitap/978-0131103627 ile /kitaplar bir arada kullanıldığında istemci
tarafındaki her adres kurma noktası, tekil mi çoğul mu olduğunu ayrıca hatırlamak zorunda
kalır. Tek bir kural — koleksiyon çoğul, üyesi onun altında — bu hatırlama yükünü sıfırlar.
Hangi kuralın seçildiği ikincildir; kurs boyunca değişmemesi birincildir.
Sabit Dilim ile Parametre Diliminin Çakışması
Adres kalıpları bir tablodur ve tablo sırayla taranır. Sıra yanlış olduğunda hata sessizdir. Aşağıdaki eşleyici, hangi kalıbın eşleştiğini yanıtın içinde bildirir; böylece karar görünür olur.
// yol-esleyici.mjs — adres kaliplarini sirayla deneyen esleyici; eslesen kalibi bildirir import { createServer } from "node:http"; import { DatabaseSync } from "node:sqlite"; const db = new DatabaseSync("kutuphane.db"); const yanitla = (yanit, kod, nesne) => { yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" }); yanit.end(JSON.stringify(nesne)); }; // Sira onemlidir: liste yukaridan asagi taranir, ilk eslesen kazanir. const KALIPLAR = [ ["/kitaplar/{isbn}", /^\/kitaplar\/([^/]+)$/, (e) => db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(e[1]) ?? null], ["/kitaplar/ara", /^\/kitaplar\/ara$/, () => db.prepare("SELECT isbn, ad FROM kitap WHERE yazar LIKE 'K%'").all()], ["/uyeler/{kod}/oduncler/{id}", /^\/uyeler\/([^/]+)\/oduncler\/(\d+)$/, (e) => db.prepare("SELECT * FROM odunc WHERE id = ?").get(Number(e[2])) ?? null], ]; const sunucu = createServer((istek, yanit) => { const yol = new URL(istek.url, "http://127.0.0.1").pathname; for (const [ad, kalip, isleyici] of KALIPLAR) { const eslesme = kalip.exec(yol); if (eslesme) return yanitla(yanit, 200, { eslesen: ad, sonuc: isleyici(eslesme) }); } yanitla(yanit, 404, { hata: "yol_yok" }); }); sunucu.listen(8474, "127.0.0.1", () => console.log("yol esleyici 127.0.0.1:8474"));
Şema, bir önceki derste kurulan sema.sql dosyasıdır; betik veritabanını her çalıştırmada
yeniden üretir.
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql node yol-esleyici.mjs & sunucu=$! sleep 0.4 iste() { printf '%-34s %s\n' "$1" "$(curl -sS "http://127.0.0.1:8474$1")"; } iste "/kitaplar/978-0131103627" iste "/kitaplar/ara" iste "/uyeler/U-1002/oduncler/1" iste "/kitaplar/978-0131103627/" iste "/Kitaplar/978-0131103627" kill $sunucu
yol esleyici 127.0.0.1:8474
/kitaplar/978-0131103627 {"eslesen":"/kitaplar/{isbn}","sonuc":{"isbn":"978-0131103627","ad":"C Programlama Dili","yazar":"Ritchie","yil":1978,"sube":"S-01"}}
/kitaplar/ara {"eslesen":"/kitaplar/{isbn}","sonuc":null}
/uyeler/U-1002/oduncler/1 {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"id":1,"uye":"U-1001","isbn":"978-0262033848","verilis":"2026-03-01","iade":null}}
/kitaplar/978-0131103627/ {"hata":"yol_yok"}
/Kitaplar/978-0131103627 {"hata":"yol_yok"}
Beş satırda dört ayrı sorun görünür.
İkinci satır çakışmadır. /kitaplar/ara adresi arama kalıbına hiç ulaşmadı; parametre
dilimi olan onu önce yakaladı ve ara dizgisini ISBN sanıp veritabanında aradı.
Sonuç null döndü — yani istemci, arama uç noktasının bozuk olduğunu değil, aradığı
kitabın bulunmadığını düşünür. Uygulama Mimarisi kursunda tanımlanan özgüllük ölçütü
burada da geçerlidir: sabit dilimli kalıplar parametre dilimli kalıplardan önce
denenmelidir. Sıraya güvenmek istemiyorsanız ikinci yol vardır — arama ölçütünü koleksiyon
adresinin sorgu bölümüne taşımak: /kitaplar?yazar=Knuth. Bu yazımda çakışacak bir dilim
kalmaz.
Üçüncü satır daha ağırdır. /uyeler/U-1002/oduncler/1 adresine U-1001 üyesinin ödünç
kaydı döndü. Kalıp üst dilimi yakaladı ama işleyici onu hiç kullanmadı; yalnızca
ile sorgu yaptı. İç içe yol burada süsleme durumuna düşmüştür: adres bir sahiplik
iddiası taşır, sunucu o iddiayı denetlemez.
Dördüncü ve beşinci satırlar kanonik biçim sorunudur. Sondaki eğik çizgi ve büyük harf, yolun eşleşmemesine yol açtı. İkisi de “bulunamadı” olarak döndü, oysa kaynak duruyor.
Üst Dilimin Doğrulanması ve Kanonik Biçim
Aşağıdaki eşleyici üç düzeltmeyi birlikte uygular: sabit dilim öne alınır, üst dilim sorguya katılır, sondaki eğik çizgi kanonik adrese yönlendirilir.
// yol-esleyici-2.mjs — ozgullugu once sinayan, ust dilimi dogrulayan esleyici import { createServer } from "node:http"; import { DatabaseSync } from "node:sqlite"; const db = new DatabaseSync("kutuphane.db"); const yanitla = (yanit, kod, nesne) => { yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" }); yanit.end(JSON.stringify(nesne)); }; // Sabit dilimli kaliplar once; parametre dilimi en sonda kalir. const KALIPLAR = [ ["/kitaplar/ara", /^\/kitaplar\/ara$/, () => [200, db.prepare("SELECT isbn, ad FROM kitap WHERE yazar LIKE 'K%'").all()]], ["/kitaplar/{isbn}", /^\/kitaplar\/([^/]+)$/, (e) => { const k = db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(e[1]); return k ? [200, k] : [404, { hata: "kitap_yok" }]; }], ["/uyeler/{kod}/oduncler/{id}", /^\/uyeler\/([^/]+)\/oduncler\/(\d+)$/, (e) => { // Ust dilim suslemedir demek yanlistir: kayit o uyeye ait degilse yol yoktur. const o = db.prepare("SELECT * FROM odunc WHERE id = ? AND uye = ?").get(Number(e[2]), e[1]); return o ? [200, o] : [404, { hata: "odunc_yok" }]; }], ]; const sunucu = createServer((istek, yanit) => { let yol = new URL(istek.url, "http://127.0.0.1").pathname; // Kanonik bicim: sondaki egik cizgi tek adrese indirgenir. if (yol.length > 1 && yol.endsWith("/")) { yanit.writeHead(308, { location: yol.slice(0, -1) }); return yanit.end(); } for (const [ad, kalip, isleyici] of KALIPLAR) { const eslesme = kalip.exec(yol); if (eslesme) { const [kod, sonuc] = isleyici(eslesme); return yanitla(yanit, kod, { eslesen: ad, sonuc }); } } yanitla(yanit, 404, { hata: "yol_yok" }); }); sunucu.listen(8475, "127.0.0.1", () => console.log("duzeltilmis esleyici 127.0.0.1:8475"));
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql node yol-esleyici-2.mjs & sunucu=$! sleep 0.4 iste() { printf '%-30s %3s %s\n' "$1" \ "$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:8475$1")" \ "$(curl -sS "http://127.0.0.1:8475$1")"; } iste "/kitaplar/ara" iste "/uyeler/U-1002/oduncler/1" iste "/uyeler/U-1001/oduncler/1" iste "/kitaplar/978-0131103627/" # Ayni adres, yonlendirme izlenerek: printf '%-30s %3s %s\n' "/kitaplar/978-0131103627/ (-L)" \ "$(curl -sSL -o /dev/null -w '%{http_code}' http://127.0.0.1:8475/kitaplar/978-0131103627/)" \ "$(curl -sSL http://127.0.0.1:8475/kitaplar/978-0131103627/)" kill $sunucu
duzeltilmis esleyici 127.0.0.1:8475
/kitaplar/ara 200 {"eslesen":"/kitaplar/ara","sonuc":[{"isbn":"978-0201896831","ad":"Programlama Sanati"}]}
/uyeler/U-1002/oduncler/1 404 {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"hata":"odunc_yok"}}
/uyeler/U-1001/oduncler/1 200 {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"id":1,"uye":"U-1001","isbn":"978-0262033848","verilis":"2026-03-01","iade":null}}
/kitaplar/978-0131103627/ 308
/kitaplar/978-0131103627/ (-L) 200 {"eslesen":"/kitaplar/{isbn}","sonuc":{"isbn":"978-0131103627","ad":"C Programlama Dili","yazar":"Ritchie","yil":1978,"sube":"S-01"}}
İkinci ve üçüncü satır birlikte okunmalıdır: aynı ödünç kaydı, iki farklı üst dilimle iki farklı sonuç verir. Üst dilim artık iddia değil koşuldur. Bu, güvenlik değil doğruluk kararıdır; yetkilendirme ayrı bir konudur ve Kimlik Doğrulama ve Yetkilendirme kursunda ele alınır. Buradaki kazanç şudur: adres neyi söylüyorsa sunucu onu doğrular.
Dördüncü satır yönlendirmeyi gösteriyor: sondaki eğik çizgili adres 308 ile kanonik adrese çevrilir, gövde boştur. Beşinci satırda aynı istek yönlendirme izlenerek yapılır ve 200 ile kaynağa ulaşır. Yönlendirmenin bu türü yöntemi ve gövdeyi korur; bu ayrım, yazma isteklerinin yönlendirilmesi gerektiğinde belirleyici olur.
Büyük harf sorunu bilinçli olarak düzeltilmedi. Yol büyük–küçük harfe duyarlıdır ve
duyarlı kalmalıdır; /Kitaplar yolunu da kabul etmek, aynı kaynak için sonsuz sayıda
adres üretmek demektir. Doğru karar tek yazımı belgelemek ve diğerlerini reddetmektir.
Kimliğin İçindeki Ayraçlar
Yol dilimlerini ayıran karakter eğik çizgidir; kimlik değeri o karakteri içeriyorsa adres bozulur.
# Kimlikte yol ayraci gecerse ne olur? node -e ' const kod = "S-01/Merkez"; console.log("ham :", "/subeler/" + kod); console.log("kodlanmis :", "/subeler/" + encodeURIComponent(kod)); const adres = new URL("http://127.0.0.1/subeler/" + encodeURIComponent(kod)); console.log("cozulen :", decodeURIComponent(adres.pathname.split("/")[2])); '
ham : /subeler/S-01/Merkez kodlanmis : /subeler/S-01%2FMerkez cozulen : S-01/Merkez
Ham yazımda tek bir kimlik iki dilime bölünür ve eşleyici onu alt kaynak sanır. Kodlanmış yazımda dilim bütünlüğünü korur. Bu, istemcinin sorumluluğudur ve sözleşmede yazılı olmalıdır. Daha sağlam olan yol ise kimliğin biçimini baştan kısıtlamaktır: ayraç, boşluk ve büyük harf içermeyen kimlikler bu sorunu hiç doğurmaz.
Kanonik Adres Kararı
Bir kaynağa birden çok yoldan ulaşılabilir: /oduncler/1 ve /uyeler/U-1001/oduncler/1
aynı kaydı verir. İkisi de geçerli olabilir, ama biri kanonik adres olarak seçilmelidir.
Seçim ölçütü şudur: kaynağın bağımsız kimliği varsa kanonik adres kısa olandır. Ödünç
kaydının kendi kimliği vardır, dolayısıyla /oduncler/1 kanoniktir; iç içe yazım, üyenin
kayıtlarını gezerken kullanılan bir kısayoldur.
Kanonik adresin belirlenmesi üç yerde iş görür. Yanıt içinde bir kaynağa bağlantı
verilirken hangi biçimin yazılacağı bellidir. Yeni bir kayıt oluşturulduğunda Location
başlığına konacak adres bellidir. Önbellek anahtarı olarak adres kullanıldığında aynı
kaynak iki farklı anahtarla saklanmaz.
Derinlik de bu ölçütle sınırlanır. /subeler/S-01/uyeler/U-1001/oduncler/1 gibi dört
düzeyli bir yol, dört ayrı doğrulama gerektirir ve dördü de her istekte yapılmalıdır. Kural
olarak bir düzey yeterlidir: koleksiyonun sahibi olan kaynak, sonra koleksiyon, sonra üyesi.
Daha derini gerekiyorsa bu, ilişkinin sorgu ölçütü olarak yazılmasının daha uygun olduğunun
işaretidir.
Özet
- Yol kaynağın kimliğini taşır, sorgu bölümü görünümünü belirler; yolda fiil bulunmaz, ne yapılacağını yöntem söyler.
- Koleksiyonlar çoğul adlandırılır ve bu karar kurs boyunca değişmez; tutarlılık, hangi kuralın seçildiğinden daha önemlidir.
- Sabit dilimli kalıplar parametre dilimlilerden önce denenmelidir; aksi hâlde
/kitaplar/araadresi ISBN sanılır ve hata “bulunamadı” gibi görünür. - İç içe yolun üst dilimi süsleme değil koşuldur: kayıt o üyeye ait değilse yanıt 404 olmalıdır.
- Her kaynağın tek bir kanonik adresi belirlenir; sondaki eğik çizgi gibi türevler 308 ile ona yönlendirilir, büyük harf türevleri hiç kabul edilmez.
- Kimlik değerleri yol ayracı içeriyorsa adres kodlanmalıdır; daha sağlam çözüm kimlik biçimini baştan kısıtlamaktır.
Sonraki Adım
Adresler yerleştiğine göre geriye eylemin nereden geldiği sorusu kalır. Bu derste yol düzeninin fiil taşımadığı söylendi ve “ne yapılacağını yöntem söyler” denip geçildi. Ancak yöntem seçmek bir ad seçmek değildir: her yöntemin, istemcinin ve aradaki bileşenlerin güvendiği özellikleri vardır. Bir okumanın veriyi değiştirmediği, aynı isteğin iki kez gönderilmesinin tek etki ürettiği varsayımları buradan gelir. Sonraki ders bu özellikleri tanımlar ve her birini veritabanındaki satır sayısını sayarak sınar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.