using concurrent::ActorPool
using concurrent::AtomicRef
using web::WebMod
using web::WebClient
using afIoc::Registry
using afIoc::RegistryMeta
using afIoc::RegistryShutdownErr
using afIocConfig::ConfigSource
** The `web::WebMod` that runs in [Wisp]`pod:wisp`.
const class BedSheetWebMod : WebMod {
private const static Log log := Utils.log
** Returns 'pod.dis' (or 'proj.name'if not found) from the application's pod meta, or the pod name if neither are defined.
const Str appName
** The port number this Bed App will be listening on.
const Int port
** The IoC registry.
const Registry registry
private const MiddlewarePipeline pipeline
private const AtomicRef podCheckerRef := AtomicRef(null)
** Creates this 'WebMod'. Use a 'BedSheetBuilder' to create the 'Registry' instance - it ensures all the options have been set.
new make(Registry registry) {
bedServer := (BedSheetServer) registry.activeScope.serviceById(BedSheetServer#.qname)
this.registry = registry
this.appName = bedServer.appName
this.port = bedServer.port
// BUGFIX: eager load the middleware pipeline, so we can use the ErrMiddleware
// otherwise Errs thrown when instantiating middleware end up in limbo
// Errs from the FileHandler ctor are a prime example
this.pipeline = registry.activeScope.serviceById(MiddlewarePipeline#.qname)
}
@NoDoc
override Void onService() {
req.mod = this
if (podCheckerRef.val != null) {
if (req.modRel == BsConstants.pingUrl) {
// web pages ping us to check if we've restarted yet
res.headers["Content-Type"] = MimeType("text/plain").toStr
res.out.print("OK").flush.close
return
}
if (appRequiresRestart) {
notifyClientOfRestart
restartApp
return
}
}
try {
registry.activeScope.createChild("httpRequest") {
// this is the actual call to BedSheet!
// the rest of this class is just startup and error handling fluff!
pipeline.service
}
} catch (RegistryShutdownErr err) {
// nothing we can do here
if (!res.isCommitted)
res.sendErr(500, "BedSheet shutting down...")
return
// theoretically, this should have already been dealt with by our ErrMiddleware...
// ...but it's handy for BedSheet development!
} catch (Err err) {
// try to send something to the browser
errLog := err.traceToStr
try {
errPrinter := (ErrPrinterStr) registry.activeScope.serviceById(ErrPrinterStr#.qname)
errLog = errPrinter.errToStr(err)
} catch {}
// log and throw, because we don't trust Wisp to log it
Env.cur.err.printLine(errLog)
if (!res.isCommitted)
res.sendErr(500, "${err.typeof} - ${err.msg}")
throw err
}
}
@NoDoc
override Void onStart() {
// start the destroyer!
meta := (RegistryMeta) registry.activeScope.serviceById(RegistryMeta#.qname)
beds := (BedSheetServer) registry.activeScope.serviceById(BedSheetServer#.qname)
if (meta.options[BsConstants.meta_watchdog] == true) {
pingPort := (Int) meta.options[BsConstants.meta_watchdogPort]
destroyer := (AppDestroyer) registry.activeScope.build(AppDestroyer#, [ActorPool(), pingPort])
destroyer.start
watchAllPods := meta.options[BsConstants.meta_watchAllPods]?.toStr?.toBool(false) ?: false
appPod := (Pod) meta.options[BsConstants.meta_appPod]
if (appPod.meta["pod.isScript"] == "true")
throw Err(BsLogMsgs.appRestarter_canNotProxyScripts(appPod.name))
podChecker := PodChecker(appPod.name, watchAllPods)
this.podCheckerRef.val = podChecker.initialise
}
// print BedSheet connection details
configSrc := (ConfigSource) registry.activeScope.serviceByType(ConfigSource#)
host := (Uri) configSrc.get(BedSheetConfigIds.host, Uri#)
ver := beds.appPod?.version
log.info(BsLogMsgs.bedSheetWebMod_started(appName, ver, host))
}
@NoDoc
override Void onStop() {
registry.shutdown
log.info(BsLogMsgs.bedSheetWebMod_stopping(appName))
}
private Bool appRequiresRestart() {
((PodChecker?) podCheckerRef.val)?.podsModifed ?: false
}
private Void notifyClientOfRestart() {
registry.activeScope.createChild("httpRequest") {
msg := ((PodChecker?) podCheckerRef.val)?.restartMsg ?: "Just because I feel like it."
meta := (RegistryMeta) it.serviceById(RegistryMeta#.qname)
pages := (BedSheetPages) it.serviceById(BedSheetPages#.qname)
appPod := (Pod) meta.options[BsConstants.meta_appPod]
text := pages.renderRestart(appName, appPod.version, msg)
buf := text.toBuf
res.statusCode = 503
res.headers["Content-Type"] = text.contentType.toStr
res.headers["Content-Length"] = buf.size.toStr
res.out.writeBuf(buf).flush.close
}
}
private Void restartApp() {
meta := (RegistryMeta) registry.activeScope.serviceById(RegistryMeta#.qname)
dogPort := (Int) meta.options[BsConstants.meta_watchdogPort]
try {
resBody := WebClient() {
reqUri = "http://localhost:${dogPort}${BsConstants.restartUrl}".toUri
}.getStr.trim
if (resBody != "OK")
throw IOErr("Watchdog not OK: $resBody")
} catch (Err err) {
log.warn("Could not restart App: $err.msg")
}
}
}