Отображение html страницы

- Добавлен шаблонизатор jet

- Сделан views для html страниц

- Переработано отображение ошибок, теперь они корректно отображаются
This commit is contained in:
Michael Makarochkin 2025-03-14 02:53:36 +03:00
parent ff614cea04
commit f7a4ad6cdb
15 changed files with 298 additions and 29 deletions

0
README.md Normal file
View File

View File

@ -3,6 +3,8 @@ module pink_fox
go 1.24.1
require (
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
github.com/CloudyKit/jet/v6 v6.3.1 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect

View File

@ -1,3 +1,7 @@
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw=
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=

View File

@ -1,16 +1,23 @@
package cmd
import (
"bytes"
"database/sql"
"fmt"
"github.com/CloudyKit/jet/v6"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
"io"
"net/http"
"pink_fox/src/app"
c "pink_fox/src/app/config"
"pink_fox/src/app/db"
l "pink_fox/src/app/logger"
"pink_fox/src/app/routes_manager"
"pink_fox/src/app/views_manager"
"pink_fox/src/routes"
"pink_fox/src/views"
"runtime/debug"
)
type Server struct {
@ -52,13 +59,93 @@ func (it *Server) StartServer(application *app.Application) error {
c.String(http.StatusOK, "ok")
})
routes.RegistrationRoutes(routes_manager.NewManager(r, application))
r.Use(it.errorHandler(application.Logger))
err := it.loadTemplates(r)
if err != nil {
return err
}
tm := views_manager.NewManager()
views.RegistrationViews(tm)
routes.RegistrationRoutes(routes_manager.NewManager(r, application, tm))
fmt.Printf("Starting http_server at port %d...", application.Conf.Port)
err := r.Run(fmt.Sprintf(":%d", application.Conf.Port))
err = r.Run(fmt.Sprintf(":%d", application.Conf.Port))
if err != nil {
return err
}
return nil
}
func (it *Server) loadTemplates(e *gin.Engine) error {
set := jet.NewSet(jet.NewOSFileSystemLoader("views"))
renderer := &JetRenderer{set: set}
e.HTMLRender = renderer
return nil
}
func (it *Server) errorHandler(logger *l.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
it.errorHandlerSetRecovery(ctx)
if len(ctx.Errors) > 0 {
logger.Error(ctx.Errors[0].Error())
ctx.JSON(http.StatusInternalServerError, gin.H{
"errors": "Internal Server Error",
})
}
}
}
func (it *Server) errorHandlerSetRecovery(ctx *gin.Context) {
defer func() {
if err := recover(); err != nil {
_ = ctx.Error(fmt.Errorf("panic: %v\n%s", err, debug.Stack()))
}
}()
ctx.Next()
}
type JetRenderer struct {
set *jet.Set
template string
data any
}
func (it *JetRenderer) Instance(tpl string, data any) render.Render {
it.template = tpl
it.data = data
return it
}
func (it *JetRenderer) Render(w http.ResponseWriter) error {
tpl, err := it.set.GetTemplate(it.template)
if err != nil {
return err
}
vars := make(jet.VarMap)
if it.data != nil {
if v, ok := it.data.(jet.VarMap); ok {
vars = v
} else {
vars.Set("data", it.data)
}
}
var buf bytes.Buffer
err = tpl.Execute(&buf, vars, nil)
if err != nil {
return err
}
_, err = io.Copy(w, &buf)
return err
}
func (it *JetRenderer) WriteContentType(http.ResponseWriter) {
// не знаю в каких случаях будет вызван этот метод
panic("implement me") // TODO
}

View File

