私は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秒間変更されなかったときに名前付きエラーをスローしました。名前付きエラーを既存のエラー処理で拾い、そこからダウンロードを再試行する方がはるかに簡単でした。
私はおそらく私のアプローチでバックグラウンドで少なくとも1つのgoroutineをクラッシュさせます(後でこれをいくつかのシグナリングで修正する可能性があります)が、これは実行時間の短いアプリケーション(そのインストーラー)であるため、これは許容可能です(少なくとも許容可能)。
手動でコピーを行うことは特に難しくありません。適切に実装する方法がわからない場合は、ioパッケージから数十行でコピーして、ニーズに合わせて変更します(ErrShortWrite
stdライブラリのio.Writerの実装が正しいと想定できるため、この句は削除しました))
これは、コピーコンテキストのような関数で、キャンセルコンテキストとアイドルタイムアウトパラメーターも必要です。読み取りが成功するたびに、キャンセルゴルーチンに続行して新しいタイマーを開始するように通知します。
func idleTimeoutCopy(dst io.Writer, src io.Reader, timeout time.Duration,
ctx context.Context, cancel context.CancelFunc) (written int64, err error) {
read := make(chan int)
go func() {
for {
select {
case <-ctx.Done():
return
case <-time.After(timeout):
cancel()
case <-read:
}
}
}()
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
read <- nr
nw, ew := dst.Write(buf[0:nr])
written += int64(nw)
if ew != nil {
err = ew
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
time.After
簡潔にするために使用しましたが、を再利用する方が効率的Timer
です。これは、Reset
関数の戻り値が壊れているため、正しいリセットパターンを使用するように注意することを意味します。
t := time.NewTimer(timeout)
for {
select {
case <-ctx.Done():
return
case <-t.C:
cancel()
case <-read:
if !t.Stop() {
<-t.C
}
t.Reset(timeout)
}
}
Stop
ここでは、呼び出しを完全にスキップすることができます。私の意見では、Resetの呼び出し中にタイマーが発生した場合、とにかくキャンセルするのに十分近かったためですが、このコードが将来拡張される場合に備えて、コードを慣用的にすることはしばしば良いことです。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加