16

We are using an HttpClient to post json to a restful web service. In one instance, we are running into something that has us baffled. Using tools like postman, fiddler etc, we can post to an endpoint and see that it is working. When we do the same with HttpClient.PostAsJsonAsync, we can verify in the software we are posting to that it received the data just fine. However, our PostAsJsonAsync will always eventually time out rather than give us a response.

We have worked with the team that created the service we are consuming, plus our additional testing on our side, and we have not yet been able to truly time out that service.

Every time we do a post with HttpClient, we then can verify that the target software we post to does indeed get the data. Any time we do a post to that target software from any other tool, we always very quickly see a response with status code of 200. Something about HttpClient is failing to accept the response from this particular service. Does anyone have an idea what we can look at from here?

Here's the code (though it is so cookie cutter that I hardly feel it is needed)

public string PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
    {
        using (var client = new HttpClient())
        {
            if (timeoutMinutes > 0)
            {
                client.Timeout = new TimeSpan(0,timeoutMinutes,0);
            }
            var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
            var response = client.PostAsJsonAsync(useUrl, o).Result;
            if(response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                return response.Content.ReadAsStringAsync().Result;
            }
            return "";
        }

    }
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Danny
  • 308
  • 1
  • 4
  • 12
  • I actually am getting the same exact problem when using WebClient. The posted data is reflected in the target system we post to, but our request hangs. I am starting to think that it is something to do with a self signed cert. We are connecting to a stage/test instance of this service on https, and the certificate probably does not have a valid signature. So far however, I can't seem to figure out how to verify if that is or isn't actually the problem. – Danny Jun 05 '15 at 19:48

5 Answers5

14

This:

var response = client.PostAsJsonAsync(useUrl, o).Result;

Is causing you code to deadlock. This is often the case when blocking on async API's, and that's why you're experiencing the "I don't see any response coming back" effect.

How is this causing a deadlock? The fact that you are executing this request in an environment that contains a synchronization context, perhaps one which belongs to the UI. It's executing the async request, and when the response arrives, it continues via an IO completion thread which attempts to post the continuation onto that same UI context, which is currently blocked by your .Result call.

If you want to make an HTTP request synchronously, use WebClient instead. If you want to take advantage of the async API properly, then await instead of block with .Result.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Thanks for the answer. When I was first looking at web client vs http client, I read that Http Client was newer and was given the distinct impression that it was the future where web client was going to be deprecated. I also saw stuff that said that doing the .Result as my code did was an acceptable way to use HttpClient in a blocking way. I will take another look at web client. Thanks. – Danny Jun 05 '15 at 16:19
  • @Danny Sometimes people don't actually understand that they're mis-using async API's. You can also ways go "raw" with `HttpWebRequest`. – Yuval Itzchakov Jun 05 '15 at 16:20
  • Webclient is doing the same thing. I think it may be .net code not liking a self signed certificate that is on the test/staging instance of the service that we are using. Having a beast of a time working out how to eliminate that possibility, or fix it if that is the problem. – Danny Jun 05 '15 at 19:50
  • @Danny Even the synchronous method invocation causes the freeze? Have you tried fiddlering the request? Does this happen if you remove the SSC and use `Http`? – Yuval Itzchakov Jun 05 '15 at 19:51
  • 1
    Yeah, I hadn't realized that we are accessing the service via a proxy (mulesoft API manager). If we access the service directly we can get webclient to work. When we go against the api manager proxy, we are stuck. So we are narrowing down our problem area. – Danny Jun 08 '15 at 15:05
9

I had the same issue and this SO answer fixed it for me.

In a nutshell, you have to use the ConfigureAwait(false) extension to avoid the deadlock:

var response = await client.PostAsJsonAsync(useUrl, o).ConfigureAwait(false);
Community
  • 1
  • 1
webStuff
  • 1,468
  • 14
  • 22
  • note that if you go that route to avoid the await capturing your UI context, you need to have `ConfigureAwait(false)` on *everything* before/beneath your `.Result` call. For that reason, it's seen as a little brittle. – Don Cheadle May 17 '17 at 22:06
6

Is there a reason why you're not following the async await pattern? You're calling an async method, but not awaiting it. You didn't say if the code calling your REST service was a Windows Forms or ASP.NET application, but that .Result is probably causing you issues.

Can you restructure your method like this:

public async Task<string> PostData(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
    using (var client = new HttpClient())
    {
        if (timeoutMinutes > 0)
        {
            client.Timeout = new TimeSpan(0,timeoutMinutes,0);
        }
        var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
        var response = await client.PostAsJsonAsync(useUrl, o);
        if(response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return await response.Content.ReadAsStringAsync();
        }
        return "";
    }

}
Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • Thanks for taking the time to reply. In my case I do want it to be a synchronous call. This code works fine when invoked on a web api server. We have a second environment where the code is being run on what we call our "task server" that is itself a service running on a server, and it takes a queued list of tasks and runs multiple tasks in its thread pool. In that environment, we are not getting the response. So I suspect that it is the difference in environment. – Danny Jun 05 '15 at 16:21
  • I probably am going to use web client being that I do want it to behave synchronously. I had read that HttpClient was ultimately going to deprecate web client, and that it was still quite workable for synchronous stuff. Guess I read wrong :) – Danny Jun 05 '15 at 16:22
  • @Danny - I was a fan of [RestSharp](http://restsharp.org/) before the HttpClient was introduced. That might be another option as well. – Justin Helgerson Jun 05 '15 at 16:41
1

This is a slight modification to @Justin Helgerson's solution. There are 2 blocking .Result calls in your method; once you go async you should fix them both.

public async Task<string> PostDataAsync(string resourcePath, Object o, Boolean isCompleteUrl = false, int timeoutMinutes = -1)
{
    using (var client = new HttpClient())
    {
        if (timeoutMinutes > 0)
        {
            client.Timeout = new TimeSpan(0,timeoutMinutes,0);
        }
        var useUrl = isCompleteUrl ? resourcePath : ApiBase + resourcePath;
        var response = await client.PostAsJsonAsync(useUrl, o);
        if(response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            return await response.Content.ReadAsStringAsync();
        }
        return "";
    }
}

Note I've also renamed the method to PostDataAsync in accordance with the TAP pattern.

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
1
System.Net.ServicePointManager.Expect100Continue = false;

That one line of code in our case fixed the problem. A developer from a different team offered that suggestion, and it works. I have yet to google it and read up on it enough to offer an explanation of what that is addressing.

Danny
  • 308
  • 1
  • 4
  • 12