none
Http Reuse of connections RRS feed

  • Question

  • I look for a way to reuse connection and work in async at the same time.

    first I loop over the data I get from the db : 

    foreach (SmsMessage message in lSmsMessage)
                    {
                        HttpManager httpManager = new HttpManager();
                        inputTaskList.Add(httpManager.SendData(strURL, message, Encoding.Default,xml);
                    }
    
    var results = await Task.WhenAll(inputTaskList);

    and the HttpManager class :

    public class HttpManager
        {
            private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
            private readonly static string reservedCharacters = "!*'();:@&=+$,/?%#[]";
            public async Task<bool> SendData(string url, Message message, Encoding encodingPage,string baseXml)
            {
                string xml = BuildXml(message, baseXml);
                bool isSuccess = true;
                using (var client = new HttpClient())
                {
                    client.DefaultRequestHeaders.ConnectionClose = false;//keeyp-alive
                    string resultContent = string.Empty;
    
                    client.BaseAddress = new Uri(url);
                    var content = new FormUrlEncodedContent(new[]
                    {
                        new KeyValuePair<string, string>("XML", xml)
                    });
                    try
                    {
                        HttpResponseMessage result = await client.PostAsync("", content);
                        if (result.StatusCode == HttpStatusCode.OK)
                        {
                            resultContent = await result.Content.ReadAsStringAsync();
                            if(resultContent.IndexOf(">True<")>0)
                            {
                            }
                            else
                            {
                                isSuccess = false;
                                logger.Error("Failes to send1,reason={0}", resultContent);
                            }
    
                        }
                        else
                        {
                            isSuccess = false;
                            logger.Error("Failes to send2 CampaignId={3}");
                        }
                    }
                    catch (Exception ex)
                    {
                        
                        logger.Error("Failes to send3()::failed sending Err={0}",  ex.Message);
                    }
    
                }
    
                return isSuccess;
            }
    
            
    
            private static string ReplaceSpeicalChars(string txt)
            {
                txt=txt.Replace("&", "&amp;");
                return txt;
            }
           
        }

    what is the corret wa to resuse the connections?


    Monday, January 27, 2020 4:01 PM

All replies

  • HttpClient is already built for reuse. You don't need to do anything. Once the client is initialized the very first time you need it you can continue to use the same instance (across threads if needed) from then on. The only method it exposes is already thread safe. Also note you shouldn't dispose of HttpClient. While it implements that interface it is not designed to be wrapped in a using statement. Doing so will eventually cause your program to fail as discussed here and many other places.

    public class HttpManager
    {
       //Simple version
       public HttpManager ( string url ) : this(new HttpClient(url))
       {
       }
    
       //More correct version
       public HttpManager ( HttpClient client )
       {
          _client = client;
       }
    
       public async Task<bool> SendData ( Message message, ... )
       {
          var xml = BuildXml(message);
          ...
          
          using (var response = await _client.PoseAsync("", content))
          {
             response.EnsureSuccessStatusCode();
             ...
          };
       }
    
       private readonly HttpClient _client;
    }
    
    //In a perfect world do the init of the client at app startup
    void Startup ()
    {
       var client = new HttpClient() {
          BaseAddress = ""
       };   
    
       //Store client somewhere everybody has access to such as a DI container
    }
    
    

    .NET Core has a static factory for creating clients if you cannot use a DI container. If you aren't using Core then you can build your own simple version instead.

    public static class SimpleHttpClientFactory
    {
       public static void Register ( string clientName, HttpClient client )
       {
          s_clients[clientName] = client;
       }
    
       public static HttpClient Get ( string clientName )
       {
          return s_clients[clientName];
       };
    
       private static readonly Dictionary<string, HttpClient> s_clients = new Dictionary<string, HttpClient>(StringComparer.OrdinalIgnorecase);
    }

    Now you can use it anywhere.

    //App startup where configuration should be done
    void Startup()
    {
       //Build the client you'll want to use
       var client = new HttpClient()
       {
          BaseAddress = "";
       };
    
       SimpleHttpClientFactory.Register("MyClient", client);
    }
    
    //later when you need it
    void Foo ()
    {
       var client = SimpleHttpClientFactory.Get("MyClient");
       var manager = new HttpManager(client);
    }

    Note that to be more efficient you should probably create the client on first use so personally I use a function to create the client, thread safe, on demand. But the net effect is the same. The same HttpClient is used for any requests to the same URL. Thread safety is already handled by the client and you shouldn't clean it up. Once the first call to the client is triggered you cannot change the URL or headers as an error will occur, by design.

    If you need to target arbitrary URLs then HttpClient may not be the best choice as you could end up creating multiple instances to the same URL. In that case WebClient might be a better option.


    Michael Taylor http://www.michaeltaylorp3.net

    Monday, January 27, 2020 7:23 PM
    Moderator
  • You should not re-use the connections.

    Although the HttpClient implements the IDisposable interface, it is a shared object. Instead of creating a new instance of HttpClient for each execution you should share a single instance of the HttpClient for the entire lifetime of the application; otherwise, you can expect to see SocketExceptions. This is well documented.

    https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

    you should do something like this:

    private static HttpClient Client = new HttpClient();

    And you should use the HttpClientFactory if you are using .NET Core.

    https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests















    william xifaras



    Monday, January 27, 2020 10:01 PM
  • thanks for the replies.

    I am using NET (not CORE)

    I will refactor and retest.

    due to that all the post I do is used with the same URL, should I do any other code modifications?

    Monday, January 27, 2020 10:46 PM
  • Personally I think your `HttpManager` class is misnamed. Traditionally what we do is create a `Client` wrapper class that accepts an `HttpClient` and exposes functionality specific to the API you are calling. So rename your class to match the API you're calling and, using the sample code I already posted, you should be fine. The key is to not keep recreating the HttpClient but ensuring that you clean up the response object from the call.


    Michael Taylor http://www.michaeltaylorp3.net

    Monday, January 27, 2020 10:53 PM
    Moderator
  • Hi,

    i have implemented all the changes in the abovecode

    but i am facing the next error : 

    "A task was canceled" .

    how can i investigate this issue deeply?

    Monday, February 3, 2020 12:26 PM
  • This isn't an issue in most cases. This occurs when running async code and the request is cancelled. It is bubbled up as a cancellation exception so higher level code can clean up. It is generally the caller who triggers this so you'll need to look at where it is being called from. 

    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 2:50 PM
    Moderator
  • I don't have any cancel code, and the above code is the most of the project
    Monday, February 3, 2020 3:08 PM
  • Post your updated code because none of the original code had it. Also post the code calling this code. Cancellation is almost always triggered externally by a user (e.g. navigating to a different browser page, clicking a button on the screen, etc)

    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 3:09 PM
    Moderator
  • public class HttpManager
        {
            private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
            private readonly ServerInternalQueue InternalQueue = ServerInternalQueue.Instance;
            private readonly static string reservedCharacters = "!*'();:@&=+$,/?%#[]";
            private readonly HttpClient _client;
            bool isSuccess = true;
            public HttpManager(HttpClient httpClient)
            {
                _client = httpClient;
            }
            public async Task<bool> SendData( SmsMessage smsMessage, Encoding encodingPage, string authInfo, string baseXml)
            {
                string xml = BuildXml(smsMessage, baseXml);
                //httpClient.DefaultRequestHeaders.ConnectionClose = false;
                string resultContent = string.Empty;
                //_client.BaseAddress = new Uri(url);
                var content = new FormUrlEncodedContent(new[]
                {
                        new KeyValuePair<string, string>("XMLString", xml)
                    });
                try
                {
                    using (HttpResponseMessage result = await _client.PostAsync("", content)) { 
                        if (result.StatusCode == HttpStatusCode.OK)
                        {
                            resultContent = await result.Content.ReadAsStringAsync();
                            
                            if (resultContent.IndexOf(">True<") > 0)
                            {
                                smsMessage.Status = Enums.SMSLogStatus.Sent;
                                InternalQueue.nonFinalStatusUpdateDbQ.Add(smsMessage);
                            }
                            else
                            {
                                isSuccess = false;
                                smsMessage.Status = Enums.SMSLogStatus.Failure;
                                smsMessage.ErrorCode = 1;
                                InternalQueue.finalStatusUpdateDbQ.Add(smsMessage);
                               
                            }
    
                        }
                        else//result.StatusCode
                        {
                            smsMessage.Status = Enums.SMSLogStatus.Failure;
                            smsMessage.ErrorCode = (int)result.StatusCode;
                            InternalQueue.finalStatusUpdateDbQ.Add(smsMessage);
                            isSuccess = false;
                            logger.Error("Failes to send2={0},StatusCode={1},SmsLogId:{2},CampaignId={3}", resultContent, result.StatusCode, smsMessage.LogID, smsMessage.CampaignId);
                        }
                    }
                }
                catch (Exception ex)
                {
                    smsMessage.Status = Enums.SMSLogStatus.Failure;
                    smsMessage.ErrorCode = 2;
                    InternalQueue.finalStatusUpdateDbQ.Add(smsMessage);
                    isSuccess = false;
                    logger.Error("Failes to send3()::failed sending ,SmsLogId:{0},CampaignId={1},Err={2}", smsMessage.LogID, smsMessage.CampaignId, ex.Message);
                }
    
    
    
                return isSuccess;
            }
    
            
        }

    Sender class which calls the HttpManager :

        public class Sender
        {
            private readonly ServerInternalQueue InternalQueue = ServerInternalQueue.Instance;
            private static dynamic config = new Configuration();
            private readonly Thread _thread;
            private CRMDal dal = null;
            private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
            private int pullDBLimit = Convert.ToInt32(config.PullDBLimit);
            private int sendSpeedLimit = Convert.ToInt32(config.sendSpeedLimit);
            private readonly int SMSProviderID = Convert.ToInt32(config.SMSProviderID);
            private CountersV2 counters = CountersV2.Instance;
            private DateTime lastPrintStatistics = DateTime.Now;
            private System.Timers.Timer qPrintTimer = null;
            private readonly string baseXml;
            private readonly HttpClient httpClient;
    
            public Sender()
            {
                baseXml = InitXml();
                _thread = new Thread(DoWork);
                string connectionString = Convert.ToString(((Formo.Configuration)config).ConnectionString.Ptring);
                dal = new CRMDal(connectionString);
                httpClient = new HttpClient()
                {
                    BaseAddress = new Uri(config.LA_URL)
                };
    			_thread.Start();
    
    
            }
    
          
    
            #region private methods
            private async void DoWork()
            {
                List<GetSmsForSendingWithExtResult> smsLogs = null;
                while (true)
                {
                    smsLogs = null;
                    bool isFailedSql = false;
                    try
                    {
                        smsLogs = SenderDataLayer.GetSmsForSendingWithExt(SMSProviderID, pullDBLimit);
                    }
                    catch (SqlException e)
                    {
                        isFailedSql = true;
                        Logger.Error("SqlException::failed on SenderDataLayer.GetSmsForSendingWithExt,Error:{0}", e.Message);
                    }
                    catch (Exception e)
                    {
                        isFailedSql = true;
                        Logger.Error("Exception::failed on SenderDataLayer.GetSmsForSendingWithExt,Error:{0}", e.Message);
                    }
    
                    if (smsLogs != null && smsLogs.Count == 0)
                    {
                        Thread.Sleep(1000);
                        if (lastPrintStatistics.AddSeconds(60) < DateTime.Now)
                        {
                            Logger.Info(counters.print());
                            lastPrintStatistics = DateTime.Now;
                        }
                        continue;
                    }
                    if (!isFailedSql)
                    {
                        FastSend(smsLogs);
                        Logger.Info(counters.print());
                       }
    
                    Thread.Sleep(1000);
                }//while
            }//DoWork()
             
                 
    
            private async void FastSend(List<SmsMessage> lSmsMessage)
            {
                string unuiqueEntranceId = Guid.NewGuid().ToString();
                Logger.Info("Enter FastSend:{0},with EntranceId:{1}", DateTime.Now, unuiqueEntranceId);
                string Err = null;
    
                #region BuildListForSending
                try
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    List<Task<bool>> inputTaskList = new List<Task<bool>>();
                    foreach (SmsMessage message in lSmsMessage)
                    {
                        HttpManager httpManager = new HttpManager(httpClient);
                        inputTaskList.Add(httpManager.SendData(message, Encoding.Default, "", baseXml));
                    }
                    var results = await Task.WhenAll(inputTaskList);
                    int Good = 0, notGood = 0;
                    foreach (var res in results)
                    {
                        if (res == true)
                            ++Good;
                        else
                            ++notGood;
                    }
                    stopwatch.Stop();
                    Logger.Info("Good={0},notGood={1},EntranceId:={2},FinishTime:{3},TotalTime(sec):{4}", Good, notGood, unuiqueEntranceId, DateTime.Now, stopwatch.ElapsedMilliseconds / 1000);
    
                }
                catch (InvalidOperationException ex)
                {
                    Err = string.Format("FaseSend::ForEach2:{0}", ex.InnerException);
                }
                catch (Exception ex)
                {
                    Err = string.Format("FaseSend::ForEach2:{0}", ex.InnerException);
                }
                if (Err != null) Logger.Error(Err);
            }//(SmsMessage message in lSmsMessage)
            #endregion BuildListForSending
    
    


    • Edited by want 2 Learn Monday, February 3, 2020 3:36 PM fixed code
    Monday, February 3, 2020 3:34 PM
  • And where is this exception being thrown? You need to filter out cancellation exceptions from your error handling. They are not generally considered to be an actual error. Sort of like if the user enter a non-numeric value in a textbox. You might represent it as an exception but it isn't an error, but input validation.

    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 3:47 PM
    Moderator
  • the error is on

    Failes to send3()

    Monday, February 3, 2020 3:49 PM
  • First thing I should point out is that your code in this block is not properly handling HTTP results. OK is just one of an entire class of success status codes. You may be flagging errors for responses that are valid. Use EnsureSuccessStatusCode to check the response before processing it. It is designed to handle these scenarios.

    As for the error itself I'm going to guess that the request is taking too long. IIRC HttpClient uses task cancellation to indicate the server took too long to respond rather than a traditional timeout exception. You should be able to verify this. If it is timing out then increase the timeout on the client.


    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 3:57 PM
    Moderator
  • if I increase the time

    then the code won't continue for the next iteration :

    var results = await Task.WhenAll(inputTaskList);
    also I see that the default time is 1MIn40SEC which should be more then enough


    • Edited by want 2 Learn Monday, February 3, 2020 4:09 PM add comment
    Monday, February 3, 2020 4:05 PM
  • "then the code won't continue for the next iteration :"

    That's correct because you want to wait for everything to complete. But that has nothing to do with the question you asked so it isn't relevant here. If you want to discuss how to block/not block calls then you'll need to post a separate question. This question was focused on reusing an existing client which we've already solved. We're now just trying to help you get past the cancellation exception which is technically a completely different issue as well. But, if you increase the timeout does it work? Have you set a breakpoint on the PostAsync and verified that it is failing almost immediately with a cancellation exception? Did you look at the inner exception to see if there is any additional information?


    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 4:12 PM
    Moderator
  • Michael - thanks for the feedback I will test and update.

    if I will put breakpoint on the async it won't simulate a time out,

    since I need something to block it.


    Monday, February 3, 2020 4:28 PM
  • You're not trying to simulate a timeout. You're trying to debug the issue. By putting a breakpoint on that line and then running your code under the scenario that is causing the cancellation exception you should be able to step through the code and see what the exception is, what data you sent that triggered it, etc. If the remote server is timing out the request then debugging will replicate that correctly.

    Michael Taylor http://www.michaeltaylorp3.net

    Monday, February 3, 2020 5:03 PM
    Moderator