Before websockets we had polling. This literally means having the client periodically (every few seconds, or whatever time period makes sense for your application), make a request to the server to find out the status of the job.
An optimization many people use is "long" polling. This involves having the server accept the request, and internally to the server, check for changes, and sleep while there are none, until either a specific timeout is reached or the desired event occurs, which is then messaged back to the client.
If a timeout is reached, the connection is closed and the client needs to make another request. The server code would look something like the following, assume the functions do sensible things based on their names and signatures:
import (
"net/http"
"time"
)
func PollingHandler(w http.ResponseWriter, r *http.Request) {
jobID := getJobID(r)
for finish := 60; finish > 0; finish-- { // iterate for ~1 minute
status, err := checkStatus(jobID)
if err != nil {
writeError(w, err)
return
}
if status != nil {
writeStatus(w, status)
return
}
time.Sleep(time.Second) // sleep 1 second
}
writeNil(w) // specific response telling client to request again.
}
A better way to handle the timeout would be to use the context package and create a context with a timeout. That would look something like:
import (
"net/http"
"time"
"golang.org/x/net/context"
)
func PollingHandler(w http.ResponseWriter, r *http.Request) {
jobID := getJobID(r)
ctx := context.WithTimeout(context.Background(), time.Second * 60)
for {
select{
case <-ctx.Done():
writeNil(w)
default:
status, err := checkStatus(jobID)
if err != nil {
writeError(w, err)
return
}
if status != nil {
writeStatus(w, status)
return
}
time.Sleep(time.Second) // sleep 1 second
}
}
}
This second version is just going to return in a more reliable amount of time, especially in the case where checkStatus
may be a slower call.