gopp/server/handler.go

189 lines
3.6 KiB
Go

package server
import (
//"github.com/d5/tengo/v2"
"surdeus.su/util/tpp/mdx"
//"surdeus.su/core/xgo/xmodules/htmlx"
"surdeus.su/core/xgo/xmodules/httpx"
"surdeus.su/core/xgo/xmodules"
"surdeus.su/util/tpp"
"path/filepath"
"net/http"
"context"
"path"
"mime"
"log"
"os"
"io"
)
var _ = http.Handler(&Handler{})
// The type describes behaviour
// of handling stuff like in PHP.
type Handler struct {
// THe field represents
// where we store site's
// source files and request handlers.
sourcePath string
// Preprocessor must be set by user
// to be able to bring custom features in.
pp *tpp.Preprocessor
// Aditional extension. ".tpp" by default.
// For example "file.html.tpp" will be
// first preprocessed TPP and sent back as simple HTML.
ext string
// The global map accessable from all the
// request so you can keep the states
// between requests.
global any
md *mdx.Markdown
indexSuffix string
}
// Returns the new Handler with
// specified preprocessor, source path,
// preprocessor extension and the global value.
func NewHandler(
pp *tpp.Preprocessor,
src, ext, index string,
global any,
) *Handler {
ret := &Handler{}
ret.sourcePath = src
ret.ext = ext
ret.pp = pp
ret.global = global
ret.indexSuffix = index
return ret
}
// Returns the default tpp.Preprocessor suitable for
// the most needs.
func DefaultPP(mod string) *tpp.Preprocessor {
t := tpp.NewTengo().SetPreCompile(func(
ctx context.Context,
s *tpp.Script,
) {
s.SetImportDir(mod)
s.SetImports(xmodules.Modules)
s.EnableFileImport(true)
s.Add("__http_request__", ctx.Value(KeyRequest))
s.Add("__global__", ctx.Value(KeyGlobal))
s.Add("__markdown__", ctx.Value(KeyMarkdown))
}).SetPreCode(func(ctx context.Context) []byte {
return []byte(`
markdown := func(...args) {
pp.write_raw(__markdown__(args...))
}
__http__ := immutable({
request : __http_request__
})
context.http = __http__
context.pp = pp
context.global = __global__
import("./pre")(context)
`)
}).SetPostCode(func(ctx context.Context) []byte {
return []byte(`
import("./post")(context)
`)
})
return tpp.New(t)
}
func (h *Handler) SetMD(md *mdx.Markdown) *Handler {
h.md = md
return h
}
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
shouldProcess := true
urlPath := r.URL.Path
// Cleaning URL path to prevent injections.
urlPath = path.Clean(urlPath)
urlExt := path.Ext(urlPath)
filePath := filepath.Join(
filepath.FromSlash(h.sourcePath),
filepath.FromSlash(urlPath),
)
fileStat, err := os.Stat(filePath)
if err == nil {
if fileStat.IsDir() {
filePath = filepath.Join(
filePath,
h.indexSuffix,
)
}
}
filePathTpp := filePath + h.ext
//log.Println("pth:", urlPath, filePathTpp)
file, err := os.Open(filePathTpp)
if err != nil {
shouldProcess = false
file, err = os.Open(filePath)
if err != nil {
http.NotFound(w, r)
return
}
}
//process := true
fileData, err := io.ReadAll(file)
if err != nil {
http.NotFound(w, r)
return
}
ctx := context.WithValue(
r.Context(),
KeyRequest,
&httpx.Request{
Request: r,
},
)
ctx = context.WithValue(
ctx,
KeyGlobal,
h.global,
)
ctx = context.WithValue(
ctx,
KeyMarkdown,
h.md,
)
// Setting before the code to let it change own
// content type?
contentType := mime.TypeByExtension(urlExt)
w.Header().Set("Content-Type", contentType)
processedData := fileData
if shouldProcess {
processedData, err = h.pp.Process(
ctx,
true,
filePathTpp,
fileData,
)
if err != nil {
http.NotFound(w, r)
log.Printf(
"Error: pp.Process(...): %s\n",
err,
)
return
}
}
w.Write(processedData)
}