Flush Output Incrementally To Browser

I’m new to Go and very inexperienced with it so forgive me if this is a simple matter. The code below should output to the browser the strings in whatever order they were returned from the goroutine one at a time rather than waiting for all to return and displayed at once. I originally tried this with a single channel to return a response and than display it which didn’t work, now I tried to do it with a second channel to output. Here is the code:

package main

import (
	"net/http"
	"fmt"
	"time"
)

var l = []string{
	"one",
	"two",
	"three",
	"four",
	"five",
	"six",
	"seven",
	"eight",
	"nine",
	"ten",
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		c := make(chan int)
		s := make(chan string)

		for i, _ := range l {
			go doSomething(i, c)
		}

		for i :=0; i < len(l); i++ {
			go func(j int, c chan string) {
				time.Sleep(1000*time.Millisecond)
				//fmt.Fprintf(w, l[j] + "\n")
				s <- l[j]+"\n"
			}(<-c, s)
		}

		for i :=0; i < len(l); i++ {
			f, _ := w.(http.Flusher)
			fmt.Fprint(w, <-s)
			f.Flush()
		}
	})

	fmt.Println("Listening on localhost:8080")
	http.ListenAndServe(":8000", nil)
}

func doSomething(i int, c chan int) {
	c <- i
}

If you can point me in the right direction I would greatly appreciate it.

Got it working finally, here is the working code (the comments are for my understanding…):

package main

import (
	"net/http"
	"fmt"
	"time"
	"math/rand"
)

var list = []string{
	"one",
	"two",
	"three",
	"four",
	"five",
	"six",
	"seven",
	"eight",
	"nine",
	"ten",
}

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// this header had no effect on results, kept it anyway
		w.Header().Set("Connection", "Keep-Alive")
		// these 2 headers were needed in order to get the http chunked incrementally
		w.Header().Set("Transfer-Encoding", "chunked")
		w.Header().Set("X-Content-Type-Options", "nosniff")

		// creating a channel for bi-directional communication
		c := make(chan int)

		// loop through and spawn off threads
		for i, _ := range list {
			go doSomething(i, c)
		}

		// used to flush html incrementally, without it the html won't
		// display until all threads have been executed
		f, _ := w.(http.Flusher)

		// join our threads back to the main func thread
		for i :=0; i < len(list); i++ {
			// create a time.Duration to multiply against milliseconds
			t := time.Duration(random(100, 499))
			// sleep to simulate waiting for a response
			// possibly from a remote service call etc...
			time.Sleep(t * time.Millisecond)
			// print value to browser
			fmt.Fprint(w, list[<-c] + "\n")
			// flush it to the browser as it's ready without waiting for
			// everything to finish executing
			f.Flush()
		}
	})

	// spawn off a server on port :8000
	fmt.Println("Listening on localhost:8000")
	http.ListenAndServe(":8000", nil)
}

// a function to convert into a go routine
func doSomething(i int, c chan int) {
	c <- i
}

// generate a random number given min/max values and returns an int
func random(min, max int) int {
	// create a seed to guarantee a unique value each time
	seed := rand.NewSource(time.Now().UnixNano())
	// pass our seed to our new rand generator
	r := rand.New(seed)
	// return a random value between min and max
	return r.Intn(max - min) + min
}

There is still an error in your code: you need to check that your ResponseWriter really is a Flusher. You are ignoring the most important value.

f, _ := w.(http.Flusher)

Should really be:

if f, ok := w.(http.Flusher); ok {
    f.Flush();
}

Always check errors or boolean last return values or your code will crash.

You’re absolutely right, I eliminated that at one point as I was testing different solutions. I’m only 2 weeks or less into Go and it’s not as easy for me to learn as other languages in the past. Thanks for the suggestion!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.