2

I have a website solution written in .net framework 4.8 and I am trying to send JSON message over HTTP Post to a payment server and get a response, yet there is no error and no response message, it doesn't even time out. I tested the same code in another project, it is working fine but it is written in .net 5.0 (the latest and current).

I have no idea why the same code will yield such different results (although pointing to different versions of dll). Anyone can help me on this? Is there any documentation on this? I think that there is something can be done at the server side as well but I have no idea where to start. Basically I need my website to be able to "talk" to the payment server, but I'm stuck here.

Here's a snippet of my code

private static readonly HttpClient client = new HttpClient();

public static async Task<string> SubmitPaymentTransaction (string reqJsonstr)
    {
        try
        {
            HttpContent httpContent = new StringContent(reqJsonstr, Encoding.UTF8);
            httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

            var message = new HttpRequestMessage();
            message.Content = httpContent;
            message.Method = HttpMethod.Post;
            message.RequestUri = new Uri($"https://payment.com/PayURL");

            HttpResponseMessage response = await client.SendAsync(message);
            response.EnsureSuccessStatusCode();
            string result = await response.Content.ReadAsStringAsync();

            return result;
        }
        catch(Exception error)
        {
            return error.ToString();
        }
        
    }

UPDATE:

I am able to capture the error message ... by removing the await: client.SendAsync(message)

InnerException = {"Authentication failed because the remote party has closed the transport stream."} 
Message = "The underlying connection was closed: An unexpected error occurred on a send." 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
  at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
  at Mall.PayPlugin.Pay.PaymentCore.<SubmitPaymentTransaction>d__19.MoveNext()

I did a quick search on the error and it turns out the security protocol is the underlying issue. My web application is actually running on .net 4.5 so by default it is using TLS 1.0 which is unacceptable for the API server. Specifying the security protocol, I am able to get a response from the server, however, still at the expense of disabling the await.

paulsm4
  • 114,292
  • 17
  • 138
  • 190
