Temel kavramlar
Sayfalar ve layout'lar
Sayfa nedir, layout'u ve içeriği nasıl bir araya gelir, ona hangi yollar ulaşır, nasıl önbelleğe alınır, başarısız olduğunda ne gösterir ve kayıt ona ne yapar.
Bir sayfa, adı olan bir render yapılandırmasıdır. Sayfayı hangi fragment'lerin oluşturduğunu — bir layout ve içindeki içerik —, ona hangi URL'lerin ulaştığını, çıktısının nasıl önbelleğe alındığını ve render edilemediğinde onun yerine hangi sayfanın gösterileceğini söyler. Kendine ait bir şablonu ya da verisi yoktur; bunlar fragment'lerine aittir.
page := collage.NewPage("blog-post").
WithLayout(layout).
WithContent(post).
WithPath("en", "/blog/{slug}").
Incremental(10 * time.Minute).
Build()
if err := app.RegisterPage(page); err != nil {
log.Fatal(err)
}
Sayfa kurmak
collage.NewPage(name) bir builder başlatır, her WithX çağrısı tek bir şeyi
ayarlar ve Build() *collage.Page'i döndürür.
Ad, sayfanın kimliğidir. Bağlantılar ondan kurulur
({{pageURL "blog-post" "slug" .Slug}}), testler ve plugin'ler sayfaları onunla
arar ve iki sayfa aynı adı paylaşamaz. Adı sayfanın nerede durduğuna göre değil, ne
olduğuna göre seçin; böylece URL değiştiğinde de geçerli kalır.
Builder'lar bir hata döndürmek için zinciri asla kesmez. İsteneni yapamayan bir
çağrı hatayı not eder ve devam eder; BuildErr() not edilen her şeyi döndürür:
builder := collage.NewPage("blog-post").WithLayout(layout).WithPath("en", "/blog/{slug}")
page := builder.Build()
if err := builder.BuildErr(); err != nil {
return err // collage.ErrMissingContent: there is no WithContent
}
BuildErr'ü görmezden gelmek bir hatanın geçip gitmesine izin vermez. Bir builder'ın
not ettiği şey kurduğu değerin üzerinde kalır ve RegisterPage, böyle bir hata
taşıyan sayfayı — hata ister sayfanın kendisine, ister ağacındaki herhangi bir
fragment'e ait olsun, örneğin iki kez bildirilmiş bir slot — sayfanın adını veren bir
hatayla reddeder; birinin BuildErr'ü çağırıp çağırmadığından bağımsız olarak.
BuildErr'ü, kontrolünüzde olmayan bir girdiden kurulan ve hatayı nedenine daha
yakın bir yerde görmek istediğiniz sayfalarda denetleyin.
Layout ve içerik
Bir sitedeki çoğu sayfa dış kısmını — <head>, üst bilgi, alt bilgi — paylaşır ve
içeride farklılaşır. Dış kısım layout'tur: content adında bir slot'u olan bir
fragment. İç kısım ise içerik fragment'idir: sayfanın göstermek için var olduğu
fragment.
layout := collage.NewFragment("layout", "layouts/default.html").
WithSlot(collage.DefaultContentSlot, true, false). // "content": required, one fragment
Build()
<!-- templates/layouts/default.html -->
<!doctype html>
<html lang="en">
<head>{{hoist "head"}}</head>
<body>
<header>…</header>
{{slot "content"}}
<footer>…</footer>
</body>
</html>
WithLayout(layout) ve WithContent(post) bu ikisini adlandırır ve içeriği
layout'un content slot'una kayıt yerleştirir. Bağlamayı kendiniz yapmazsınız.
Layout'un content slot'unu boş bırakın: onu kayıt doldurur ve tek fragment tutan
bir slot ikincisini reddeder (ErrSlotOccupied).
Bir sayfanın içeriği olmalıdır — yoksa ErrMissingContent. Layout ise isteğe
bağlıdır: layout'u olmayan bir sayfa, içerik fragment'ini yanıtın tamamı olarak
render eder; şablonu eksiksiz bir HTML belgesi olan bir sayfanın istediği de budur.
Tek layout, çok sayfa
Layout, bir sitede en çok yeniden kullanılan şeydir; bu yüzden tek bir
*collage.Fragment değerini hata sayfaları dahil bütün sayfalar arasında paylaşmak
güvenlidir. Kayıt, içeriği bağlamadan önce her sayfaya layout'un slot tablosunun
özel bir kopyasını verir; böylece A sayfasının içeriği asla B sayfasında görünmez.
Yalnızca slot tablosu kopyalanır. Layout'un şablonu, data handler'ı, yedeği ve diğer slot'larına zaten bağlanmış fragment'ler paylaşımlı kalır — yani kayıt bu bağlamaların anlık bir görüntüsünü alır. Bir sayfa kaydedildikten sonra paylaşılan layout'a bağlanan bir fragment o sayfada görünmez. Layout'u eksiksiz kurun, sonra sayfaları onunla kaydedin.
İskelet (scaffold) layout'unu, her çağrıda yeni bir fragment döndüren
layouts.Layout() fonksiyonu olarak yazar. Bu da aynı derecede iyi çalışır; tek bir
değeri paylaşmak yalnızca izin verilen bir seçenektir.
Yollar
WithPath(locale, pattern) sayfaya tek bir locale'de ulaşan URL'yi kaydeder. Tek
dilli bir site tek bir locale kullanır; bir sayfanın her locale'de farklı bir yolu
olabilir:
collage.NewPage("about").
WithContent(about).
WithPath("en", "/about").
WithPath("tr", "/hakkinda")
Bir pattern segment'lerden oluşur:
| Segment | Eşleştiği |
|---|---|
blog |
Tam olarak bu metin |
{slug} |
Tam olarak bir segment, slug olarak yakalanır |
{rest...} |
Geriye kalan her şey, rest olarak yakalanır. Yalnızca son segment olarak |
Bir data handler yakalananı rc.Param("slug") ya da rc.PathParams["slug"] ile
okur. Değerler percent-decode edilmiş olarak, her seferinde bir segment gelir; bu
yüzden bir segmentin içindeki kodlanmış bir / yeni bir segment değil, değerin
parçasıdır.
Her düzeyde statik bir segment {param}'dan önce, {param} da {rest...}'ten önce
denenir — geri izlemeyle (backtracking); böylece ikisi de eşleşebilecek olsa bile
/blog/archive, /blog/{slug}'a üstün gelir. /blog ve /blog/ aynı route'tur.
Pattern'lerdeki hatalar, istek anında çıkan sürprizler değil, kayıt sırasındaki hatalardır:
- Bir pattern
/ile başlamalı, boş segment ve boş placeholder adı içermemeli ve catch-all'u yalnızca en sona koymalıdır —ErrInvalidPathya daErrInvalidPattern. - Placeholder bütün bir segmenttir. v0.11.0'dan itibaren bir segmentin içine
yazılmış bir placeholder, örneğin
/feeds/{category}.xmlya da/post-{id},ErrInvalidPattern'dir; bunun yerine/feeds/{category}/rss.xmlyazın. - Tek bir locale'de aynı yolda iki route —
ErrDuplicateRoute. Bir sayfa ile bir document'ın çakışması da buna dahildir, çünkü ikisi tek bir ağacı paylaşır. - Aynı konumda iki parametre adı, örneğin
/blog/{slug}ve/blog/{id}/edit—ErrAmbiguousParameterName.
Bir sayfa GET ve HEAD'e, OPTIONS'a da Allow header'ı URL'nin kabul ettiği
metotları listeleyen bir 204 ile yanıt verir. Diğer her metot, sayfanın o metot
için bir action'ı yoksa, aynı Allow header'ıyla bir
405'tir — bir form da üzerinde durduğu sayfaya böyle gönderilir.
Locale'ler, /tr/hakkinda gibi locale önekleri — ve /en/about'u /about'a
gönderen yönlendirme — ve sayfa adlarından bağlantı kurmak
Bağlantılar ve locale'ler sayfasında anlatılıyor. Bir sayfa
ayrıca WithRedirect(from, to, status) ve WithPermanentRedirect(from, to) ile eski
URL'lerden yönlendirmeler taşıyabilir.
Render stratejileri
Her sayfanın, çıktısının önbelleğe alınıp alınmayacağına karar veren üç stratejiden biri vardır:
| Metot | Strateji | Davranış |
|---|---|---|
Dynamic() |
StrategyDynamic |
Her istekte render edilir, asla önbelleğe alınmaz. Varsayılan |
Static() |
StrategyStatic |
Bir kez render edilir, etiketleri geçersiz kılınana kadar önbellekten sunulur |
Incremental(ttl) |
StrategyIncremental |
Önbellekten sunulur, ttl geçtikten sonra yeniden render edilir |
Incremental pozitif bir TTL ister (ErrMissingTTL). collage export'un dosyalara
yazabildiği sayfalar da Static ve Incremental sayfalardır; Dynamic bir sayfa,
istek başına render edilmek için var olduğundan
atlanır.
Önbelleğe alma yalnızca uygulama onu etkinleştirdiğinde gerçekleşir
(Config.Cache.Enabled, iskelette açık) ve geliştirme modunda asla gerçekleşmez;
orada önbellekteki bir sayfa az önce düzenlediğiniz şablonu gizlerdi.
İki builder metodu daha önbellekteki bir sayfayı şekillendirir.
WithDependency(tags...), data handler'ların bildirdiği bağımlılık etiketlerine
sayfanın kendi etiketlerini ekler; WithCacheParams(names...) ise hangi query
parametrelerinin önbellek anahtarına katıldığını söyler. Bütün bunların — etiketler,
geçersiz kılma, query parametreleri, sayfalar yerine verinin önbelleğe alınması —
nasıl bir araya geldiği Önbellekleme sayfasında anlatılıyor.
Bulunamadı ve hata sayfaları
Bir sayfa gösterilemediğinde onun yerine başka bir sayfa gösterilir. İki durum ve iki düzey vardır.
- Bulunamadı — 404. Hiçbir route URL ile eşleşmedi ya da
Required()bir fragment'in data handler'ıcollage.ErrNotFound'u sarmalayan bir hata döndürdü: URL'nin adlandırdığı içerik mevcut değil. - Hata — 500.
Required()bir fragment başka bir şekilde başarısız oldu ya da framework'ün kendi yolundaki bir şey başarısız oldu.
Bir sayfa kendi yerine geçecek sayfaları adlandırabilir, uygulama da site genelinde geçerli olanları:
post := collage.NewPage("blog-post").
WithLayout(layout).
WithContent(postContent).
WithPath("en", "/blog/{slug}").
WithNotFoundPage(postNotFound). // "no such post", with a search box
WithErrorPage(postError).
Build()
app.RegisterNotFoundPage(siteNotFound)
app.RegisterErrorPage(siteError)
Bir hata durumunda önce sayfanın kendi NotFoundPage'i ya da ErrorPage'i, sonra
site genelindeki sayfa, sonra da framework'ün yerleşik sayfası kullanılır. Hiçbir
route ile eşleşmeyen bir URL'nin soracağı bir sayfa yoktur; bu yüzden doğrudan site
genelindeki bulunamadı sayfasına gider.
Bir hata sayfası diğerleri gibi bir sayfadır — bir layout, bir içerik fragment'i, isterse data handler'lar — ama yolu yoktur. Yola ihtiyacı da yoktur; ona bir şeyin başarısız olmasıyla ulaşılır.
func NotFoundPage() *collage.Page {
content := collage.NewFragment("not-found-content", "pages/404.html").Build()
return collage.NewPage("not-found").
WithLayout(layouts.Layout()).
WithContent(content).
Dynamic().
Build()
}
Birkaç kural, hata sayfalarının en kötü anda başarısız olmasını önler:
WithNotFoundPageya daWithErrorPageile adlandırılan her sayfa kaydedilmiş olmalıdır:RegisterPage,RegisterNotFoundPageya daRegisterErrorPageile. Aksi hâlde uygulamaErrUnregisteredErrorPageile başlamayı reddeder — nedeni için aşağıya bakın.- Şablonları, onları adlandıran sayfa kaydedildiğinde denetlenir; böylece bir 500 sayfasındaki yazım hatası, site zaten hata verirken keşfedilen bir şey değil, bir başlangıç hatası olur.
- Bir sayfa kendi hata sayfası olamaz (
ErrSelfErrorPage). - Bir hata sayfasının render'ı asla önbelleğe alınmaz; başarısız olursa ya da hiçbir şey render etmezse onun yerine yerleşik sayfa sunulur, hata da loglanır ve plugin'lere bildirilir. Kendi içine 500 verebilen bir 500 sayfası bir kesintidir.
Yerleşik sayfa, harici dosya kullanmayan, kendi kendine yeten bir HTML'dir; bu yüzden bozulan şey asset'ler olsa bile render edilir. Geliştirme modunda hatanın başladığı fragment'i adlandırır ve hata zincirini gösterir; production'da ise tek bir genel cümle gösterir, çünkü hata metni host adlarını, dosya yollarını ve kimlik bilgilerini sızdırır. Hatalar framework'ün bildirdiği her hatayı listeler.
collage export, RegisterNotFoundPage ile kaydedilen sayfayı 404.html olarak
yazar; statik barındırma hizmetlerinin eksik bir URL için sunduğu dosya budur.
Kayıt
if err := app.RegisterPage(page); err != nil {
log.Fatal(err)
}
RegisterPage, bir sayfanın denetlendiği ve bir araya getirildiği yerdir; böylece
render sırasında başarısız olacak şey burada, başlangıçta, mesajda sayfanın adıyla
başarısız olur — sayfanın tuttuğu her fragment için, kendi URL'sinde açtığı fragment
dahil. Bir istek çalışırken bir
slot resolver'ın döndürdüğü
fragment buradan görülemez; o, ilk render edildiğinde denetlenir.
Sırasıyla şunları yapar:
- nil bir sayfayı, boş bir adı, başka bir sayfanın tuttuğu bir adı
(
ErrDuplicatePage) ve uygulama başladıktan sonra yapılan her kaydı (ErrAppStarted) reddeder; - builder'ı ya da ondan ulaşılabilen veya
WithFragmentPathile açılan herhangi bir fragment'in builder'ı bir hata not etmiş olan sayfayı — yaniBuildErr()'ün döndüreceği şeyi — reddeder; - layout'un slot tablosunu kopyalar ve içerik fragment'ini onun
contentslot'una bağlar; - sayfayı ve fragment yolları dahil bütün fragment ağacını doğrular: yollar, strateji ve TTL, yönlendirmeler, içi boş zorunlu slot'lar, kendisinden ulaşılabilen bir fragment;
- her fragment'in şablonunun yüklendiğini denetler (
ErrTemplateNotFound) — v0.11.0'dan itibaren ağacın geri kalanı gibi denetlenen,WithFragmentPathile açılmış fragment'ler ve sayfanın kendi bulunamadı ve hata sayfaları dahil; - sayfanın yollarını, yönlendirmelerini, action'larını ve fragment yollarını router'a ekler.
Her hatayı ölümcül sayın. Yarıda başarısız olan bir kayıt geri alınmaz — bir locale'de kabul edilen yol, bir sonrakinin reddedilmesi üzerine router'da kalır — çünkü başarısız bir kayıt, toparlanılacak bir durum değil, başlamaması gereken bir programdır.
Kaydedilen değer neden önemli
Kayıt, kendisine verilen sayfayı değiştirir. 3. adım page.LayoutFragment'i,
layout'un sayfaya özel, bağlanmış kopyasıyla değiştirir ve render edilen şey —
slot'unda içerikle birlikte — o kopyadır. Aynı constructor yeniden çağrılarak kurulan
bir sayfa farklı bir değerdir ve layout'unun content slot'u boştur.
Dolayısıyla RegisterPage'e verdiğiniz değer o andan itibaren sayfanın ta
kendisidir ve bir sayfaya değer üzerinden başvuran her şey o değeri kullanmalıdır:
-
Hata sayfaları.
WithNotFoundPage(p), kaydedilmiş olan*collage.Page'in ta kendisini göstermelidir. Aynı adla ayrıca kurulmuş bir sayfaErrUnregisteredErrorPageile reddedilir, çünkü layout'unu hiçbir şeyin etrafında render ederdi. -
Sayfayla yanıt veren action'lar. Bir action'daki
collage.RenderPage(p)'ye kaydedilmiş değer verilmelidir; kaydedilmemiş bir değer boş render edilmek yerineErrUnregisteredPageile reddedilir. Olağan biçim, sayfayı bir kez kurmak ve action'ın closure'ının onu yakalamasına izin vermektir:var page *collage.Page page = collage.NewPage("hello"). WithLayout(layouts.Layout()). WithContent(content). WithPath("en", "/hello"). WithAction("POST", func(_ context.Context, rc *collage.RenderContext) (*collage.ActionResult, error) { return collage.RenderPage(page), nil }). Build() return page -
rc.Pagede o değerdir; sayfayı render eden her istek ve onlara hizmet eden her goroutine tarafından paylaşılır. Onu serbestçe okuyun; asla ona yazmayın. İstekten isteğe değişen her şeyin yeri, bir handler'ın döndürdüğü veri ya da render'ın paylaşılan verisidir — bkz. Data handler'lar.
app.Page(name) ve app.Pages(), kaydedilmiş sayfaların kopyalarını onlara bakmak
için döndürür — her sayfanın yollarını listeleyen bir sitemap, bir stratejiyi
denetleyen bir test. Kopya, kaydedilmiş değer değildir; bu yüzden kaydedilmiş bir
sayfanın beklendiği yere kopya vermeyin.