Skip to content

Instantly share code, notes, and snippets.

@xkr47
Last active December 7, 2015 10:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xkr47/ade036c2da4fdb271268 to your computer and use it in GitHub Desktop.
Save xkr47/ade036c2da4fdb271268 to your computer and use it in GitHub Desktop.
Automatically shut down previously running app instance on startup
import ceylon.file {
Path,
Nil,
File,
createFileIfNil,
parsePath
}
import ceylon.logging {
Logger
}
import ceylon.net.http.client {
CliRequest=Request,
CliResponse=Response
}
import ceylon.net.http.server {
Request,
Response
}
import ceylon.net.uri {
Uri,
parseUri=parse,
Query,
Parameter
}
import com.github.bjansen.gyokuro.core {
get
}
import com.vasileff.ceylon.random.api {
platformRandom
}
import java.net {
ConnectException
}
Logger log = ...;
"""Call before app.run() to automatically shutdown previously running instance of app, if any.
Example:
value app = Application { ... };
autoShutdownPrevious(app);
app.run();
The function registers a handler at the given url (default "/shutdown") and if there is a legitimate request, calls the given
stop function.
At startup, it tries to send a GET request (before the server starts listening to the port itself, e.g. `app.start()`) to the
same uri to request the previously running app to shut down.
To make sure no attacker shuts down your app, a random secret is stored in a file on the server, and is required as a parameter
in the call to the shutdown uri.
"""
void autoShutdownPrevious(
"The application instance"
Application app,
"The function to call when a shutdown is requested. By default, shuts down the application."
void stop() => app.stop(),
"The uri at which to put the service handler for shutdown requests."
String shutdownUriPath = "/shutdown",
"The path to the file where to store the secret string required to request a shutdown"
Path shutdownSecretFilePath = parsePath("app.shutdown.secret.txt")
) {
"shutdownSecretPath must refer to a file or not exist"
assert (is File|Nil shutdownSecretFile = shutdownSecretFilePath.resource);
void sendShutdownRequestToOldApp(String shutdownPath, String? oldSecret) {
Uri uri1 = parseUri("http://localhost:" + app.port.string + shutdownPath);
Uri uri = Uri(uri1.scheme, uri1.authority, uri1.path, Query(Parameter("secret", oldSecret)));
while (true) {
CliRequest request = uri.get();
CliResponse response;
try {
response = request.execute();
} catch (ConnectException e) {
log.info("Not running");
return;
} catch (Exception e) {
if (e.message == "Premature EOF") {
log.info("Successfully requested currently running app to shut down");
return;
}
throw e;
}
if (response.status == 200) {
log.info("Successfully requested currently running app to shut down, still running");
// loop to ensure shutdown has completed
} else {
log.warn("Attempt to shut down currently running app failed");
throw Exception("Failed to shut down app");
}
}
}
variable String? oldSecret = null;
if (is File file = shutdownSecretFile) {
try(r = file.Reader()) {
oldSecret = r.readLine();
}
}
sendShutdownRequestToOldApp(shutdownUriPath, oldSecret);
value newSecret = { for (b in platformRandom().bytes().take(16)) formatInteger(256 + b.unsigned, 16).rest }.fold("")(plus<String>);
try (w = createFileIfNil(shutdownSecretFile).Overwriter()) { w.writeLine(newSecret); }
void shutdownRequestHandler(Request req, Response res) {
if (exists secret = req.parameter("secret"), secret == newSecret) {
log.info("Received successful request to shut down this app");
stop();
} else {
log.info("Received broken request to shut down this app, ignoring");
res.responseStatus = 400;
}
}
get(shutdownUriPath, shutdownRequestHandler);
}
@xkr47
Copy link
Author

xkr47 commented Dec 7, 2015

I wanted to put a sleep at the end of the while(true) loop but couldn't figure that one out yet..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment