Headers from PostStringToURL when 429 response

I’m trying to get a response header from a POST using the HTTPUtils PostStringToURL when it returns a 429, but it seems to throw an exception leaving me without access to the headers.

For example:

string retryAfterResponseHeader = null;
string URL = "http://example.com/test";
URL.PostStringToUrl("Test", responseFilter: res => retryAfterResponseHeader = res.GetHeader("Retry-After"));

If the service at http://example.com/test returns a 200, I do get the header, but when 429 is returned an exception is thrown and no header is obtained.

According to section 4 of RFC 6585, a 429 response MAY include a Retry-After header (https://www.rfc-editor.org/rfc/rfc6585#section-4)

Is there a way to get response headers from response that returns a 429 response?

Thanks!

I think there might be a an order issue, I believe you are getting a 429 in the response filter, but yes the PostStringToUrl is still throwing an exception which is the designed behavior since it uses the HttpClient response EnsureSuccessStatusCode method. The responseFilter is invoked before this happens, so you can read the header. For example, a client will run code very similar to yours:


using ServiceStack;

string? retryAfterResponseHeader = null;
string URL = "https://localhost:5001/error";
URL.PostStringToUrl("Test", responseFilter: res =>
{
    retryAfterResponseHeader = res.GetHeader("Retry-After");
    Console.WriteLine("Retry-After: " + retryAfterResponseHeader);
});

Console.ReadKey();

The output we see when running the client while our Service is running is:

Retry-After: 60

Followed by the stack trace. If you try/catch your URL.PostStringToUrl you should see the same, let us know how you go.

Nope, no go for me.

I’m using .NET Framework 4.8 and SS 6.11.0

This is the code + console output:

Using Postman to hit the same route:

If you can create a reproduction on GitHub I can take a closer look. Also to sanity test against just not having any value in the Retry-After header, can you log something unique in the responseFilter action?

Also to sanity test against just not having any value in the Retry-After header, can you log something unique in the responseFilter action?

Sure:

If you can create a reproduction on GitHub I can take a closer look.

Will do. I’m travelling interstate for the next 10 days so it might take me about that long to pull that together if I can’t get it done in the next few minutes.

More like:

URL.PostStringToUrl("{}", responseFilter: res =>
{
    retryAfterResponseHeader = res.GetHeader("Retry-After");
    Console.WriteLine(Guid.NewGuid().ToString());
});

Probably nothing, but that will more ensure order of events.

No worries, wouldn’t be needed if I could reproduce it myself, but can’t seem to, if that changes will post my findings as well. Just trying to rule out differences in response from the server, and other variables etc.

Project for a basic web service to return a 429 and a response header “Retry-After” is here : mikesheen/HTTPUtilsFourTwoNine (github.com)

It’s just the standard ServiceStack Web template with a little global request filter added to return a 429 and set the response header for all requests.

Project to use HTTPUtils to post to the URL and which does not get the Retry-After response header is here : mikesheen/TestHttpUtilsFourTwoNine (github.com)

Thanks!

Hi @mikesheen ,

Looks like this issue is due to WebRequest vs HttpClient. Using the following project with your same example captures the 429, but it looks like this might be a limit since the exception will fire before any ResponseFilters are processed when it comes to the WebRequest version on the .NET Framework as opposed to the HttpClient implementation on .NET Core.

A work around would be to access the information via the Exception if possible. Eg,

public static class Utils
{
    /// <summary>
    /// Reads Response content in string from WebException
    /// Based on https://stackoverflow.com/a/74541238
    /// </summary>
    /// <param name="webException"></param>
    /// <returns></returns>
    public static (HttpStatusCode statusCode, string? responseString,WebHeaderCollection responseHeaders) GetResponseStringNoException(this WebException webException)
    {
        if (webException.Response is HttpWebResponse response)
        {
            Stream responseStream = response.GetResponseStream();
            StreamReader streamReader = new StreamReader(responseStream, Encoding.Default);

            string responseContent = streamReader.ReadToEnd();
            HttpStatusCode statusCode = response.StatusCode;

            streamReader.Close();
            responseStream.Close();
            response.Close();

            return (statusCode, responseContent,response.Headers);
        }
        else
        {
            return (HttpStatusCode.InternalServerError, null,null);
        }
    }
}

And in the client, you can use:

catch (Exception ex)
{
    if(ex is WebException webEx)
    {
        var result = webEx.GetResponseStringNoException();
        if (result.statusCode == (HttpStatusCode)429)
        {
            Console.WriteLine(result.responseString);
            retryAfterResponseHeader = result.responseHeaders.Get("Retry-After");
        }
    }
    Console.WriteLine($"Exception encountered: {ex.Message}");
}

Definitely not ideal, but due to how HttpWebRequest works on the .NET Framework, not another option I can currently see.

Thanks - just a few minutes before you posted your answer, I had already moved away from using HttpUtils to using the .NET Framwework’s HttpClient and that’s all working fine for me.

Is there any reason why the .NET Framework flavour of ServiceStack HttpUtils uses WebRequest instead of HttpClient like the .NET Core version of ServiceStack HttpUtils does?

HttpUtils was originally developed to use HttpWebRequest initially and the .NET Framework continues to use the Windows HttpWebRequest for backwards compatibility. In .NET Core HttpWebRequest is a completely different implementation (than the Windows only version of HttpWebRequest in .NET Framework) that’s an unsupported wrapper around HttpClient which has since been deprecated which is why the .NET 6 version implementation was rewritten to use HttpClient in order for HttpUtils to use a supported HttpClient implementation.