HttpClient.PostAsJsonAsync never sees when the post is succeeding and responding
Asked Answered
E

5

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 "";
        }

    }
Earthbound answered 4/6, 2015 at 20:42 Comment(1)
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.Earthbound
E
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.

Earthbound answered 9/6, 2015 at 15:54 Comment(0)
C
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.

Cardigan answered 4/6, 2015 at 21:10 Comment(5)
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.Earthbound
@Earthbound Sometimes people don't actually understand that they're mis-using async API's. You can also ways go "raw" with HttpWebRequest.Cardigan
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.Earthbound
@Earthbound 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?Cardigan
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.Earthbound
U
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);
Uniform answered 21/9, 2016 at 10:1 Comment(1)
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.Conch
Z
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 "";
    }

}
Zymogen answered 4/6, 2015 at 21:4 Comment(3)
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.Earthbound
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 :)Earthbound
@Earthbound - I was a fan of RestSharp before the HttpClient was introduced. That might be another option as well.Zymogen
L
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.

Lublin answered 5/6, 2015 at 12:42 Comment(1)
Good catch, I didn't see the other .Result call.Zymogen
E
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.

Earthbound answered 9/6, 2015 at 15:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.