Golang和本地网络界面的下载进度?

I am new to Go and trying to make a cross-browser app which can download multiple url's with progress bar. The Grab package does the job nicely as shown in the example below. Now, I want to have a self-contained/portable/single-executable Web-UI which can display the download progress from the below code in a web-browser?

package main

import (
    "fmt"
    "github.com/cavaliercoder/grab"
    "os"
    "time"
)

func main() {
    // get URL to download from command args
    if len(os.Args) < 2 {
        fmt.Fprintf(os.Stderr, "usage: %s url [url]...
", os.Args[0])
        os.Exit(1)
    }

    urls := os.Args[1:]

    // start file downloads, 3 at a time
    fmt.Printf("Downloading %d files...
", len(urls))
    respch, err := grab.GetBatch(3, ".", urls...)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v
", err)
        os.Exit(1)
    }

    // start a ticker to update progress every 200ms
    t := time.NewTicker(200 * time.Millisecond)

    // monitor downloads
    completed := 0
    inProgress := 0
    responses := make([]*grab.Response, 0)
    for completed < len(urls) {
        select {
        case resp := <-respch:
            // a new response has been received and has started downloading
            // (nil is received once, when the channel is closed by grab)
            if resp != nil {
                responses = append(responses, resp)
            }

        case <-t.C:
            // clear lines
            if inProgress > 0 {
                fmt.Printf("\033[%dA\033[K", inProgress)
            }

            // update completed downloads
            for i, resp := range responses {
                if resp != nil && resp.IsComplete() {
                    // print final result
                    if resp.Error != nil {
                        fmt.Fprintf(os.Stderr, "Error downloading %s: %v
", resp.Request.URL(), resp.Error)
                    } else {
                        fmt.Printf("Finished %s %d / %d bytes (%d%%)
", resp.Filename, resp.BytesTransferred(), resp.Size, int(100*resp.Progress()))
                    }

                    // mark completed
                    responses[i] = nil
                    completed++
                }
            }

            // update downloads in progress
            inProgress = 0
            for _, resp := range responses {
                if resp != nil {
                    inProgress++
                    fmt.Printf("Downloading %s %d / %d bytes (%d%%)\033[K
", resp.Filename, resp.BytesTransferred(), resp.Size, int(100*resp.Progress()))
                }
            }
        }
    }

    t.Stop()

    fmt.Printf("%d files successfully downloaded.
", len(urls))
}

One kind of solution would be to use websocket, because to establish a connection it uses a special kind of header that reduces the number of handshakes required between browser and server to only one, which means the client server communication will not block.

This connection will remain active throughout its lifetime, and you can use JavaScript to write or read data from this connection, as in the case of a conventional TCP sockets.

As a matter of implementation you can encode the resulting information composed by grab package as a json string then you can transmit this string with websocket.JSON.Send.

func (cd Codec) Send(ws *Conn, v interface{}) (err error)

And on the client part you can obtain the composed json object which later you can parse and use as you wish, depending on your purpose of use and the technology prefered (canvas, DOM etc.).

Below is a snippet of websocket server side:

package main

import (
    "fmt"
    "golang.org/x/net/websocket"
    "net/http"
)

type FileInformation struct {
    BytesTransferred int
    Size             string
    Filename         string
    Progress         int
}

func Echo(ws *websocket.Conn) {
    info := FileInformation{
        BytesTransferred: 70,
        Size:             "1.7MB",
        Filename:         "test.txt",
        Progress:         60,
    }

    for {
        if err := websocket.JSON.Send(ws, info); err != nil {
            fmt.Println("Error sending message")
            break
        }
        // if BytesTransferred == 100 break
    }
}

func main() {
    http.Handle("/", websocket.Handler(Echo))

    if err := http.ListenAndServe(":1234", nil); err != nil {
        fmt.Println("Cannot iniatiate a websocket connection")
    }
}

Of course the values are hard coded and you can use a timer tick if you wish to alter the transfer speed.

The client side written in go:

package main

import (
    "fmt"
    "golang.org/x/net/websocket"
    "io"
    "os"
)

func main() {
    type FileInformation struct {
        BytesTransferred int
        Size             string
        Filename         string
        Progress         int
    }

    if len(os.Args) > 1 && (os.Args[1] == "--help" || os.Args[1] == "-h") {
        fmt.Println("Usage : " + os.Args[0] + " ws://localhost:port")
        os.Exit(1)
    }

    conn, err := websocket.Dial(os.Args[1], "", "http://127.0.0.1")
    checkError(err)
    var info FileInformation

    for {
        err := websocket.JSON.Receive(conn, &info)
        if err != nil {
            if err == io.EOF {
                break
            }
            fmt.Println("Couldn't receive msg " + err.Error())
            break
        }
        fmt.Printf("%s", info)

        if err := websocket.JSON.Send(conn, info); err != nil {
            checkError(err)
        }
    }
}

func checkError(err error) {
    if err != nil {
        fmt.Println("Fatal error: " + err.Error())
        os.Exit(1)
    }
}

In javascript you can connect to websocket and receive the data as:

<script type="text/javascript">
    var sock = null;
    var wsuri = "ws://127.0.0.1:1234";

    window.onload = function() {

        console.log("onload");

        sock = new WebSocket(wsuri);

        sock.onopen = function() {
            console.log("connected to " + wsuri);
        }

        sock.onclose = function(e) {
            console.log("connection closed (" + e.code + ")");
        }

        sock.onmessage = function(e) {
            console.log("message received: " + e.data);
            // deserialize json data
            var json = JSON.parse(e.data);
        }
    };

    function send() {
        var msg = document.getElementById('message').value;
        sock.send(msg);
    };
</script>