hikan.ru/main.go

183 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package main
import (
"bytes"
"log"
"main/cache"
"main/config"
"main/tools"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
/*
sudo systemctl stop server.service
go build main.go
sudo systemctl start server.service
*/
// App представляет основное состояние приложения
type app struct {
templates *template.Template // Шаблоны страниц
config *config.Config // Конфиг
startTime int64 // Время запуска
cache *cache.Cache // Кэш (отрендеренные странички)
}
func main() {
var app *app
var err error
// Инициализация приложения
if app, err = appInit("config.json"); err != nil {
log.Fatal(err)
}
// Настройка маршрутов и запуск
if app.setupRouterAndRun() != nil {
log.Fatal(err)
}
}
// Запускает сервер на указанном IP и порту
func runServer(ip, port string, router http.Handler) {
addr := ip + port
log.Println("Run on", addr)
log.Fatal(http.ListenAndServe(addr, router))
}
// Инициализирует приложение
func appInit(configPath string) (*app, error) {
a := &app{
startTime: time.Now().Unix(),
config: config.Init(),
cache: cache.Init(),
}
// Загрузка конфига
if err := a.config.Load(configPath); err != nil {
return nil, err
}
// Загрузка шаблонов
if err := a.loadTemplates(a.config.TemplatesPath, a.config.TemplatesExt); err != nil {
log.Fatal(err)
}
return a, nil
}
func (a *app) setupRouterAndRun() error {
// Настройка маршрутов
router := a.setupRouter()
// Запуск сервера
if ok, err := tools.IsIPInUse(a.config.ServerIP); err != nil {
return err
} else if ok {
runServer(a.config.ServerIP, a.config.Port, router)
} else {
runServer(a.config.LocalIP, a.config.Port, router)
}
return nil
}
// Настраивает маршруты
func (a *app) setupRouter() *http.ServeMux {
router := http.NewServeMux()
// Обработка статических файлов с кэшированием
router.Handle(a.config.AssetsPath, a.staticHandler())
// Обработка главной страницы
router.Handle("/", a.mainPageHandler())
return router
}
// Обработчик статических файлов с кэшированием
func (a *app) staticHandler() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
// Здесь используется встроенный файловый сервер Go (http.FileServer), который:
// Реализует интерфейс http.Handler (и поэтому имеет метод ServeHTTP)
// Автоматически обслуживает статические файлы из файловой системы
// Сам обрабатывает HTTP-запросы, определяет MIME-типы, отправляет правильные заголовки и т.д.
http.FileServer(http.Dir(".")).ServeHTTP(w, r)
})
}
// Обработчик главной страницы
func (a *app) mainPageHandler() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
tmplName := "main" + a.config.TemplatesExt
// Страничка рендерится только если ее нет в кэше
pageData, ok := a.cache.Get(tmplName)
if !ok {
context := map[string]any{
"version": a.startTime,
"renderingTimestamp": time.Now().Unix(),
}
pageData, err = a.renderPage(tmplName, context)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
a.cache.Set(tmplName, pageData)
}
a.sendPage(w, pageData.([]byte))
})
}
// Загрузка шаблонов
func (a *app) loadTemplates(templatesPath string, ext string) error {
tmpls := template.New("")
err := filepath.Walk(templatesPath, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if !f.IsDir() && strings.HasSuffix(f.Name(), ext) {
_, err = tmpls.ParseFiles(path)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
a.templates = tmpls
return nil
}
// Рендерит шаблон в срез байт
func (a *app) renderPage(tmplName string, context any) ([]byte, error) {
var pageData bytes.Buffer
if err := a.templates.ExecuteTemplate(&pageData, tmplName, context); err != nil {
return nil, err
}
return pageData.Bytes(), nil
}
// Отправляет страницу
func (a *app) sendPage(w http.ResponseWriter, data []byte) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(data)
}