1

I am using HttpClient to send a POST request to an external API and I know I'm sending the correct payload and headers and everything because it works sometimes but other times it returns an empty string response. It always returns a 200 status code. Changing it to asynchronous is not an option so my question is, how do I run this synchronously and reliably get a response back?

This is the method that I've used in a number of other places in my application for GETs and POSTs and it works perfectly fine every time:

HttpClient client = new HttpClient { BaseAddress = new Uri("url")
client.DefaultRequestHeaders.Add("X-Authorization", "Bearer " + authToken);

var input = new {json object};

var serializerSettings = new JsonSerializerSettings();
serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

var json = JsonConvert.SerializeObject(input, serializerSettings);
var data = new StringContent(json, Encoding.UTF8, "application/json");

var result = client.PostAsync("api/Create", data).Result;
if (result.IsSuccessStatusCode)
{
    var jsonStringResult = result.Content.ReadAsStringAsync().Result;
    var response = JsonConvert.DeserializeObject<ApiResponse>(jsonStringResult);
    // response is null....sometimes
}

Other methods that I've tried from scouring other forum posts and blogs:

var postTask = client.PostAsync("api/Create", data);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
    var jsonTask = result.Content.ReadAsStringAsync();
    jsonTask.Wait();
    var jsonStringResult = jsonTask.Result;
    var response = JsonConvert.DeserializeObject<ApiResponse>(jsonStringResult);
    // response is null....sometimes
}

and

var result = client.PostAsync("api/Create", data).GetAwaiter().GetResult();
if (result.IsSuccessStatusCode)
{
    var jsonStringResult = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
    var response = JsonConvert.DeserializeObject<ApiResponse>(jsonStringResult);
    // response is null....sometimes
}

and

byte[] byteArray = Encoding.UTF8.GetBytes(json);

var request = (HttpWebRequest)WebRequest.Create("url");
request.Method = "POST";
request.ContentType = "application/json; charset=UTF-8";
request.Accept = "application/json";
request.ContentLength = byteArray.Length;
request.Headers.Add("X-Authorization", "Bearer" + authToken);

Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();

WebResponse response = request.GetResponse();

dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();

dataStream.Close();
response.Close();

var responseObj = JsonConvert.DeserializeObject<ApiResponse>(responseFromServer);
// resonseObj is null...sometimes

All four methods work some of the time but I need this to be more reliable. Deadlocks are not a concern at this time. Any help is appreciated!

tw3399
  • 31
  • 5
  • 4
    I suggest you use Fiddler (or some other network trace tool) to confirm that the body is being returned from the server. There might be nothing wrong with your client. – John Wu Jun 29 '21 at 02:13
  • Why is async not an option? – mason Jun 29 '21 at 02:13
  • 1
    Its a windows form app that communicates with a microcontroller and executes a bunch of actions in a specific order. It took a lot of effort to get everything working just right. I tried running this piece asynchronously and it threw everything else out of wack and still didn't work any better than the other methods above. – tw3399 Jun 29 '21 at 15:08
  • 1
    I've run into a very similar problem. In my case, the code works reliably and I got proper responses when it's run on various Windows 10 machines, but fails most of the time when run on a Windows Server. Have you had any progress solving it? – Avi Apr 14 '22 at 13:32
  • @Avi on windows server is it run ever successfully? after how many tries it gets fail? – sa-es-ir Apr 23 '22 at 19:32
  • @Saeed, it's inconsistent. Sometimes it takes 2 or 3 tries before it works. Sometimes 5 or 6. I'd estimate that it works 30% of the time. – Avi Apr 24 '22 at 15:00
  • @Avi It always return 200? or in case on not working return other statuses like 500? – sa-es-ir Apr 24 '22 at 15:22
  • @SaeedEsmaeelinejad, when it works, it's status code 200. When it fails, it's status 0. – Avi Apr 24 '22 at 16:53
  • @SaeedEsmaeelinejad, here's a screenshot of a test program I made that tries to make the call repeatedly in a loop three times, and outputs the status code each time. You can see that it tried 3 times, and failed each one. Then it tried again 3 times, and failed again. Then it tried again, and succeeded on the second try. https://ibb.co/WgxVk15 – Avi Apr 24 '22 at 16:59
  • Maybe this help: https://stackoverflow.com/questions/872206/what-does-it-mean-when-an-http-request-returns-status-code-0 – sa-es-ir Apr 24 '22 at 17:13
  • Do you also get inconsistent results on Postman? – Emre Utku Solak Apr 25 '22 at 07:01
  • I suggest you extend the request timeout, If it doesn't work and you aren't able to debug API code, try using network packet tracers or test API using Postman – Ali Amini Apr 25 '22 at 09:36
  • @tw3399 you're not likely to get a good answer until you eliminate all of your middleware, proxies, reverse proxies, etc. You need to work with a very basic client located as "close" as possible to the API in question and see what happens (e.g. `curl` with as few network hops as possible). – Kit Apr 25 '22 at 13:03
  • @EmreUtkuSolak, no. Making the request with Postman consistently works fine. As does browsing the site regularly with a browser. – Avi Apr 27 '22 at 01:39
  • It also seems to work fine when using curl. – Avi Apr 27 '22 at 01:51
  • @EmreUtkuSolak and SaeedEsmaeelinejad, I finally got it all resolved. Please see my answer below. – Avi Apr 28 '22 at 16:36

1 Answers1

0

In a comment above, I noted that I was having the same occasional empty response issue, but I finally figured out what was happening and got my code working. There were 2 issues I had to address:

  1. My user-agent header. I was using a user-agent that included a bunch of various elements, something like Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36, and when I changed it to just Mozilla/5.0 (Windows NT 10.0; Win64; x64) everything started working more consistently. (I was having the empty responses on a Windows Server environment, so you might have to adjust this to match the user-agent string to exactly your own environment.)

  2. I had used Postman to test my calls and took the generated code that it produced, but when using Fiddler I discovered that there was a header Postman was sending that was not being outputted in their generated code. Specifically: Accept-Encoding: gzip, deflate, br. So naturally, it hadn't been included it in my code.

When I modified these two aspects of my http request code, it consistently worked without any empty responses. However, just to note, one more thing had to be adjusted after adding the accept-encoding header. The response was being compressed, so I had to specify that the HttpClient automatically decompress the response. My final HttpRequest initialization code looked like this:

    Dim HttpClient = New HttpClient(New HttpClientHandler With {.UseCookies = False, .AutomaticDecompression = DecompressionMethods.GZip Or DecompressionMethods.Deflate})
    Try
        HttpClient.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
        HttpClient.Timeout = TimeSpan.FromSeconds(10)
        HttpClient.DefaultRequestHeaders.Add("authority", "https://blahblah")
        HttpClient.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br")  
        HttpClient.DefaultRequestHeaders.Add("accept", "application/json, text/plain, */*")
        HttpClient.DefaultRequestHeaders.Add("accept-language", "en-US,en;q=0.9")
        HttpClient.DefaultRequestHeaders.Add("cookie", CookieVar)
        HttpClient.DefaultRequestHeaders.Add("origin", "https://blahblah")
        HttpClient.DefaultRequestHeaders.Add("referer", "https://blahblah")
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try

A final note: once I specified the AutomaticDecompression properties, using Fiddler I discovered that the Accept-Encoding header was added automatically to the call, so the actual line that specified it could be taken out. I left it in above just to show what I was talking about.

Also, to explain the .UseCookies = False line in the HttpClientHandler, see here.

Avi
  • 962
  • 9
  • 17