handler.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package kyra
  2. import (
  3. "surdeus.su/util/kyra/mdx"
  4. httpx "surdeus.su/core/xgo/v2/stdlib/http"
  5. "surdeus.su/core/xgo/v2/stdlib"
  6. "surdeus.su/util/gopp"
  7. "bytes"
  8. "path/filepath"
  9. "net/http"
  10. "context"
  11. "path"
  12. "mime"
  13. "log"
  14. "os"
  15. "io"
  16. )
  17. var _ = http.Handler(&Handler{})
  18. // The type describes behaviour
  19. // of handling stuff like in PHP.
  20. type Handler struct {
  21. // THe field represents
  22. // where we store site's
  23. // source files and request handlers.
  24. sourcePath string
  25. // Preprocessor must be set by user
  26. // to be able to bring custom features in.
  27. pp *gopp.Preprocessor
  28. // Aditional extension. ".kr" by default.
  29. // For example "file.html.kr" will be
  30. // first preprocessed by gopp and sent back as simple HTML.
  31. ext string
  32. // The global map accessable from all the
  33. // request so you can keep the states
  34. // between requests.
  35. global any
  36. md *mdx.Markdown
  37. indexSuffix string
  38. }
  39. // Returns the new Handler with
  40. // specified preprocessor, source path,
  41. // preprocessor extension and the global value.
  42. func NewHandler(
  43. pp *gopp.Preprocessor,
  44. src, ext, index string,
  45. global any,
  46. ) *Handler {
  47. ret := &Handler{}
  48. ret.sourcePath = src
  49. ret.ext = ext
  50. ret.pp = pp
  51. ret.global = global
  52. ret.indexSuffix = index
  53. return ret
  54. }
  55. // Returns the default gopp.Preprocessor suitable for
  56. // the most needs.
  57. func DefaultPP(mod string) *gopp.Preprocessor {
  58. t := gopp.NewXGo().SetPreCompile(func(
  59. ctx context.Context,
  60. s *gopp.Script,
  61. ) {
  62. s.SetImportDir(mod)
  63. modules := stdlib.GetModuleMap(stdlib.AllModuleNames()...)
  64. modules.Add("markdown", mdx.GetModule())
  65. s.SetImports(modules)
  66. s.EnableFileImport(true)
  67. vals := ctx.Value(KeyValues).(*ContextValues)
  68. s.Add("__http_request__", vals.Request)
  69. s.Add("__global__", vals.Global)
  70. s.Add("__markdown__", vals.Markdown)
  71. s.Add("__is_index__", vals.IsIndex)
  72. s.Add("__index_suffix__", vals.IndexSuffix)
  73. s.Add("__pp_ext__", vals.PPExt)
  74. s.Add("__source_path__", vals.SourcePath)
  75. s.Add("__fext__", vals.FExt)
  76. s.Add("__fpath__", vals.FPath)
  77. }).SetPreCode(func(ctx context.Context) []byte {
  78. return []byte(`
  79. __os__ := import("os")
  80. markdown := func(...args) {
  81. return pp.write_raw(__markdown__(args...))
  82. }
  83. __http__ := immutable({
  84. request : __http_request__
  85. })
  86. context.http = __http__
  87. context.pp = pp
  88. context.global = __global__
  89. context.is_index = __is_index__
  90. context.index_suffix = __index_suffix__
  91. context.pp_ext = __pp_ext__
  92. context.source_path = __source_path__
  93. context.fext = __fext__
  94. context.fpath = __fpath__
  95. import("./pre")(context)
  96. `)
  97. }).SetPostCode(func(ctx context.Context) []byte {
  98. return []byte(`
  99. import("./post")(context)
  100. `)
  101. })
  102. return gopp.New(t)
  103. }
  104. func (h *Handler) SetMD(md *mdx.Markdown) *Handler {
  105. h.md = md
  106. return h
  107. }
  108. func (h *Handler) ServeHTTP(
  109. w http.ResponseWriter,
  110. r *http.Request,
  111. ) {
  112. isIndex := false
  113. shouldProcess := true
  114. urlPath := r.URL.Path
  115. // Cleaning URL path to prevent injections.
  116. urlPath = path.Clean(urlPath)
  117. urlExt := path.Ext(urlPath)
  118. if urlExt == h.ext {
  119. http.NotFound(w, r)
  120. return
  121. }
  122. filePath := filepath.Join(
  123. filepath.FromSlash(h.sourcePath),
  124. filepath.FromSlash(urlPath),
  125. )
  126. fileStat, err := os.Stat(filePath)
  127. if err == nil {
  128. if fileStat.IsDir() {
  129. filePath = filepath.Join(
  130. filePath,
  131. h.indexSuffix,
  132. )
  133. isIndex = true
  134. }
  135. }
  136. filePathTpp := filePath + h.ext
  137. //log.Println("pth:", urlPath, filePathTpp)
  138. file, err := os.Open(filePathTpp)
  139. if err != nil {
  140. shouldProcess = false
  141. file, err = os.Open(filePath)
  142. if err != nil {
  143. http.NotFound(w, r)
  144. return
  145. }
  146. }
  147. fext := filepath.Ext(filePath)
  148. fpath := filePath
  149. ctx := context.WithValue(
  150. r.Context(),
  151. KeyValues,
  152. &ContextValues{
  153. Request: &httpx.Request{
  154. Request: r,
  155. },
  156. Global: h.global,
  157. Markdown: h.md,
  158. IsIndex: isIndex,
  159. IndexSuffix: h.indexSuffix,
  160. PPExt: h.ext,
  161. SourcePath: h.sourcePath,
  162. FPath: fpath,
  163. FExt: fext,
  164. },
  165. )
  166. // Setting before the code to let it change own
  167. // content type?
  168. fileExt := filepath.Ext(filePath)
  169. contentType := mime.TypeByExtension(fileExt)
  170. w.Header().Set("Content-Type", contentType)
  171. var buf bytes.Buffer
  172. var reader io.Reader
  173. if shouldProcess {
  174. compiled, err := h.pp.Compile(
  175. ctx, file,
  176. )
  177. if err != nil {
  178. log.Printf("gopp.Compile(...): %s\n", err)
  179. http.Error(w, err.Error(), 500)
  180. return
  181. }
  182. err = h.pp.XGo().RunContext(
  183. ctx, compiled,
  184. filePath, &buf,
  185. )
  186. if err != nil {
  187. log.Printf("gopp.RunContext(...): %s\n", err)
  188. http.Error(w, err.Error(), 500)
  189. return
  190. }
  191. reader = &buf
  192. } else {
  193. reader = file
  194. }
  195. io.Copy(w, reader)
  196. }