@ -2,14 +2,19 @@ package http_server
import (
"github.com/gin-gonic/gin"
"pink_fox/src/app/types"
)
type ResponseFactory struct {
ctx *gin.Context
ctx *gin.Context
makeViewObject types.MakeViewObject
}
func NewResponseFactory(ctx *gin.Context) *ResponseFactory {
return &ResponseFactory{ctx: ctx}
func NewResponseFactory(ctx *gin.Context, makeViewObject types.MakeViewObject) *ResponseFactory {
return &ResponseFactory{
ctx: ctx,
makeViewObject: makeViewObject,
}
}
func (it *ResponseFactory) String(s string) *StringResponse {
@ -23,3 +28,7 @@ func (it *ResponseFactory) HtmlError(code int) *HtmlErrorResponse {
func (it *ResponseFactory) Redirect(url string) *RedirectResponse {
return NewRedirectResponse(url, it.ctx)
}
func (it *ResponseFactory) View(id int16, file string, data any) *ViewResponse {
return NewViewResponse(it.ctx, it.makeViewObject, id, file, data)
}

View File

@ -2,8 +2,10 @@ package http_server
import (
"fmt"
"github.com/CloudyKit/jet/v6"
"github.com/gin-gonic/gin"
"net/http"
"pink_fox/src/app/types"
)
type Response interface {
@ -65,3 +67,29 @@ func (it *RedirectResponse) SetCode(code int) *RedirectResponse {
func (it *RedirectResponse) Render() {
it.ctx.Redirect(it.code, it.to)
}
type ViewResponse struct {
ctx *gin.Context
makeViewObject types.MakeViewObject
id int16
file string
data any
}
func NewViewResponse(ctx *gin.Context, makeViewObject types.MakeViewObject, id int16, file string, data any) *ViewResponse {
return &ViewResponse{
ctx: ctx,
makeViewObject: makeViewObject,
id: id,
file: file,
data: data,
}
}
func (it *ViewResponse) Render() {
data := make(jet.VarMap)
data.Set("vi", it.makeViewObject(it.id))
data.Set("it", it.data)
it.ctx.HTML(http.StatusOK, it.file, data)
}

View File

@ -5,8 +5,10 @@ import (
"github.com/gin-gonic/gin"
"net/http"
"pink_fox/src/app"
"pink_fox/src/app/http_server"
hs "pink_fox/src/app/http_server"
le "pink_fox/src/app/lerror"
"pink_fox/src/app/types"
vm2 "pink_fox/src/app/views_manager"
"pink_fox/src/dependence"
"runtime/debug"
"strings"
@ -16,21 +18,23 @@ type RoutesManager struct {
engine *gin.Engine
application *app.Application
middlewares []MiddlewareFunc
vm *vm2.ViewsManager
}
func NewManager(e *gin.Engine, application *app.Application) *RoutesManager {
func NewManager(e *gin.Engine, application *app.Application, vm *vm2.ViewsManager) *RoutesManager {
return &RoutesManager{
engine: e,
application: application,
middlewares: make([]MiddlewareFunc, 0),
vm: vm,
}
}
type MiddlewareFunc func(*dependence.Container, ActionFunc) (http_server.Response, *le.Error)
type MiddlewareFunc func(*dependence.Container, ActionFunc) (hs.Response, *le.Error)
type HandlerFunc func(container *dependence.Container) (http_server.Response, *le.Error)
type HandlerFunc func(container *dependence.Container) (hs.Response, *le.Error)
type ActionFunc func() (http_server.Response, *le.Error)
type ActionFunc func() (hs.Response, *le.Error)
func (it *RoutesManager) Group(relativePath string, middlewares ...MiddlewareFunc) *Group {
return NewGroup(it, relativePath, middlewares...)
@ -41,18 +45,18 @@ func (it *RoutesManager) Use(middlewares ...MiddlewareFunc) {
}
func (it *RoutesManager) ANY(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application, it.vm)
it.engine.GET(relativePath, handlerForGin)
it.engine.POST(relativePath, handlerForGin)
}
func (it *RoutesManager) GET(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application, it.vm)
it.engine.GET(relativePath, handlerForGin)
}
func (it *RoutesManager) POST(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.application, it.vm)
it.engine.POST(relativePath, handlerForGin)
}
@ -83,36 +87,62 @@ func (it *Group) Use(middlewares ...MiddlewareFunc) {
}
func (it *Group) ANY(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application, it.routesManager.vm)
it.routesManager.engine.GET(it.relativePath+relativePath, handlerForGin)
it.routesManager.engine.POST(it.relativePath+relativePath, handlerForGin)
}
func (it *Group) GET(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application, it.routesManager.vm)
it.routesManager.engine.GET(it.relativePath+relativePath, handlerForGin)
}
func (it *Group) POST(relativePath string, handler HandlerFunc, middlewares ...MiddlewareFunc) {
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application)
handlerForGin := makeHandlerGin(handler, append(it.middlewares, middlewares...), it.routesManager.application, it.routesManager.vm)
it.routesManager.engine.POST(it.relativePath+relativePath, handlerForGin)
}
func makeHandlerGin(handler HandlerFunc, middlewares []MiddlewareFunc, application *app.Application) gin.HandlerFunc {
func makeHandlerGin(handler HandlerFunc, middlewares []MiddlewareFunc, application *app.Application, vm *vm2.ViewsManager) gin.HandlerFunc {
return func(ctx *gin.Context) {
dic := dependence.NewContainer(application, ctx)
dic.SetViewManager(castMakeViewObject(dic, vm))
defer dic.Close()
res, err := pipeline(handler, middlewares, dic)
err := execHandlerGin(handler, middlewares, dic, ctx)
if err != nil {
writeToLog(err, application)
showHtmlError(err, application, ctx)
return
}
res.Render()
}
}
func pipeline(endpoint HandlerFunc, middlewares []MiddlewareFunc, di *dependence.Container) (resp http_server.Response, lerr *le.Error) {
func castMakeViewObject(depend *dependence.Container, vm *vm2.ViewsManager) types.MakeViewObject {
return func(id int16) any {
obj, err := vm.GetViewObject(id, depend)
if err != nil {
panic(fmt.Sprintf("шаблон с id=%d не найден", id))
}
return obj
}
}
func execHandlerGin(handler HandlerFunc, middlewares []MiddlewareFunc, dic *dependence.Container, ctx *gin.Context) *le.Error {
res, err := pipeline(handler, middlewares, dic)
if err != nil {
return err
}
res.Render()
if len(ctx.Errors) > 0 {
errorGin := ctx.Errors[0]
ctx.Errors = make([]*gin.Error, 0)
return le.New(errorGin)
}
return nil
}
func pipeline(endpoint HandlerFunc, middlewares []MiddlewareFunc, di *dependence.Container) (resp hs.Response, lerr *le.Error) {
defer func() {
if err := recover(); err != nil {
resp = nil
@ -120,7 +150,7 @@ func pipeline(endpoint HandlerFunc, middlewares []MiddlewareFunc, di *dependence
}
}()
handler := func() (http_server.Response, *le.Error) {
handler := func() (hs.Response, *le.Error) {
return endpoint(di)
}
@ -133,7 +163,7 @@ func pipeline(endpoint HandlerFunc, middlewares []MiddlewareFunc, di *dependence
}
func createMiddleware(next ActionFunc, middleware MiddlewareFunc, di *dependence.Container) ActionFunc {
return func() (http_server.Response, *le.Error) {
return func() (hs.Response, *le.Error) {
return middleware(di, next)
}
}
@ -158,5 +188,5 @@ func showHtmlError(err *le.Error, application *app.Application, ctx *gin.Context
}
}
ctx.Header("Content-Type", "text/html; charset=utf-8")
ctx.String(500, fmt.Sprintf("<h1>500 %s</h1>\n%s\n", http.StatusText(500), message))
ctx.String(http.StatusInternalServerError, fmt.Sprintf("<h1>%d %s</h1>\n%s\n", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), message))
}

View File

@ -0,0 +1,3 @@
package types
type MakeViewObject func(int16) any

View File

@ -0,0 +1,30 @@
package views_manager
import (
"fmt"
"pink_fox/src/dependence"
)
type ViewsManager struct {
list map[int16]ViewsFunc
}
func NewManager() *ViewsManager {
return &ViewsManager{
list: make(map[int16]ViewsFunc),
}
}
func (it *ViewsManager) Add(id int16, tplFunc ViewsFunc) {
it.list[id] = tplFunc
}
func (it *ViewsManager) GetViewObject(id int16, depend *dependence.Container) (any, error) {
tplFunc, ok := it.list[id]
if !ok {
return nil, fmt.Errorf("GetViewObject: template id %v not found", id)
}
return tplFunc(depend), nil
}
type ViewsFunc func(*dependence.Container) any

View File

@ -0,0 +1,25 @@
package controllers
import (
"pink_fox/src/app/http_server"
le "pink_fox/src/app/lerror"
"pink_fox/src/views"
)
type HtmlController struct {
responseFactory *http_server.ResponseFactory
}
type HtmlControllerDependence interface {
MakeResponseFactory() *http_server.ResponseFactory
}
func NewHtmlController(depend HtmlControllerDependence) *HtmlController {
return &HtmlController{
responseFactory: depend.MakeResponseFactory(),
}
}
func (it *HtmlController) IndexAction() (http_server.Response, *le.Error) {
return it.responseFactory.View(views.Base, "test.jet", "hello"), nil
}

View File

@ -4,12 +4,14 @@ import (
"github.com/gin-gonic/gin"
"pink_fox/src/app"
"pink_fox/src/app/http_server"
"pink_fox/src/app/types"
repo "pink_fox/src/repositories"
)
type Container struct {
application *app.Application
context *gin.Context
application *app.Application
context *gin.Context
makeViewObject types.MakeViewObject
}
func NewContainer(application *app.Application, context *gin.Context) *Container {
@ -19,6 +21,10 @@ func NewContainer(application *app.Application, context *gin.Context) *Container
}
}
func (it *Container) SetViewManager(makeViewObject types.MakeViewObject) {
it.makeViewObject = makeViewObject
}
func (it *Container) Close() {
}
@ -27,7 +33,7 @@ func (it *Container) MakeRequest() *http_server.Request {
}
func (it *Container) MakeResponseFactory() *http_server.ResponseFactory {
return http_server.NewResponseFactory(it.context)
return http_server.NewResponseFactory(it.context, it.makeViewObject)
}
func (it *Container) MakeLinksRepository() repo.LinksRepository {

View File

@ -1,7 +1,7 @@
package routes
import (
"pink_fox/src/app/http_server"
hs "pink_fox/src/app/http_server"
le "pink_fox/src/app/lerror"
"pink_fox/src/app/routes_manager"
"pink_fox/src/controllers"
@ -12,12 +12,18 @@ func RegistrationRoutes(r *routes_manager.RoutesManager) {
r.GET("/", IndexController)
r.GET("/link/:token", GoController)
r.GET("/html", HtmlController)
}
func IndexController(depend *di.Container) (http_server.Response, *le.Error) {
func HtmlController(depend *di.Container) (hs.Response, *le.Error) {
return controllers.NewHtmlController(depend).IndexAction()
}
func IndexController(depend *di.Container) (hs.Response, *le.Error) {
return controllers.NewIndexController(depend).IndexAction()
}
func GoController(depend *di.Container) (http_server.Response, *le.Error) {
func GoController(depend *di.Container) (hs.Response, *le.Error) {
return controllers.NewGoController().IndexAction(depend)
}

View File

@ -0,0 +1,21 @@
package views
import (
repo "pink_fox/src/repositories"
)
const Base int16 = 1
type BaseView struct {
Title string
}
type BaseViewInterface interface {
MakeLinksRepository() repo.LinksRepository
}
func NewBase(_ BaseViewInterface) *BaseView {
return &BaseView{
Title: "Hello World!",
}
}

View File

@ -0,0 +1,16 @@
package views
import (
"pink_fox/src/app/views_manager"
"pink_fox/src/dependence"
)
func RegistrationViews(tm *views_manager.ViewsManager) {
tm.Add(Base, MakeBaseView())
}
func MakeBaseView() func(container *dependence.Container) any {
return func(depend *dependence.Container) any {
return NewBase(depend)
}
}

View File

@ -0,0 +1,2 @@
<h1>{{ vi.Title }}</h1>
<p>{{ it }}</p>