PageRenderTime 1406ms CodeModel.GetById 1389ms RepoModel.GetById 0ms app.codeStats 0ms

/src/fan/DraftMod.fan

https://bitbucket.org/afrankvt/draft/
Unknown | 261 lines | 224 code | 37 blank | 0 comment | 0 complexity | b3b00b3cf221d4b77ef5cd7c8e5afac9 MD5 | raw file
  1. //
  2. // Copyright (c) 2011, Andy Frank
  3. // Licensed under the MIT License
  4. //
  5. // History:
  6. // 14 May 2011 Andy Frank Creation
  7. //
  8. using concurrent
  9. using util
  10. using web
  11. using webmod
  12. **
  13. ** DraftMod
  14. **
  15. abstract const class DraftMod : WebMod
  16. {
  17. ** Constructor.
  18. new make()
  19. {
  20. router = Router { routes=[,] }
  21. // check for pubDir prop
  22. dir := typeof.pod.config("pubDir")
  23. if (dir != null) pubDir = dir.toUri.toFile
  24. }
  25. ** Router model.
  26. const Router router
  27. **
  28. ** Directory to publish as public files under '/pub/' URI:
  29. ** pubDir := `/foo/bar/`
  30. ** /foo/bar/index.css => `/pub/index.css`
  31. ** /foo/bar/img/logo.png => `/pub/img/logo.png`
  32. **
  33. ** The pubDir may also be defined as a [config]`sys::Env#config`
  34. ** property in 'etc/draft/config.props`
  35. **
  36. const File? pubDir := null
  37. **
  38. ** Directory to write log files to. If left null, no logging
  39. ** will be performed.
  40. **
  41. const File? logDir := null
  42. **
  43. ** Format of the web log records as a string of names.
  44. ** See [webmod]`webmod::pod-doc#log`
  45. **
  46. const Str logFields := "date time c-ip cs(X-Real-IP) cs-method " +
  47. "cs-uri-stem cs-uri-query sc-status time-taken " +
  48. "cs(User-Agent) cs(Referer) cs(Cookie)"
  49. **
  50. ** Map of URI path names to sub-WebMods. Sub mods are checked
  51. ** for matching routes before we process our own routes.
  52. **
  53. // TODO: not sure how this works yet
  54. @NoDoc
  55. const Str:WebMod subMods := Str:WebMod[:]
  56. ** Invoked prior to serviceing the current request.
  57. virtual Void onBeforeService(Str:Str args) {}
  58. ** Invoked after serviceing the current request.
  59. virtual Void onAfterService(Str:Str args) {}
  60. ** Service incoming request.
  61. override Void onService()
  62. {
  63. try
  64. {
  65. // set mod
  66. req.mod = this
  67. // check for pub
  68. if (req.uri.path.first == "pub" && pubDir != null)
  69. { onServicePub; return }
  70. // check for pod
  71. if (req.uri.path.first == "pod")
  72. { onServicePod; return }
  73. // check for sub mod
  74. sub := subMods[req.modRel.path.first ?: ""]
  75. if (sub != null)
  76. {
  77. req.mod = sub
  78. req.modBase = req.modBase + `$req.modRel.path.first/`
  79. sub.onService
  80. return
  81. }
  82. // match req to Route
  83. match := router.match(req.modRel, req.method)
  84. if (match == null) throw DraftErr(404)
  85. req.stash["draft.route.meta"] = match.meta
  86. // access session here before response is commited so
  87. // session cookie has a chance to be added to res header
  88. dummy := flash
  89. // allow pre-service
  90. onBeforeService(match.args)
  91. if (res.isDone) return
  92. // delegate to Route.handler
  93. h := match.route.handler
  94. args := h.params.isEmpty ? null : [match.args]
  95. weblet := h.parent == typeof ? this : h.parent.make
  96. weblet.trap(h.name, args)
  97. // allow post-service
  98. onAfterService(match.args)
  99. // store flash for next req
  100. req.session["draft.flash"] = flash.res.ro.toImmutable
  101. // TODO - force flush here?
  102. // res.out.flush
  103. }
  104. catch (Err err)
  105. {
  106. if (err isnot DraftErr) err = DraftErr(500, err)
  107. logErr(err)
  108. onErr(err)
  109. }
  110. finally
  111. {
  112. // log requst
  113. logMod?.onService
  114. }
  115. }
  116. ** Service a pub request.
  117. private Void onServicePub()
  118. {
  119. file := pubDir + req.uri[1..-1]
  120. if (!file.exists) throw DraftErr(404)
  121. FileWeblet(file).onService
  122. }
  123. ** Service a pod request.
  124. private Void onServicePod()
  125. {
  126. // must have at least 3 path segments
  127. path := req.uri.path
  128. if (path.size < 2) throw DraftErr(404)
  129. // lookup pod
  130. pod := Pod.find(path[1], false)
  131. if (pod == null) throw DraftErr(404)
  132. // lookup file
  133. file := pod.file(`/` + req.uri[2..-1], false)
  134. if (file == null) throw DraftErr(404)
  135. FileWeblet(file).onService
  136. }
  137. //////////////////////////////////////////////////////////////////////////
  138. // Flash
  139. //////////////////////////////////////////////////////////////////////////
  140. ** Get flash instance for this request.
  141. Flash flash()
  142. {
  143. flash := req.stash["draft.flash"]
  144. if (flash == null)
  145. {
  146. map := req.session["draft.flash"] ?: Str:Str[:]
  147. req.stash["draft.flash"] = flash = Flash(map)
  148. }
  149. return flash
  150. }
  151. //////////////////////////////////////////////////////////////////////////
  152. // Lifecycle
  153. //////////////////////////////////////////////////////////////////////////
  154. ** Handle startup tasks.
  155. override Void onStart()
  156. {
  157. if (logDir != null)
  158. {
  159. // start LogMod
  160. logMod := LogMod { dir=logDir; filename="web-{YYYY-MM}.log"; fields=logFields }
  161. this.logModRef.val = logMod
  162. logMod.onStart
  163. }
  164. }
  165. private LogMod? logMod() { logModRef.val }
  166. private const AtomicRef logModRef := AtomicRef(null)
  167. //////////////////////////////////////////////////////////////////////////
  168. // Errs
  169. //////////////////////////////////////////////////////////////////////////
  170. ** Handle an error condition during a request.
  171. virtual Void onErr(DraftErr err)
  172. {
  173. // pick best err msg
  174. msg := err.errCode == 500 && err.cause != null ? err.cause.msg : err.msg
  175. // setup response if not already commited
  176. if (!res.isCommitted)
  177. {
  178. res.statusCode = err.errCode
  179. res.headers["Content-Type"] = "text/html; charset=UTF-8"
  180. res.headers["Draft-Err-Msg"] = msg
  181. }
  182. // send HTML response
  183. out := res.out
  184. out.docType
  185. out.html
  186. out.head
  187. .title.esc(err.msg).titleEnd
  188. .style.w("pre,td { font-family:monospace; }
  189. td:first-child { color:#888; padding-right:1em; }").styleEnd
  190. .headEnd
  191. out.body
  192. // msg
  193. out.h1.esc(err.msg).h1End
  194. if (err.msg != msg) out.h2.esc(msg).h2End
  195. if (!err.errCode.toStr.startsWith("4"))
  196. {
  197. out.hr
  198. // req headers
  199. out.table
  200. req.headers.each |v,k| { out.tr.td.w(k).tdEnd.td.w(v).tdEnd.trEnd }
  201. out.tableEnd
  202. out.hr
  203. // stack trace
  204. out.pre.w(err.traceToStr).preEnd
  205. }
  206. out.bodyEnd
  207. out.htmlEnd
  208. }
  209. ** Log error.
  210. private Void logErr(DraftErr err)
  211. {
  212. // don't spam logs for favicon/robots.txt or 404
  213. if (req.uri == `/favicon.ico`) return
  214. if (req.uri == `/robots.txt`) return
  215. if (err.errCode == 404) return
  216. buf := StrBuf()
  217. buf.add("$err.msg - $req.uri\n")
  218. req.headers.each |v,k| { buf.add(" $k: $v\n") }
  219. err.traceToStr.splitLines.each |s| { buf.add(" $s\n") }
  220. log.err(buf.toStr.trim)
  221. }
  222. ** Log for DraftMod.
  223. private const static Log log := Log.get("draft")
  224. }