//
// Copyright (c) 2011, Andy Frank
// Licensed under the MIT License
//
// History:
// 6 Jun 2011 Andy Frank Creation
//
using concurrent
using util
using web
using wisp
//////////////////////////////////////////////////////////////////////////
// DevRestarter
//////////////////////////////////////////////////////////////////////////
** DevRestarter
const class DevRestarter : Actor
{
new make(ActorPool p, |This| f) : super(p)
{
f(this)
}
** Check if pods have been modified.
Void checkPods() { send("verify").get(30sec) }
override Obj? receive(Obj? msg)
{
if (msg == "verify")
{
map := Actor.locals["ts"] as Pod:DateTime
if (map == null)
{
startProc
Actor.locals["ts"] = update
}
else if (podsModified(map))
{
log.info("Pods modified, restarting WispService")
stopProc; startProc; Actor.sleep(2sec)
Actor.locals["ts"] = update
}
}
return null
}
** Update pod modified timestamps.
private Pod:DateTime update()
{
map := Pod:DateTime[:]
Pod.list.each |p| { map[p] = podFile(p).modified }
log.debug("Update pod timestamps ($map.size pods)")
return map
}
** Return pod file for this Pod.
private File podFile(Pod pod)
{
Env? env := Env.cur
file := env.workDir + `_doesnotexist_`
// walk envs looking for pod file
while (!file.exists && env != null)
{
if (env is PathEnv)
{
((PathEnv)env).path.eachWhile |p|
{
file = p + `lib/fan/${pod.name}.pod`
return file.exists ? true : null
}
}
else
{
file = env.workDir + `lib/fan/${pod.name}.pod`
}
env = env.parent
}
// verify exists and return
if (!file.exists) throw Err("Pod file not found $pod.name")
return file
}
** Return true if any pods have been modified since startup.
private Bool podsModified(Pod:DateTime map)
{
true == Pod.list.eachWhile |p|
{
if (podFile(p).modified > map[p])
{
log.debug("$p.name pod has been modified")
return true
}
return null
}
}
** Start DraftMod process.
private Void startProc()
{
home := Env.cur.homeDir.osPath
args := ["java", "-cp", "${home}/lib/java/sys.jar", "-Dfan.home=$home",
"fanx.tools.Fan", "draft", "-port", "$port", "-prod"]
if (props != null) args.addAll(["-props", props.osPath])
args.add(type.qname)
proc := Process(args).run
Actor.locals["proc"] = proc
log.debug("Start external process")
}
** Stop DraftMod process.
private Void stopProc()
{
proc := Actor.locals["proc"] as Process
if (proc == null) return
proc.kill
log.debug("Stop external process")
}
const Type type
const Int port
const File? props
const Log log := Log.get("draft")
}
//////////////////////////////////////////////////////////////////////////
// DevMod
//////////////////////////////////////////////////////////////////////////
** DevMod
const class DevMod : WebMod
{
** Constructor.
new make(DevRestarter r)
{
this.restarter = r
this.port = r.port
}
** Target port to proxy requests to.
const Int port
** DevRestarter instance.
const DevRestarter restarter
override Void onService()
{
// check pods
restarter.checkPods
// 13-Jan-2013
// Safari seems to have trouble creating seesion cookie
// with proxy server - create session here as a workaround
dummy := req.session
// proxy request
c := WebClient()
c.followRedirects = false
c.reqUri = `http://localhost:${port}${req.uri.relToAuth}`
c.reqMethod = req.method
req.headers.each |v,k|
{
// TODO FIXIT: 4-Jun-2014
// WebClient does not support sending multiple Set-Cookie headers
// so can break applications behind the proxy. This is likley the
// same problem as the above Safari hack.
if (k == "Host") return
c.reqHeaders[k] = v
}
c.writeReq
is100Continue := c.reqHeaders["Expect"] == "100-continue"
if (req.method == "POST" && ! is100Continue)
c.reqOut.writeBuf(req.in.readAllBuf).flush
// proxy response
c.readRes
if (is100Continue && c.resCode == 100)
{
c.reqOut.writeBuf(req.in.readAllBuf).flush
c.readRes // final response after the 100continue
}
res.statusCode = c.resCode
c.resHeaders.each |v,k|
{
// we don't re-gzip responses
if (k == "Content-Encoding" && v == "gzip") return
res.headers[k] = v
}
if (c.resHeaders["Content-Type"] != null ||
c.resHeaders["Content-Length"] != null)
res.out.writeBuf(c.resIn.readAllBuf).flush
c.close
}
}