mvc
parent
7884d972d2
commit
e7c0d09456
152
main.go
152
main.go
|
@ -1,17 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"log"
|
"log"
|
||||||
"main/cache"
|
"main/mvc/controllers"
|
||||||
"main/config"
|
"main/mvc/models"
|
||||||
"main/tools"
|
"main/tools"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -20,163 +14,53 @@ go build main.go
|
||||||
sudo systemctl start server.service
|
sudo systemctl start server.service
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// App представляет основное состояние приложения
|
|
||||||
type app struct {
|
|
||||||
templates *template.Template // Шаблоны страниц
|
|
||||||
config *config.Config // Конфиг
|
|
||||||
startTime int64 // Время запуска
|
|
||||||
cache *cache.Cache // Кэш (отрендеренные странички)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var app *app
|
var app *models.App
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Инициализация приложения
|
// Инициализация приложения
|
||||||
if app, err = appInit("config.json"); err != nil {
|
if app, err = models.AppInit("config.json"); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Настройка маршрутов и запуск
|
// Настройка маршрутов и запуск
|
||||||
if app.setupRouterAndRun() != nil {
|
if setupRoutesAndRun(app) != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запускает сервер на указанном IP и порту
|
func setupRoutesAndRun(a *models.App) error {
|
||||||
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()
|
router := setupRoutes(a)
|
||||||
|
|
||||||
// Запуск сервера
|
// Запуск сервера
|
||||||
if ok, err := tools.IsIPInUse(a.config.ServerIP); err != nil {
|
if ok, err := tools.IsIPInUse(a.Config.ServerIP); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
runServer(a.config.ServerIP, a.config.Port, router)
|
runServer(a.Config.ServerIP, a.Config.Port, router)
|
||||||
} else {
|
} else {
|
||||||
runServer(a.config.LocalIP, a.config.Port, router)
|
runServer(a.Config.LocalIP, a.Config.Port, router)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Настраивает маршруты
|
// Настраивает маршруты
|
||||||
func (a *app) setupRouter() *http.ServeMux {
|
func setupRoutes(a *models.App) *http.ServeMux {
|
||||||
router := http.NewServeMux()
|
router := http.NewServeMux()
|
||||||
|
|
||||||
// Обработка статических файлов с кэшированием
|
// Обработка статических файлов с кэшированием
|
||||||
router.Handle(a.config.AssetsPath, a.staticHandler())
|
router.Handle(a.Config.AssetsPath, controllers.StaticHandler())
|
||||||
|
|
||||||
// Обработка главной страницы
|
// Обработка главной страницы
|
||||||
router.Handle("/", a.mainPageHandler())
|
router.Handle("/", controllers.MainPageHandler(a))
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчик статических файлов с кэшированием
|
// Обертка над ListenAndServe, запускает сервер на указанном IP, PORT
|
||||||
func (a *app) staticHandler() http.HandlerFunc {
|
func runServer(ip, port string, router http.Handler) {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
addr := ip + port
|
||||||
w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
|
log.Println("Run on", addr)
|
||||||
// Здесь используется встроенный файловый сервер Go (http.FileServer), который:
|
log.Fatal(http.ListenAndServe(addr, router))
|
||||||
// Реализует интерфейс 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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"main/mvc/models"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Обработчик главной страницы
|
||||||
|
func MainPageHandler(a *models.App) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Страничка рендерится только если ее нет в кэше
|
||||||
|
pageData, ok := a.Cache.Get(models.MainPageTmplName)
|
||||||
|
if !ok {
|
||||||
|
pageData, err = a.RenderMainPage()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.Cache.Set(models.MainPageTmplName, pageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMainPage(w, pageData.([]byte))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправляет страницу
|
||||||
|
func SendMainPage(w http.ResponseWriter, data []byte) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Обработчик статических файлов с кэшированием
|
||||||
|
func 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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App хранит информацию о приложении
|
||||||
|
type App struct {
|
||||||
|
Config *Config // Конфиг
|
||||||
|
Templates *template.Template // Шаблоны страниц
|
||||||
|
Cache *Cache // Кэш (отрендеренные странички)
|
||||||
|
StartTime int64 // Время запуска
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализирует приложение
|
||||||
|
func AppInit(configPath string) (*App, error) {
|
||||||
|
|
||||||
|
a := &App{
|
||||||
|
StartTime: time.Now().Unix(),
|
||||||
|
Config: ConfigInit(),
|
||||||
|
Cache: CacheInit(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка конфига
|
||||||
|
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) 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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package cache
|
package models
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ type Cache struct {
|
||||||
Mu sync.RWMutex
|
Mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() *Cache {
|
func CacheInit() *Cache {
|
||||||
return &Cache{Data: make(map[string]any)}
|
return &Cache{Data: make(map[string]any)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package config
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -14,7 +14,7 @@ type Config struct {
|
||||||
Port string
|
Port string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init() *Config {
|
func ConfigInit() *Config {
|
||||||
return &Config{}
|
return &Config{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Имя соответствующего шаблона
|
||||||
|
MainPageTmplName = "main_page.gohtml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *App) RenderMainPage() ([]byte, error) {
|
||||||
|
var pageData bytes.Buffer
|
||||||
|
|
||||||
|
context := map[string]any{
|
||||||
|
"version": a.StartTime,
|
||||||
|
"renderingTimestamp": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.Templates.ExecuteTemplate(&pageData, MainPageTmplName, context); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageData.Bytes(), nil
|
||||||
|
}
|
Loading…
Reference in New Issue