草庐IT

用于下载进度的 Golang 和本地 Web 界面?

coder 2024-07-08 原文

我是 Go 的新手,正在尝试制作一个跨浏览器的应用程序,它可以下载多个带有进度条的 url。 Grab 包可以很好地完成这项工作,如下例所示。现在,我想要一个独立的/可移植的/单一可执行的网络用户界面,它可以在网络浏览器中显示来自以下代码的下载进度?

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]...\n", os.Args[0])
        os.Exit(1)
    }

    urls := os.Args[1:]

    // start file downloads, 3 at a time
    fmt.Printf("Downloading %d files...\n", len(urls))
    respch, err := grab.GetBatch(3, ".", urls...)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", 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\n", resp.Request.URL(), resp.Error)
                    } else {
                        fmt.Printf("Finished %s %d / %d bytes (%d%%)\n", 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\n", resp.Filename, resp.BytesTransferred(), resp.Size, int(100*resp.Progress()))
                }
            }
        }
    }

    t.Stop()

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

最佳答案

一种解决方案是使用 websocket,因为要建立连接,它会使用一种特殊的 header ,将浏览器和服务器之间所需的握手次数减少到只有一次,这意味着客户端服务器通信不会阻塞。

此连接将在其整个生命周期内保持事件状态,您可以使用 JavaScript 在此连接中写入或读取数据,就像传统的 TCP 套接字一样。

作为一个实现问题,您可以将由 grab 包组成的结果信息编码为 json 字符串,然后您可以使用 websocket.JSON.Send .

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

在客户端,您可以获得组合的 json 对象,稍后您可以根据您的使用目的和首选技术(canvas、DOM 等)解析和使用该对象。

下面是 websocket 服务器端的片段:

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")
    }
}

当然,这些值是硬编码的,如果您想改变传输速度,您可以使用计时器滴答。

用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)
    }
}

在 javascript 中,您可以连接到 websocket 并接收数据:

<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>

关于用于下载进度的 Golang 和本地 Web 界面?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36217363/

有关用于下载进度的 Golang 和本地 Web 界面?的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  3. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  4. ruby - 安装 Ruby 时遇到问题(无法下载资源 "readline--patch") - 2

    当我尝试安装Ruby时遇到此错误。我试过查看this和this但无济于事➜~brewinstallrubyWarning:YouareusingOSX10.12.Wedonotprovidesupportforthispre-releaseversion.Youmayencounterbuildfailuresorotherbreakages.Pleasecreatepull-requestsinsteadoffilingissues.==>Installingdependenciesforruby:readline,libyaml,makedepend==>Installingrub

  5. ruby - inverse_of 是否适用于 has_many? - 2

    当我使用has_one时,它​​工作得很好,但在has_many上却不行。在这里您可以看到object_id不同,因为它运行了另一个SQL来再次获取它。ruby-1.9.2-p290:001>e=Employee.create(name:'rafael',active:false)ruby-1.9.2-p290:002>b=Badge.create(number:1,employee:e)ruby-1.9.2-p290:003>a=Address.create(street:"123MarketSt",city:"SanDiego",employee:e)ruby-1.9.2-p290

  6. ruby-on-rails - 有没有办法为 CarrierWave/Fog 设置上传进度指示器? - 2

    我在Rails应用程序中使用CarrierWave/Fog将视频上传到AmazonS3。有没有办法判断上传的进度,让我可以显示上传进度如何? 最佳答案 CarrierWave和Fog本身没有这种功能;你需要一个前端uploader来显示进度。当我不得不解决这个问题时,我使用了jQueryfileupload因为我的堆栈中已经有jQuery。甚至还有apostonCarrierWaveintegration因此您只需按照那里的说明操作即可获得适用于您的应用的进度条。 关于ruby-on-r

  7. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  8. ruby - "undefined method"用于 rails 模型 - 2

    我正在使用带有Rails的Devise,我想添加一个方法“getAllComments”,所以我这样写:classUser在我的Controller中:defdashboard@user=current_user@comments=@user.getAllComments();end当我访问我的url时,我得到了undefinedmethod`getAllComments'for#我做错了什么?谢谢 最佳答案 因为getAllComments是一个类方法,而您正试图将其作为实例方法访问。您要么需要访问它:User.getAllCom

  9. Ruby on Rails regexp equals-tilde 与 array include 用于检查选项列表 - 2

    我正在使用Rails3.2.3和Ruby1.9.3p0。我发现我经常需要确定某个字符串是否出现在选项列表中。看来我可以使用Ruby数组.includemethod:或正则表达式equals-tildematchshorthand用竖线分隔选项:就性能而言,一个比另一个好吗?还有更好的方法吗? 最佳答案 总结:Array#include?包含String元素,在接受和拒绝输入时均胜出,对于您的示例只有三个可接受的值。对于要检查的更大的集合,看起来Set#include?和String元素可能会获胜。如何测试我们应该根据经验对此进行测试

  10. ruby - 如何配置 Ruby Mechanize 代理以通过 Charles Web 代理工作? - 2

    我正在使用Ruby/Mechanize编写一个“自动填写表格”应用程序。它几乎可以工作。我可以使用精彩CharlesWeb代理以查看服务器和我的Firefox浏览器之间的交换。现在我想使用Charles查看服务器和我的应用程序之间的交换。Charles在端口8888上代理。假设服务器位于https://my.host.com。.一件不起作用的事情是:@agent||=Mechanize.newdo|agent|agent.set_proxy("my.host.com",8888)end这会导致Net::HTTP::Persistent::Error:...lib/net/http/pe

随机推荐