Andy Leong
  • 41
  • 4
  • Q: Could you show us where "client" is declared? Initialized? The same for "response"? – paulsm4 Jun 20 '21 at 05:33
  • 1
    Your Timeout TimeSpan is actually about 16 minutes (1,000 seconds) The one-value constructor is in 100-nanosecond units, not nanoseconds. That's why it is much smarter to say `new TimeSpan(0,0,10)`. – Tim Roberts Jun 20 '21 at 05:45
  • @paulsm4 my "client" is simply "private static readonly HttpClient client = new HttpClient();" in the class, and the response is already declared in my method. It's a HttpResponseMessage. – Andy Leong Jun 21 '21 at 00:40
  • I repeat: `Q: Could you show us where "client" is declared? Initialized?`. REASON: It sounds like your "client" probably isn't "connecting". I'm trying to learn more about it, to help figure out "why". Otherwise, we're "flying blind". Please update your post with as much info as you can. ALSO: look [here](https://stackoverflow.com/q/50749042/421195), [here](https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/) and [here](https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/) – paulsm4 Jun 21 '21 at 00:55
  • 1
    ADDITIONAL SUGGESTION: please comment out the timeout: `// client.Timeout = new TimeSpan(10000000000); //10 secs`. If at all possible, we *WANT* to "fail quickly". To get a meaningful error message. So we can understand the "root cause", and resolve it :) – paulsm4 Jun 21 '21 at 00:58
  • @paulsm4 yes, I have updated my code snippet in my post. Please have a look for a clearer picture. My client declaration is the same as the fix in the second link you provided. – Andy Leong Jun 21 '21 at 01:11
  • Cool - thank you :) Q: Can you go into the MSVS debugger, and set a bkpt after `await client.SendAsync(message)`? And a second bkpt in your "catch" block? Does it hit either line? What is the error code in "response"? Do you see any evidence that your app actually "connected" to https://payment.com/PayURL? Or even *TRIED* to connect? Can you connect to that URL in a browser, or in [Postman](https://www.postman.com/product/rest-client/)? – paulsm4 Jun 21 '21 at 01:16
  • Yes, I've already tried adding breakpoints for those two lines. It hit the "await client.SendAsync(message)" but it doesn't go into the "catch" block. After it hits the "await client.SendAsync(message)" line, the yellow line just goes away and the website just keep loading. I don't see any evidence of connectivity but I can connect to the URL in a browser. I have not tried Postman yet. Let me try and get back to you – Andy Leong Jun 21 '21 at 01:33
  • @paulsm4 I have just tested on Postman. I am able to get a JSON response from the API. – Andy Leong Jun 21 '21 at 01:47
  • Thanks for the update. Your .Net 4.8 version worked, and Postman works on your workstation ... but we're still batting zero on anything that would tell us *WHAT* the ASP.Net/HttpCient libraries "don't like". Q: Any chance you can do a network trace, e.g. [Wireshark](https://www.wireshark.org/download.html)? Or consider HttpClient verbose logging, like this: https://www.stevejgordon.co.uk/httpclientfactory-asp-net-core-logging. ALSO: Please revisit this link: https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/ – paulsm4 Jun 21 '21 at 03:26
  • PS: You said "After it hits the "await client.SendAsync(message)" line, the yellow line just goes away and the website just keep loading..." It really sounds like the "connect" wasn't even successful. Q: Did it ever get to `response.EnsureSuccessStatusCode();`? I'm guessing "No." Please try to get a network trace and/or enable HTTP logging. Also take another look at https://josef.codes/you-are-probably-still-using-httpclient-wrong-and-it-is-destabilizing-your-software/ – paulsm4 Jun 21 '21 at 05:23
  • @paulsm4 I'm still in the midst of studying the link you provided yesterday. It's taking me some time as I'm not exactly familiar with http logging and the implementation of it. You're right, it never made past "await client.SendAsync(message)" line. On the other note, I found from a similar [post](https://stackoverflow.com/questions/19704432/await-httpclient-sendasynchttpcontent-is-non-responsive) that some of the users are able to get the `client.SendAsync(message)` to work by removing the `await`. Doing the same, I am able to capture the error message. – Andy Leong Jun 22 '21 at 11:22
  • InnerException = {"Authentication failed because the remote party has closed the transport stream."} Message = "The underlying connection was closed: An unexpected error occurred on a send." at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult() at Mall.PayPlugin.Pay.PaymentCore.d__19.MoveNext() – Andy Leong Jun 22 '21 at 11:25
  • @paulsm4 I did a quick search on the error and it turns out the security protocol is the underlying issue. My web application is actually running on .net 4.5 so by default it is using TLS 1.0 which is unacceptable for the API server. Specifying the security protocol, I am able to get a response from the server, however, still at the expense of disabling the `await`. I know that this is not the best practice and it'll probably be causing performance and crash issues. Enabling back the `await`, I'll be back to square one. Any advice on this? – Andy Leong Jun 22 '21 at 13:40
  • Cool - progress! Thank you for the update. I've taken the liberty of updating your post - I hope you don't mind. As you know, you should *NOT* be using TLS 1.0. That's the key issue. This might help: https://stackoverflow.com/a/45692875/421195 – paulsm4 Jun 22 '21 at 15:18
  • Yes, thank you so much @paulsm4 for updating my post for me. Yeah, the TLS issue definitely slipped my mind. At least now that the website is working, I'll be working on finishing the payment process flow before coming back and revisit this `await` issue. I'm not sure how this works. Shall I close this post first? – Andy Leong Jun 23 '21 at 12:40
  • SUGGESTION: Write an "answer". Describe 1) the problem, 2) what you found, 3) how you resolved it (at least "resolved" for now). Then "accept" it. That will "close" this post. As far as "await" - frankly, I'm not one who thinks "asynchronous I/O" should necessarily be used all the time, everywhere. Heck - it actually MASKED the problem here. Nevertheless, I don't see "await" and "TLS 1.2" as mutually exclusive - you should easily be able to do both with .Net 4.5. Ever better, maybe you can upgrade to a library where TLS 1.2 is the default. – paulsm4 Jun 23 '21 at 16:17

1 Answers1

2

Special thanks to @paulsm4 for helping, guiding and most importantly, inspiring me through solving this problem when I have almost given up.

The post and the comments should describe clearly the problem and my journey to the solution. I'm not going to repeat myself so I'm just going to share my code, which is working now for me.

private static readonly HttpClient client = new HttpClient();

private enum MySecurityProtocolType
{
    //
    // Summary:
    //     Specifies the Secure Socket Layer (SSL) 3.0 security protocol.
    Ssl3 = 48,
    //
    // Summary:
    //     Specifies the Transport Layer Security (TLS) 1.0 security protocol.
    Tls = 192,
    //
    // Summary:
    //     Specifies the Transport Layer Security (TLS) 1.1 security protocol.
    Tls11 = 768,
    //
    // Summary:
    //     Specifies the Transport Layer Security (TLS) 1.2 security protocol.
    Tls12 = 3072
}

public static async Task<string> SubmitPaymentTransaction (string reqJsonstr)
{
    try
    {
        HttpContent httpContent = new StringContent(reqJsonstr, Encoding.UTF8);
        httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

        ServicePointManager.SecurityProtocol = (SecurityProtocolType)(MySecurityProtocolType.Tls12 | MySecurityProtocolType.Tls11 | 
                    MySecurityProtocolType.Tls | MySecurityProtocolType.Ssl3);

        var message = new HttpRequestMessage();
        message.Content = httpContent;
        message.Method = HttpMethod.Post;
        message.RequestUri = new Uri($"https://payment.com/PayURL");

        HttpResponseMessage response = await client.SendAsync(message).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();
        string result = await response.Content.ReadAsStringAsync();

        return result;
    }
    catch(Exception error)
    {
        return error.ToString();
    }
    
}
Andy Leong
  • 41
  • 4