177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"log"
|
||
"main/cache"
|
||
"main/config"
|
||
"main/tools"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"text/template"
|
||
"time"
|
||
)
|
||
|
||
// 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)
|
||
}
|