add client cache, add page struct
parent
f3a14b50b2
commit
975e7e8a4c
|
@ -38,25 +38,35 @@ func MainPageHandler(app *models.App) http.HandlerFunc {
|
|||
}
|
||||
|
||||
// Страничка рендерится только если ее нет в кэше
|
||||
pageData, ok := app.PagesCache.Get(models_pages.MainPageTmplName)
|
||||
page, ok := app.PagesCache.Get(models_pages.MainPageTmplName)
|
||||
if !ok {
|
||||
pageData, err = models_pages.RenderMainPage(app.Templates, app.Version)
|
||||
pageData, err := models_pages.RenderMainPage(app.Templates, app.Version)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
app.PagesCache.Set(models_pages.MainPageTmplName, pageData)
|
||||
ETag := models.GenerateETag(pageData)
|
||||
page = &models.Page{Data: pageData, ETag: ETag}
|
||||
app.PagesCache.Set(models_pages.MainPageTmplName, page)
|
||||
}
|
||||
|
||||
sendMainPage(w, pageData)
|
||||
sendMainPage(r, w, page)
|
||||
})
|
||||
}
|
||||
|
||||
// Отправляет страницу
|
||||
func sendMainPage(w http.ResponseWriter, data []byte) {
|
||||
func sendMainPage(r *http.Request, w http.ResponseWriter, page *models.Page) {
|
||||
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
w.Header().Set("ETag", page.ETag)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(data)
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match == page.ETag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(page.Data)
|
||||
}
|
||||
|
||||
// Ответ на метод COUNT
|
||||
|
|
|
@ -10,8 +10,6 @@ import (
|
|||
// Обработчик главной страницы
|
||||
func PostPageHandler(app *models.App) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
postLink := r.URL.Path
|
||||
|
||||
// Ссылки на посты имеют вид postLink = /link/, а если прилетело что то типо /link/123123,
|
||||
|
@ -22,26 +20,33 @@ func PostPageHandler(app *models.App) http.HandlerFunc {
|
|||
}
|
||||
|
||||
// Страничка рендерится только если ее нет в кэше
|
||||
pageData, ok := app.PagesCache.Get(postLink)
|
||||
page, ok := app.PagesCache.Get(postLink)
|
||||
if !ok {
|
||||
|
||||
post := app.Posts[models_pages.PostLink(postLink)]
|
||||
|
||||
pageData, err = models_pages.RenderPostPage(app.Templates, app.Version, post.Data)
|
||||
pageData, err := models_pages.RenderPostPage(app.Templates, app.Version, post.Data)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
app.PagesCache.Set(postLink, pageData)
|
||||
ETag := models.GenerateETag(pageData)
|
||||
page = &models.Page{Data: pageData, ETag: ETag}
|
||||
app.PagesCache.Set(postLink, page)
|
||||
}
|
||||
|
||||
sendPostPage(w, pageData)
|
||||
sendPostPage(r, w, page)
|
||||
})
|
||||
}
|
||||
|
||||
// Отправляет страницу
|
||||
func sendPostPage(w http.ResponseWriter, data []byte) {
|
||||
func sendPostPage(r *http.Request, w http.ResponseWriter, page *models.Page) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
w.Header().Set("ETag", page.ETag)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(data)
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match == page.ETag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(page.Data)
|
||||
}
|
||||
|
|
|
@ -9,26 +9,33 @@ import (
|
|||
// Обработчик главной страницы
|
||||
func PostsPageHandler(app *models.App) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
// Страничка рендерится только если ее нет в кэше
|
||||
pageData, ok := app.PagesCache.Get(models_pages.PostsPageTmplName)
|
||||
page, ok := app.PagesCache.Get(models_pages.PostsPageTmplName)
|
||||
if !ok {
|
||||
pageData, err = app.Posts.RenderPostsPage(app.Templates, app.Version)
|
||||
pageData, err := app.Posts.RenderPostsPage(app.Templates, app.Version)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
app.PagesCache.Set(models_pages.PostsPageTmplName, pageData)
|
||||
ETag := models.GenerateETag(pageData)
|
||||
page = &models.Page{Data: pageData, ETag: ETag}
|
||||
app.PagesCache.Set(models_pages.PostsPageTmplName, page)
|
||||
}
|
||||
|
||||
sendPostsPage(w, pageData)
|
||||
sendPostsPage(r, w, page)
|
||||
})
|
||||
}
|
||||
|
||||
// Отправляет страницу
|
||||
func sendPostsPage(w http.ResponseWriter, data []byte) {
|
||||
func sendPostsPage(r *http.Request, w http.ResponseWriter, page *models.Page) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000")
|
||||
w.Header().Set("ETag", page.ETag)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(data)
|
||||
|
||||
if match := r.Header.Get("If-None-Match"); match == page.ETag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(page.Data)
|
||||
}
|
||||
|
|
|
@ -1,25 +1,40 @@
|
|||
package models
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
Data []byte
|
||||
ETag string
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
Data map[string][]byte
|
||||
Data map[string]*Page
|
||||
Mu sync.RWMutex
|
||||
}
|
||||
|
||||
func initCache() *Cache {
|
||||
return &Cache{Data: make(map[string][]byte)}
|
||||
return &Cache{Data: make(map[string]*Page)}
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string) ([]byte, bool) {
|
||||
func (c *Cache) Get(key string) (*Page, bool) {
|
||||
c.Mu.RLock()
|
||||
pageData, ok := c.Data[key]
|
||||
page, ok := c.Data[key]
|
||||
c.Mu.RUnlock()
|
||||
return pageData, ok
|
||||
return page, ok
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, data []byte) {
|
||||
func (c *Cache) Set(key string, page *Page) {
|
||||
c.Mu.Lock()
|
||||
c.Data[key] = data
|
||||
c.Data[key] = page
|
||||
c.Mu.Unlock()
|
||||
}
|
||||
|
||||
func GenerateETag(data []byte) string {
|
||||
hash := md5.Sum(data)
|
||||
return fmt.Sprintf(`"%s"`, hex.EncodeToString(hash[:]))
|
||||
}
|
||||
|
|
|
@ -16,14 +16,8 @@
|
|||
<li>unix timestamp of page rendering: <code>{{ .renderingTimestamp }}</code></li>
|
||||
<li><code>curl -X LIMINAL https://hikan.ru</code> - what do you know about liminal spaces?</li>
|
||||
<li>this site code repository - <a href="https://git.hikan.ru/serr/hikan.ru" target="_blank">git.hikan.ru/serr/hikan.ru</a></li>
|
||||
<li>24-hour server request count (<code>curl -X COUNT https://hikan.ru</code>):</li>
|
||||
<li><code>curl -X COUNT https://hikan.ru</code> - 24-hour server request count</li>
|
||||
</ul>
|
||||
<p class="count">
|
||||
<img src="/assets/pic/0.gif?v={{ .version }}">
|
||||
<img src="/assets/pic/0.gif?v={{ .version }}">
|
||||
<img src="/assets/pic/0.gif?v={{ .version }}">
|
||||
<img src="/assets/pic/0.gif?v={{ .version }}">
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
|
@ -34,16 +28,4 @@
|
|||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
fetch('/', {method: 'COUNT'})
|
||||
.then(r => r.text())
|
||||
.then(num => num.trim())
|
||||
.then(number => {
|
||||
if (number.length > 0) {
|
||||
document.querySelector('p.count').innerHTML =
|
||||
[...number].map(d => `<img src="/assets/pic/${d}.gif?v={{ .version }}">`).join('');
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
</script>
|
||||
{{ end }}
|
||||
|
|
Loading…
Reference in New Issue