C# RestSharp在重试时将IRestResponse.StatusCode==0返回给Polly

C# RestSharp在重试时将IRestResponse.StatusCode==0返回给Polly,c#,restsharp,polly,C#,Restsharp,Polly,我在RestSharp客户机周围编写了一个相当简单的包装器,使用Polly为其添加了一些重试逻辑。 我设置了Fiddler来测试它,并模拟了一些“糟糕”的响应 我遇到的问题是onRetry委托中的result.result.StatusCode位有时似乎记录为0,而不是实际的错误状态代码(在我的一些测试中为502) 然而,通过我的单元测试,它似乎工作得很好。也许这里的比赛条件 知道为什么会这样吗 提前谢谢 using Newtonsoft.Json; using Newtonsoft.Json.

我在RestSharp客户机周围编写了一个相当简单的包装器,使用Polly为其添加了一些重试逻辑。 我设置了Fiddler来测试它,并模拟了一些“糟糕”的响应

我遇到的问题是onRetry委托中的
result.result.StatusCode
位有时似乎记录为0,而不是实际的错误状态代码(在我的一些测试中为502)

然而,通过我的单元测试,它似乎工作得很好。也许这里的比赛条件

知道为什么会这样吗

提前谢谢

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Polly;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Net;

namespace FundsAFE.Graphite
{
    public class RequestExecutor
    {
        private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        private IRestClient client;
        private IRestRequest request;
        private Policy<IRestResponse> retryPolicy;

        public IRestResponse LastErrorResponse { get; set; }

        private static readonly List<HttpStatusCode> invalidStatusCodes = new List<HttpStatusCode> {
            HttpStatusCode.BadGateway,
            HttpStatusCode.Unauthorized,
            HttpStatusCode.InternalServerError,
            HttpStatusCode.RequestTimeout,
            HttpStatusCode.BadRequest,
            HttpStatusCode.Forbidden,
            HttpStatusCode.GatewayTimeout
        };


        public RequestExecutor(IRestClient client, IRestRequest request)
        {
            this.client = client;
            this.request = request;
        }

        public IRestResponse Execute(int retryCount, int delay)
        {

            retryPolicy = Policy                
                .HandleResult<IRestResponse>(resp => invalidStatusCodes.Contains(resp.StatusCode) || !IsValidJson(resp))                
                .WaitAndRetry(retryCount, i => TimeSpan.FromMilliseconds(delay), (result, timeSpan, currentRetryCount, context) =>
                {
                    //Status code here is sometimes 0???
                    logger.Error($"Request failed with {result.Result.StatusCode}. Waiting {timeSpan} before next retry. Retry attempt {currentRetryCount}");
                    LastErrorResponse = result.Result;
                });

            var policyResponse = retryPolicy.ExecuteAndCapture(() =>
            {
                var url = client.BuildUri(request);
                logger.Debug(url.ToString());
                var response = client.Execute(request);
                return response;
            });
            if(policyResponse.Result != null)
            {
                return policyResponse.Result;
            } else
            {
                return LastErrorResponse;
            }
        }

        public static bool IsValidJson(IRestResponse response)
        {
            if (response.Content.Length == 0)
            {
                //Empty response treated as invalid
                return false;
            }
            try
            {
                var parsed = JObject.Parse(response.Content);
            }
            catch (JsonReaderException e)
            {
                //Will catch any mallformed json
                return false;
            }
            return true;
        }
    }
}


using Microsoft.VisualStudio.TestTools.UnitTesting;
using FundsAFE.Graphite;
using Moq;
using RestSharp;
using System.Net;
using FluentAssertions;
using System;
using FluentAssertions.Extensions;

namespace FundsAFE.Test.Moq
{
    [TestClass]
    public class MoqUnitTestRequest
    {

        public Mock<IRestClient> CreateMockClientWithStatusCodeAndContent(HttpStatusCode code, string content)
        {
            Mock<IRestClient> mockClient = new Mock<IRestClient>();
            mockClient.Setup(c => c.Execute(It.IsAny<IRestRequest>())).Returns(
                new RestResponse
                {
                    Content = content,
                    StatusCode = code
                }
            );

            mockClient.Setup(c => c.BuildUri(It.IsAny<IRestRequest>())).Returns(
                new Uri("http://fake.fake")
            );

            return mockClient;
        }

