/src/fan/Dev.fan

https://bitbucket.org/afrankvt/draft/ · Unknown · 203 lines · 176 code · 27 blank · 0 comment · 0 complexity · cee3c91711a85b22c511ef3707d66953 MD5 · raw file

  1. //
  2. // Copyright (c) 2011, Andy Frank
  3. // Licensed under the MIT License
  4. //
  5. // History:
  6. // 6 Jun 2011 Andy Frank Creation
  7. //
  8. using concurrent
  9. using util
  10. using web
  11. using wisp
  12. //////////////////////////////////////////////////////////////////////////
  13. // DevRestarter
  14. //////////////////////////////////////////////////////////////////////////
  15. ** DevRestarter
  16. const class DevRestarter : Actor
  17. {
  18. new make(ActorPool p, |This| f) : super(p)
  19. {
  20. f(this)
  21. }
  22. ** Check if pods have been modified.
  23. Void checkPods() { send("verify").get(30sec) }
  24. override Obj? receive(Obj? msg)
  25. {
  26. if (msg == "verify")
  27. {
  28. map := Actor.locals["ts"] as Pod:DateTime
  29. if (map == null)
  30. {
  31. startProc
  32. Actor.locals["ts"] = update
  33. }
  34. else if (podsModified(map))
  35. {
  36. log.info("Pods modified, restarting WispService")
  37. stopProc; startProc; Actor.sleep(2sec)
  38. Actor.locals["ts"] = update
  39. }
  40. }
  41. return null
  42. }
  43. ** Update pod modified timestamps.
  44. private Pod:DateTime update()
  45. {
  46. map := Pod:DateTime[:]
  47. Pod.list.each |p| { map[p] = podFile(p).modified }
  48. log.debug("Update pod timestamps ($map.size pods)")
  49. return map
  50. }
  51. ** Return pod file for this Pod.
  52. private File podFile(Pod pod)
  53. {
  54. Env? env := Env.cur
  55. file := env.workDir + `_doesnotexist_`
  56. // walk envs looking for pod file
  57. while (!file.exists && env != null)
  58. {
  59. if (env is PathEnv)
  60. {
  61. ((PathEnv)env).path.eachWhile |p|
  62. {
  63. file = p + `lib/fan/${pod.name}.pod`
  64. return file.exists ? true : null
  65. }
  66. }
  67. else
  68. {
  69. file = env.workDir + `lib/fan/${pod.name}.pod`
  70. }
  71. env = env.parent
  72. }
  73. // verify exists and return
  74. if (!file.exists) throw Err("Pod file not found $pod.name")
  75. return file
  76. }
  77. ** Return true if any pods have been modified since startup.
  78. private Bool podsModified(Pod:DateTime map)
  79. {
  80. true == Pod.list.eachWhile |p|
  81. {
  82. if (podFile(p).modified > map[p])
  83. {
  84. log.debug("$p.name pod has been modified")
  85. return true
  86. }
  87. return null
  88. }
  89. }
  90. ** Start DraftMod process.
  91. private Void startProc()
  92. {
  93. home := Env.cur.homeDir.osPath
  94. args := ["java", "-cp", "${home}/lib/java/sys.jar", "-Dfan.home=$home",
  95. "fanx.tools.Fan", "draft", "-port", "$port", "-prod"]
  96. if (props != null) args.addAll(["-props", props.osPath])
  97. args.add(type.qname)
  98. proc := Process(args).run
  99. Actor.locals["proc"] = proc
  100. log.debug("Start external process")
  101. }
  102. ** Stop DraftMod process.
  103. private Void stopProc()
  104. {
  105. proc := Actor.locals["proc"] as Process
  106. if (proc == null) return
  107. proc.kill
  108. log.debug("Stop external process")
  109. }
  110. const Type type
  111. const Int port
  112. const File? props
  113. const Log log := Log.get("draft")
  114. }
  115. //////////////////////////////////////////////////////////////////////////
  116. // DevMod
  117. //////////////////////////////////////////////////////////////////////////
  118. ** DevMod
  119. const class DevMod : WebMod
  120. {
  121. ** Constructor.
  122. new make(DevRestarter r)
  123. {
  124. this.restarter = r
  125. this.port = r.port
  126. }
  127. ** Target port to proxy requests to.
  128. const Int port
  129. ** DevRestarter instance.
  130. const DevRestarter restarter
  131. override Void onService()
  132. {
  133. // check pods
  134. restarter.checkPods
  135. // 13-Jan-2013
  136. // Safari seems to have trouble creating seesion cookie
  137. // with proxy server - create session here as a workaround
  138. dummy := req.session
  139. // proxy request
  140. c := WebClient()
  141. c.followRedirects = false
  142. c.reqUri = `http://localhost:${port}${req.uri.relToAuth}`
  143. c.reqMethod = req.method
  144. req.headers.each |v,k|
  145. {
  146. // TODO FIXIT: 4-Jun-2014
  147. // WebClient does not support sending multiple Set-Cookie headers
  148. // so can break applications behind the proxy. This is likley the
  149. // same problem as the above Safari hack.
  150. if (k == "Host") return
  151. c.reqHeaders[k] = v
  152. }
  153. c.writeReq
  154. is100Continue := c.reqHeaders["Expect"] == "100-continue"
  155. if (req.method == "POST" && ! is100Continue)
  156. c.reqOut.writeBuf(req.in.readAllBuf).flush
  157. // proxy response
  158. c.readRes
  159. if (is100Continue && c.resCode == 100)
  160. {
  161. c.reqOut.writeBuf(req.in.readAllBuf).flush
  162. c.readRes // final response after the 100continue
  163. }
  164. res.statusCode = c.resCode
  165. c.resHeaders.each |v,k|
  166. {
  167. // we don't re-gzip responses
  168. if (k == "Content-Encoding" && v == "gzip") return
  169. res.headers[k] = v
  170. }
  171. if (c.resHeaders["Content-Type"] != null ||
  172. c.resHeaders["Content-Length"] != null)
  173. res.out.writeBuf(c.resIn.readAllBuf).flush
  174. c.close
  175. }
  176. }