Преобразование HTTP-запроса Polly в F#

Я делаю HTTP-запрос с Полли. Я хочу повторить попытку после ожидания 1 секунды на каждом прокси в массиве.
Как я могу сделать это лучше?
Как я могу сделать это в F#?

public static Result RequestWithRetry(string url, string[] proxies, string username, string password)
{
    if (proxies == null) throw new ArgumentNullException("null proxies array");
    var client = new WebClient { Credentials = new NetworkCredential(username, password) };
    var result = String.Empty;
    var proxyIndex = 0;

    var policy = Policy
            .Handle<Exception>()
            .WaitAndRetry(new[]
                {
                    TimeSpan.FromSeconds(1)
                }, (exception, timeSpan) => proxyIndex++);

    policy.Execute(() =>
    {                 
        if (proxyIndex >= proxies?.Length) throw new Exception($"Exhausted proxies: {String.Join(", ", proxies)}");

        client.Proxy = new WebProxy(proxies?[proxyIndex]) { UseDefaultCredentials = true };
        result = client.DownloadString(new Uri(url));
    });

    return new Result(value: result, proxy: proxies[proxyIndex]);
}

person Brett Rowberry    schedule 12.07.2018    source источник


Ответы (2)


Вы можете попробовать более функциональный способ с Result<'TOk,'TError> и Async<T>

open System.Net
open System

type Request =
    { Url      : string
      Proxies  : string list
      UserName : string
      Password : string }

let requestWithRetry request =
    let client = 
        new WebClient (
            Credentials = new NetworkCredential(
                request.UserName,
                request.Password))
    let uri = Uri request.Url
    let rec retry = function
        | [] -> Error "Exhausted proxies" |> async.Return
        | (proxy:string)::rest -> async {
            try 
                do client.Proxy <- new WebProxy(proxy, UseDefaultCredentials = true)
                let! response = client.AsyncDownloadString uri
                return Ok (response, proxy)
            with _ ->
                do! Async.Sleep 1000
                return! retry rest
        }
    retry request.Proxies
person Szer    schedule 13.07.2018
comment
Мне все нравится в твоем ответе. Отличная работа! К сожалению, AsyncDownloadString не попал в .NET Core. - person Brett Rowberry; 13.07.2018
comment
Я думаю, что let! response = client.AsyncDownloadString uri эквивалентен let! response = { return client.DownloadString uri } Только второй работает с .NET Core. - person Brett Rowberry; 20.07.2018
comment
@BrettRowberry, это не то же самое. Не делайте этого :D Это может заблокировать каждый поток в ThreadPool вместо их повторного использования! Вы можете использовать взаимодействие с IEvent - Async.AwaitEvent. Или просто Async.AwaitTask - person Szer; 20.07.2018

Мне удалось собрать этот перевод. Мне это не очень нравится, хотя в процессе я многое узнал о F# и Action.

type Result = { Value: string; Proxy: string }

let request (proxies:string[]) (username:string) (password:string) (url:string) : Result =                   
    if (proxies = null) then raise <| new ArgumentNullException()

    use client = new WebClient()
    client.Credentials <- NetworkCredential(username, password)
    let mutable result = String.Empty;
    let mutable proxyIndex = 0;

    let policy =
        Policy
            .Handle<Exception>()
            .WaitAndRetry(
                sleepDurations = [| TimeSpan.FromSeconds(1.0) |], 
                onRetry = Action<Exception, TimeSpan>(fun _ _ -> proxyIndex <- proxyIndex + 1)
                )

    let makeCall () =
        if (proxyIndex >= proxies.Length) 
        then failwith ("Exhausted proxies: " + String.Join(", ", proxies))
        else
            let proxy = WebProxy(proxies.[proxyIndex])
            proxy.UseDefaultCredentials <- true
            client.Proxy <- proxy
            result <- client.DownloadString(new Uri(url));

    policy.Execute(Action makeCall)

    { Result.Value = result; Proxy = proxies.[proxyIndex]}
person Brett Rowberry    schedule 13.07.2018