        [DataTestMethod]
        [DataRow(HttpStatusCode.BadGateway)]
        [DataRow(HttpStatusCode.Unauthorized)]
        [DataRow(HttpStatusCode.InternalServerError)]
        [DataRow(HttpStatusCode.RequestTimeout)]
        [DataRow(HttpStatusCode.BadRequest)]
        [DataRow(HttpStatusCode.Forbidden)]
        [DataRow(HttpStatusCode.GatewayTimeout)]
        public void TestBadStatusCodesAndRetry(HttpStatusCode httpStatusCode) {
            //Arrange
            Mock<IRestRequest> mockRequest = new Mock<IRestRequest>();
            Mock<IRestClient> mockClient = CreateMockClientWithStatusCodeAndContent(httpStatusCode, "fakecontent");
            RequestExecutor requestExecutor = new RequestExecutor(mockClient.Object, mockRequest.Object);

            int retries = 10;
            int delay = 50;
            int totalWaitTime = (retries * delay) - 10; //10ms error margin

            //Act and Verify            
            var response = requestExecutor.Execute(retryCount: retries, delay: 101);
            mockClient.Verify(x => x.Execute(It.IsAny<IRestRequest>()), Times.Exactly(retries + 1)); //1st failed attempt + 10 retries = 11            

            //Assert            
            requestExecutor.ExecutionTimeOf(re => re.Execute(retries, delay)).Should().BeGreaterOrEqualTo(totalWaitTime.Milliseconds());
            response.Should().NotBeNull();
            response.StatusCode.Should().Be(httpStatusCode);
            requestExecutor.LastErrorResponse.StatusCode.Should().Be(httpStatusCode);
        }

