2017-10-17 2 views
4

httpリクエストで利用可能なさまざまなタイムアウトを読んでいて、リクエストの合計時間でハードデッドラインとして機能するようです。httpダウンロードでインアクティビティタイムアウトを実装するにはどうすればいいですか

私はhttpダウンロードを実行していますが、私は自分のユーザー接続について何も知らないし、低速接続でタイムアウトしたくないため、最初のハンドシェイクを超えてハードタイムアウトを実装したくありません。私が理想的に思っているのは、何も何もダウンロードされていない一定期間の無活動の後にタイムアウトすることです。組み込みとしてこれを行う方法はありますか、ファイルの記述に基づいて中断する必要がありますか?

作業コードは分離するのが少し難しいですが、これらは関連する部分だと思っていますが、進捗を提供する別のループがありますが、これを使用してダウンロードを中断するために少しリファクタリングする必要があります:

このへの入力を持っていたすべての人に
// 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秒間変化しなければ名前付きエラーが発生しました。既存のエラー処理を使って指定されたエラーを見つけ出し、そこからダウンロードを再試行するほうがはるかに簡単でした。

バックグラウンドで私のアプローチでクラッシュする可能性がありますが(これは後でシグナリングで修正できます)、これは短時間実行するアプリケーション(インストーラ)なので、これは許容されます(少なくとも耐えられる)

+2

'io.Copy'を自分で書いたもので置き換えることができます。個々の' Read'コールのタイムアウトを設定し、何かをチャンネルなどに書き込むことでこれまでのコピーデータ量を通知することもできます。 – Krom

+0

それは悪い解決策ではなく、私が計画していたものよりもきれいだと感じています - 提案に感謝 – WebweaverD

+0

'io.Copy'を置き換える方がはるかに複雑であると考えています。素敵なプロパティ:[関連性があるかもしれません](https://groups.google。com/d/msg/golang-nuts/434c3YInH_M/3Qd7C0uDUqQJ) – Krom

答えて

2

コピーを手動で行うことは特に難しくありません。あなたがそれを正しく実装する方法がわからない場合は、あなたのニーズに合わせてコピーして修正する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を再利用する方が効率的です。

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関数の戻り値として、正しいリセットパターンを使用するように注意しながら、意味しますとにかくキャンセルするには十分に近いですが、このコードが将来拡張される場合には、コードを慣用的にすることはしばしば役に立ちます。

+0

@JimBの例をありがとう、私はいくつかのアイデアと劇を持って戻って報告するつもりです。これは仕事をするだろうと私はよくこのようなものを実装することは間違いないが、私は私の現在のコードのコンテキストでクリーナーであると思っている私はちょうど私の既存のファイルのstatループを使用して非アクティブを検出し、私はそれがどう行動するかを見なければならないでしょう。提案にもう一度感謝します。今投票では、私はおそらく受け入れ、私の最終的な解決策で質問を更新します。 – WebweaverD

関連する問題