2013-06-29 3 views
5

私は、インターネットからWebページを取得し、次のF#のプログラムを持っている:WebExceptionsを正しく処理するには?

open System.Net 

[<EntryPoint>] 
let main argv = 
    let mutable pageData : byte[] = [| |] 
    let fullURI = "http://www.badaddress.xyz" 
    let wc = new WebClient() 
    try 
     pageData <- wc.DownloadData(fullURI) 
     () 
    with 
    | :? System.Net.WebException as err -> printfn "Web error: \n%s" err.Message 
    | exn -> printfn "Unknown exception:\n%s" exn.Message 

    0 // return an integer exit code 

これは罰金を作品URIが有効ある場合、マシンがインターネットに接続を持っているWebサーバが適切に応答します理想的な関数プログラミングの世界では、関数の結果は引数として渡されない外部変数(副作用)に依存しません。私が知りたいのですがどのような

回復可能外部のエラーに対処するための機能が必要な場合があります操作に対処するための適切なF#のデザインパターンは何かということです。たとえば、ウェブサイトがダウンしている場合は、5分待ってからもう一度やり直してください。再試行回数とリトライ間の遅延回数を明示的に渡すべきか、これらの変数を関数に埋め込むのは問題ないのでしょうか? F#ので

答えて

6

、あなたはほぼ普遍optionChoice<_,_>タイプを使用したい回復可能エラーを処理します。実際には、Choiceは、エラーのいくつかの情報を返すことを許可しますが、optionはエラーではありません。言い換えれば、optionが問題でないときに最も良いです。howまたはなぜ何かが失敗した(失敗しただけです)。 Choice<_,_>は、についての情報がある場合に使用されますまたはなぜ何かが失敗したことが重要です。たとえば、エラー情報をログに書き込むことができます。またはに基づいて異なる状況を異なる方法で処理したい場合は、何かが失敗しました。これは、ユーザーが問題を診断するのに役立つ正確なエラーメッセージを提供する大きなケースです。念頭に置いて

は、ここで私はきれいで、機能的なスタイルで障害を処理するようにコードをリファクタリングしたい方法は次のとおりです。

open System 
open System.Net 

/// Retrieves the content at the given URI. 
let retrievePage (client : WebClient) (uri : Uri) = 
    // Preconditions 
    checkNonNull "uri" uri 
    if not <| uri.IsAbsoluteUri then 
     invalidArg "uri" "The URI must be an absolute URI." 

    try 
     // If the data is retrieved successfully, return it. 
     client.DownloadData uri 
     |> Choice1Of2 
    with 
    | :? System.Net.WebException as webExn -> 
     // Return the URI and WebException so they can be used to diagnose the problem. 
     Choice2Of2 (uri, webExn) 
    | _ -> 
     // Reraise any other exceptions -- we don't want to handle them here. 
     reraise() 

/// Retrieves the content at the given URI. 
/// If a WebException is raised when retrieving the content, the request 
/// will be retried up to a specified number of times. 
let rec retrievePageRetry (retryWaitTime : TimeSpan) remainingRetries (client : WebClient) (uri : Uri) = 
    // Preconditions 
    checkNonNull "uri" uri 
    if not <| uri.IsAbsoluteUri then 
     invalidArg "uri" "The URI must be an absolute URI." 
    elif remainingRetries = 0u then 
     invalidArg "remainingRetries" "The number of retries must be greater than zero (0)." 

    // Try to retrieve the page. 
    match retrievePage client uri with 
    | Choice1Of2 _ as result -> 
     // Successfully retrieved the page. Return the result. 
     result 
    | Choice2Of2 _ as error -> 
     // Decrement the number of retries. 
     let retries = remainingRetries - 1u 

     // If there are no retries left, return the error along with the URI 
     // for diagnostic purposes; otherwise, wait a bit and try again. 
     if retries = 0u then error 
     else 
      // NOTE : If this is modified to use 'async', you MUST 
      // change this to use 'Async.Sleep' here instead! 
      System.Threading.Thread.Sleep retryWaitTime 

      // Try retrieving the page again. 
      retrievePageRetry retryWaitTime retries client uri 

[<EntryPoint>] 
let main argv = 
    /// WebClient used for retrieving content. 
    use wc = new WebClient() 

    /// The amount of time to wait before re-attempting to fetch a page. 
    let retryWaitTime = TimeSpan.FromSeconds 2.0 

    /// The maximum number of times we'll try to fetch each page. 
    let maxPageRetries = 3u 

    /// The URI to fetch. 
    let fullURI = Uri ("http://www.badaddress.xyz", UriKind.Absolute) 

    // Fetch the page data. 
    match retrievePageRetry retryWaitTime maxPageRetries wc fullURI with 
    | Choice1Of2 pageData -> 
     printfn "Retrieved %u bytes from: %O" (Array.length pageData) fullURI 

     0 // Success 
    | Choice2Of2 (uri, error) -> 
     printfn "Unable to retrieve the content from: %O" uri 
     printfn "HTTP Status: (%i) %O" (int error.Status) error.Status 
     printfn "Message: %s" error.Message 

     1 // Failure 

は基本的に、私は2つの機能にコードを分割、プラスオリジナルmain

  • 指定されたURIからコンテンツを取得しようとする機能の1つ。
  • 試行を再試行するロジックを含む1つの関数。これは、実際の要求を実行する最初の関数を「ラップ」します。
  • 元のメイン機能では、「設定」(app.configまたはweb.configから簡単に取得できます)と最終結果の印刷のみが処理されるようになりました。言い換えれば、再試行のロジックが気になることはありません。matchステートメントでコードの1行を修正し、必要であればリトライしないリクエスト関数を使用することができます。

あなたが複数のURIからコンテンツをプルAND(例えば、5分)の再試行の間にかなりの時間を待つしたい場合は、Thread.Sleepを使用するのではなく、プライオリティキューか何かを使用するように再試行ロジックを変更する必要がありますかAsync.Sleep

恥知らずのプラグイン:私のExtCoreライブラリには、このようなものをビルドするときに、特にすべての非同期化をしたい場合は、人生を大幅に楽にするためのいくつかのことが含まれています。最も重要なのは、asyncChoiceワークフローとcollections functions designed to work with itです。

(リトライタイムアウトやリトライ回数などの)パラメータを渡すことについてのあなたの質問については、それらを渡すかハードコーディングするかを決定するための難易度の高いルールはないと思います関数。ほとんどの場合、私はそれらを渡すことを好みますが、いくつかのパラメータを渡す必要がある場合は、それらをすべて保持して代わりに渡すようにレコードを作成する方がよいでしょう。私が使ったもう1つのアプローチは、デフォルトのファイルをコンフィギュレーションファイルから取得する場合の値optionの値を作成することです(ファイルから一度だけから取得し、再解析を避けるためにプライベートフィールドに割り当てます)あなたの関数が呼び出されるたびに設定ファイル)。これにより、コードで使用したデフォルト値を簡単に変更できますが、必要に応じてそれらをオーバーライドする柔軟性も得られます。

関連する問題