        [DataTestMethod]
        //Empty content
        [DataRow("")]
        //Missing closing quote
        [DataRow("{\"fruit\": \"Apple,\"size\": \"Large\",\"color\": \"Red\"}")]
        //Missing angle bracket
        [DataRow("\"q1\": {\"question\": \"Which one is correct team name in NBA?\",\"options\": \"New York Bulls\",\"Los Angeles Kings\",\"Golden State Warriros\",\"Huston Rocket\"],\"answer\": \"Huston Rocket\"}")]
        //Missing curly bracket
        [DataRow("\"sport\": {\"q1\": {\"question\": \"Which one is correct team name in NBA?\",\"options\": \"New York Bulls\",\"Los Angeles Kings\",\"Golden State Warriros\",\"Huston Rocket\"],\"answer\": \"Huston Rocket\"}")]
        public void TestBadContentRetries(string content)
        {

            //Arrange
            Mock<IRestRequest> mockRequest = new Mock<IRestRequest>();
            Mock<IRestClient> mockClient = CreateMockClientWithStatusCodeAndContent(HttpStatusCode.OK, content);
            RequestExecutor requestExecutor = new RequestExecutor(mockClient.Object, mockRequest.Object);

            int retries = 10;
            int delay = 50;
            int totalWaitTime = (retries * delay) - 10; //10ms error margin

            //Act and Verify            
            var response = requestExecutor.Execute(retryCount: retries, delay: delay);
            mockClient.Verify(x => x.Execute(It.IsAny<IRestRequest>()), Times.Exactly(retries + 1)); //1st failed attempt + 10 retries = 11            

            //Assert            
            requestExecutor.ExecutionTimeOf(re => re.Execute(retries, delay)).Should().BeGreaterOrEqualTo(totalWaitTime.Milliseconds());
            response.Should().NotBeNull();

        }
    }
}
使用Newtonsoft.Json;
使用Newtonsoft.Json.Linq;
使用Polly;
使用RestSharp;
使用制度;
使用System.Collections.Generic;
Net系统;
名称空间安全。石墨
{
公共类请求执行器
{
私有静态只读NLog.Logger Logger=NLog.LogManager.GetCurrentClassLogger();
私人IRestClient;
私人IRestRequest请求;
私人政策;
公共IRestResponse LastErrorResponse{get;set;}
私有静态只读列表invalidStatusCodes=新列表{
HttpStatusCode.BadGateway,
HttpStatusCode。未经授权,
HttpStatusCode.InternalServerError,
HttpStatusCode.RequestTimeout,
HttpStatusCode.BadRequest,
HttpStatusCode。禁止,
HttpStatusCode.GatewayTimeout
};
公共请求执行器(IRestClient客户端、IRestRequest请求)
{
this.client=client;
this.request=请求;
}
公共iResponse执行(int retryCount,int delay)
{
retryPolicy=Policy
.HandleResult(resp=>invalidStatusCodes.Contains(resp.StatusCode)| | |!IsValidJson(resp))
.WaitAndRetry(retryCount,i=>TimeSpan.From毫秒(延迟),(结果,TimeSpan,currentRetryCount,上下文)=>
{
//这里的状态代码有时是0???
logger.Error($“请求失败,带有{result.result.StatusCode}。在下次重试之前等待{timeSpan}。重试尝试{currentrycount}”);
LastErrorResponse=result.result;
});
var policyResponse=retryPolicy.ExecuteAndCapture(()=>
{
var url=client.BuildUri(请求);
Debug(url.ToString());
var response=client.Execute(请求);
返回响应;
});
if(policyResponse.Result!=null)
{
返回policyResponse.Result;
}否则
{
返回LastErrorResponse;
}
}
公共静态bool IsValidJson(iResponse响应)
{
if(response.Content.Length==0)
{
//空响应被视为无效
返回false;
}
尝试
{
var parsed=JObject.Parse(response.Content);
}
捕获(JsonReaderException e)
{
//将捕获任何错误信息
返回false;
}
返回true;
}
}
}
使用Microsoft.VisualStudio.TestTools.UnitTesting;
使用安全石墨;
使用最小起订量;
使用RestSharp;
Net系统;
使用FluentAssertions;
使用制度;
使用FluentAssertions.Extensions;
命名空间FundsAFE.Test.Moq
{
[测试类]
公共类MoqUnitTestRequest
{
公共模拟CreateMockClientWithStatusCodeAndContent(HttpStatusCode代码,字符串内容)
{
Mock mockClient=new Mock();
mockClient.Setup(c=>c.Execute(It.IsAny())。返回(
新的反应
{
内容=内容,
状态代码=代码
}
);
mockClient.Setup(c=>c.BuildUri(It.IsAny())。返回(
新Uri(“http://fake.fake")
);
返回客户;
}
[数据测试方法]
[数据行(HttpStatusCode.BadGateway)]
[数据行(HttpStatusCode.Unauthorized)]
[数据行(HttpStatusCode.InternalServerError)]
[数据行(HttpStatusCode.RequestTimeout)]
[数据行(HttpStatusCode.BadRequest)]
[数据行(HttpStatusCode.Forbidden)]
[数据行(HttpStatusCode.GatewayTimeout)]
公共无效TestBadStatusCode和重试(HttpStatusCode HttpStatusCode){
//安排
Mock mockRequest=new Mock();
Mock mockClient=CreateMockClientWithStatusCodeAndContent(httpStatusCode,“fakecontent”);
RequestExecutor RequestExecutor=新的RequestExecutor(mockClient.Object,mockRequest.Object);
int重试次数=10次;
int延迟=50;
int totalWaitTime=(重试次数*延迟)-10;//10ms误差幅度
//行动核实
var response=requestExecutor.Execute(retryCount:retries,delay:101);
mockClient.Verify(x=>x.Execute(It.IsAny()),Times.justice(重试+1));//第一次失败的尝试+10次重试=11
//断言
requestExecutor.ExecutionTimeOf(re=>re.Execute(重试,延迟)).Should().BegreateRoRequesto(totalWaitTime.millizes());
response.Should().NotBeNull();
response.StatusCode.Should().Be(httpStatusCode);
requestExecutor.LastErrorResponse.StatusCode.Should().Be(httpStatusCode);
}
[数据测试方法]
//空内容
[数据行(“”)
//缺少收盘报价
[数据行(“{”水果“:”苹果“,”大小“:”大“,”颜色“:”红色“}”)]
//缺少角括号
[数据行(“\'q1\”:{\'question\”:\“哪一个是NBA中正确的球队名称?\”,“选项”:“纽约公牛队”,“洛杉矶国王队”,“金州W