【发布时间】:2017-10-19 21:57:49
【问题描述】:
我一直在阅读 http 请求上可用的各种超时,它们似乎都是请求总时间的硬性截止日期。
我正在运行 http 下载,我不想在初始握手之后实施硬超时,因为我对我的用户连接一无所知,也不想在慢速连接上超时。理想情况下,我希望在一段时间不活动后超时(当 x 秒内没有下载任何内容时)。有什么办法可以作为内置程序执行此操作,还是我必须根据说明文件来中断?
工作代码有点难以隔离,但我认为这些是相关部分,还有另一个循环来统计文件以提供进度,但我需要进行一些重构以使用它来中断下载:
// httspClientOnNetInterface returns an http client using the named network interface, (via proxy if passed)
func HttpsClientOnNetInterface(interfaceIP []byte, httpsProxy *Proxy) (*http.Client, error) {
log.Printf("Got IP addr : %s\n", string(interfaceIP))
// create address for the dialer
tcpAddr := &net.TCPAddr{
IP: interfaceIP,
}
// create the dialer & transport
netDialer := net.Dialer{
LocalAddr: tcpAddr,
}
var proxyURL *url.URL
var err error
if httpsProxy != nil {
proxyURL, err = url.Parse(httpsProxy.String())
if err != nil {
return nil, fmt.Errorf("Error parsing proxy connection string: %s", err)
}
}
httpTransport := &http.Transport{
Dial: netDialer.Dial,
Proxy: http.ProxyURL(proxyURL),
}
httpClient := &http.Client{
Transport: httpTransport,
}
return httpClient, nil
}
/*
StartDownloadWithProgress will initiate a download from a remote url to a local file,
providing download progress information
*/
func StartDownloadWithProgress(interfaceIP []byte, httpsProxy *Proxy, srcURL, dstFilepath string) (*Download, error) {
// start an http client on the selected net interface
httpClient, err := HttpsClientOnNetInterface(interfaceIP, httpsProxy)
if err != nil {
return nil, err
}
// grab the header
headResp, err := httpClient.Head(srcURL)
if err != nil {
log.Printf("error on head request (download size): %s", err)
return nil, err
}
// pull out total size
size, err := strconv.Atoi(headResp.Header.Get("Content-Length"))
if err != nil {
headResp.Body.Close()
return nil, err
}
headResp.Body.Close()
errChan := make(chan error)
doneChan := make(chan struct{})
// spawn the download process
go func(httpClient *http.Client, srcURL, dstFilepath string, errChan chan error, doneChan chan struct{}) {
resp, err := httpClient.Get(srcURL)
if err != nil {
errChan <- err
return
}
defer resp.Body.Close()
// create the file
outFile, err := os.Create(dstFilepath)
if err != nil {
errChan <- err
return
}
defer outFile.Close()
log.Println("starting copy")
// copy to file as the response arrives
_, err = io.Copy(outFile, resp.Body)
// return err
if err != nil {
log.Printf("\n Download Copy Error: %s \n", err.Error())
errChan <- err
return
}
doneChan <- struct{}{}
return
}(httpClient, srcURL, dstFilepath, errChan, doneChan)
// return Download
return (&Download{
updateFrequency: time.Microsecond * 500,
total: size,
errRecieve: errChan,
doneRecieve: doneChan,
filepath: dstFilepath,
}).Start(), nil
}
更新 感谢所有为此提供意见的人。
我接受了 JimB 的回答,因为它似乎是一种完全可行的方法,比我选择的解决方案更通用(并且可能对在这里找到自己方式的人更有用)。
在我的例子中,我已经有一个循环监控文件大小,所以当它在 x 秒内没有改变时,我抛出了一个命名错误。通过我现有的错误处理并从那里重试下载,我更容易找到命名错误。
我可能会在后台使用我的方法使至少一个 goroutine 崩溃(稍后我可能会通过一些信号来解决此问题),但由于这是一个运行时间短的应用程序(它是一个安装程序),所以这是可以接受的(至少可以容忍)
【问题讨论】:
-
您可以将
io.Copy替换为您自己编写的内容,这会为每个Read调用设置超时,甚至通过将内容写入通道来通知您到目前为止复制的数据量左右。 -
这不是一个糟糕的解决方案,感觉比我计划的要干净 - 感谢您的建议
-
请注意,替换
io.Copy比人们想象的要复杂得多,甚至有竞争,同时获得最好的属性:might be relevant -
链接中的有趣讨论(并将其链接到下一个问题)。对此进行了更多研究,替换 io.Copy 肯定不是微不足道的。围绕统计监控重构我的一些代码来处理这个问题看起来要容易得多。我很惊讶这似乎没有作为标准库的一部分实现。不这样做可能是很好的技术原因,但我可以作为功能请求提交以查看反馈的内容,如果我这样做,将链接有问题。
-
注意您的方法:假设您在每次写入时都在对文件进行 fsync,您可能正在这样做,但效率不高。如果您不同步文件,则文件中的数据在关闭之前可能根本不会更改。