Last active
December 7, 2015 10:07
-
-
Save xkr47/ade036c2da4fdb271268 to your computer and use it in GitHub Desktop.
Automatically shut down previously running app instance on startup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wanted to put a sleep at the end of the while(true) loop but couldn't figure that one out yet..