I built a Vapor 4 app that is currently deployed on a local Ubuntu 18 server VM, running behind NGINX and serving users without any issues.
Now I would like one of my web server routes to react to specific POSTs by executing a Bash command via Process
(this, in order to send messages to a dedicated Slack channel via slack-cli, a tool I already use for other purposes and that is already configured and working both on my development machine and on the Ubuntu server).
With the following code, everything's working as desired when I run my Vapor app on my local machine (i.e.: immediately after the POST to the route the expected message appears in the Slack channel):
// What follows is placed inside my dedicated app.post route, after checking the response is valid...
let slackCLIPath = "/home/linuxbrew/.linuxbrew/bin/" // This is the slack-cli path on the Linux VM; I swap it with "/opt/homebrew/bin/" when running the app on my local Mac
_ = runShellScript("\(slackCLIPath)slack chat send '\(myMessageComingFromThePOST)' '#myChannel'")
// ...
// runShellScript() called above is the dedicated function (coming from [this SO answer](https://stackoverflow.com/a/43014767/3765705) that executes the Shell process, and its code follows:
func runShellScript(_ cmd: String) -> String? {
let pipe = Pipe()
let process = Process()
process.launchPath = "/bin/sh"
process.arguments = ["-c", String(format:"%@", cmd)]
process.standardOutput = pipe
let fileHandle = pipe.fileHandleForReading
process.launch()
return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
}
My issue is that, when I deploy my app on the Ubuntu server, both in debug and production, the execution of the Shell process does not happen like it did on my Mac: I have no errors logged by Vapor when I POST to that route, but no messages appear in Slack, even if I wait a while!
But here's the tricky part: as soon as I stop my Vapor app on the server, all messages are sent to Slack at once.
After a lot of testing (which obviously included confirming that from the server was possible to post without delays to Slack by using the exact same command passed to NSTask
's Process
class in my Vapor app), it appears like the Bash command is not executed until my Vapor app quits.
Clearly I'm missing something on how to make Process
work "in realtime" with Vapor, and I'll be grateful for all the help I can get.
You need to wait until the task has finished. Looks like you're deadlocking yourself. This is how I run stuff on Linux:
// MARK: - Functions
@discardableResult
func shell(_ args: String..., returnStdOut: Bool = false, stdIn: Pipe? = nil) throws -> (Int32, Pipe) {
return try shell(args, returnStdOut: returnStdOut, stdIn: stdIn)
}
@discardableResult
func shell(_ args: [String], returnStdOut: Bool = false, stdIn: Pipe? = nil) throws -> (Int32, Pipe) {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/env")
task.arguments = args
let pipe = Pipe()
if returnStdOut {
task.standardOutput = pipe
}
if let stdIn = stdIn {
task.standardInput = stdIn
}
try task.run()
task.waitUntilExit()
return (task.terminationStatus, pipe)
}
extension Pipe {
func string() -> String? {
let data = self.fileHandleForReading.readDataToEndOfFile()
let result: String?
if let string = String(data: data, encoding: String.Encoding.utf8) {
result = string
} else {
result = nil
}
return result
}
}
Important lines being starting it with try task.run()
and waiting with task.waitUntilExit()