I'm writing an http server with self-update feature, the steps are:
- client post to the server with the new executable, as the http body
- server replace itself with the bytes
- server echos a string "ok" to the client
- server exit process
- server will be restarted by systemd service
The problem is when the server calls os.Exit(), the client will receive EOF instead of "ok", my code looks like:
Server:
package main
import (
"io"
"net/http"
"os"
)
func replace_self_executable(executable_bytes []byte) {
// ...
}
func handle_self_update(w http.ResponseWriter, r *http.Request) {
executable_bytes, _ := io.ReadAll(r.Body)
replace_self_executable(executable_bytes)
w.Write([]byte(`ok`))
os.Exit(0)
// other stuff
}
func main() {
http.HandleFunc("/self_update", handle_self_update)
http.ListenAndServe(":123", nil)
}
Client:
package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
new_server_binary := []byte{ /*...*/ }
resp, err := http.Post(`http://localhost:123/self_update`, ``, bytes.NewReader(new_server_binary))
if err != nil {
panic(err)
}
result, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(`result`, string(result))
}
I also tried flushing and close client first, seems no difference:
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
r.Body.Close()
How to send the "ok" to client?
Signal (e.g. via channel) to a routine outside the handler so that you can return from the request handler and gracefully shutdown the server:
On an unrelated note - be very very very very careful with your implementation. Allowing a client to post you a new binary which you'll run on the server is a gigantic security hole and should have layers of authentication, signature verification, checksum validation, etc. Or better yet, let the client signal that an update is available, and have the server reach out to a known-good source for the updated binary (and still confirm signature & checksum).