HttpClient handles redirects automatically. When you send a request, if the response contains a redirect status code (3xx) and redirect location, then it’ll send a new request to the redirect location.
You can turn off this auto-redirect behavior by passing in an HttpClientHandler with AllowAutoRedirect=false. This prevents it from following redirects automatically, and allows you to handle redirects manually if you want:
var handler = new HttpClientHandler()
{
AllowAutoRedirect = false
};
var client = new HttpClient(handler);
var response = await client.GetAsync("http://api.isevenapi.xyz/api/iseven/7/");
Console.WriteLine($"Status code = {(int)response.StatusCode}");
Console.WriteLine($"Redirect location = {response.Headers.Location}");
Code language: C# (cs)
Reminder: Be sure to reuse a single instance of HttpClient.
This outputs the following:
Status code = 301
Redirect location = https://api.isevenapi.xyz/api/iseven/7/
Code language: plaintext (plaintext)
This is an example of a typical HTTP to HTTPS redirect.
Table of Contents
Default redirect behavior
HttpClient uses the RedirectHandler class for dealing with redirects. I’ll explain the default behavior below.
Redirect conditions
It will redirect based on the following conditions:
- Response.StatusCode is 300, 301, 302, 303, 307, or 308.
- Response.Headers.Location (URI) is populated. Note: It can handle absolute and relative URIs.
- It will not do an insecure HTTPS to HTTP redirect.
It will do up to 50 redirects (configurable).
If you get a redirect response code (3xx) back at the end, this means the redirect conditions were not met, it exceeded the max redirect attempts, or there’s something wrong in the redirect data. If this is happening to you, you can troubleshoot it by turning off auto-redirects and examining the response(s).
Original request headers and content
The original request headers (except the auth header) and content are retained and sent in redirect requests, but the query string parameters aren’t retained automatically.
If you’re handling redirects manually, you could combine the original query string parameters with the redirect location URI (perhaps using a UriBuilder).
Note: HttpClient internally reuses the same HttpRequestMessage object for redirects. If you try to do this yourself, you’ll get an exception “Cannot send the same request message multiple times.” The framework uses the internal MarkAsRedirected() method to be able to reuse the same request object repeatedly. If you really want to reuse the same request object yourself, you can use reflection to call the internal method. It’s probably simpler to just create a new request object.
Forced GETs
For certain combinations of status codes and HTTP methods, it will send the redirect as a GET instead of using the original HTTP method. For example, if you get a 302 response code when doing a POST request, it’ll do the redirect as a GET.
This is clearly a problem if the redirect location doesn’t allow a GET. In this scenario, you’d get the following misleading exception:
System.Net.Http.HttpRequestException: Response status code does not indicate success: 405 (Method Not Allowed).
It’s misleading because you sent a POST request, which was allowed. It sent a redirect request as a GET, which was disallowed. It’d only be obvious if you knew a redirect happened. It would be much more clear if the error message said something about it failing during a redirect attempt.
I’m not sure about the reasoning behind this Forced GET behavior. This is a pretty good example of why it might be better to disable auto-redirects. That way you avoid unintended behavior like this.
Check if a request was automatically redirected
One simple way to check if your request got automatically redirected is by comparing the original request URI with the response’s request URI. If they’re different, it got redirected.
var client = new HttpClient();
var requestUri = new Uri("http://api.isevenapi.xyz/api/iseven/7/");
var response = await client.GetAsync(requestUri);
if (requestUri != response.RequestMessage.RequestUri)
{
Console.WriteLine($"Request was redirected to {response.RequestMessage.RequestUri}");
}
Code language: C# (cs)
Note: This is a heuristic.
This outputs the following:
Request was redirected to https://api.isevenapi.xyz/api/iseven/7/
Code language: plaintext (plaintext)
Limit the number of redirects
By default, HttpClient will do up to 50 redirects. You can control this with the HttpClientHandler.MaxAutomaticRedirections setting.
Here’s an example:
var handler = new HttpClientHandler()
{
MaxAutomaticRedirections = 1
};
var client = new HttpClient(handler);
var response = await client.GetAsync("https://localhost:12345/movies/find");
response.EnsureSuccessStatusCode();
Code language: C# (cs)
When this exceeds the max redirect attempts, it’ll return the response from the last redirect request it attempted. Since this response will have a 3xx status code, EnsureSuccessStatusCode() will throw an exception.
HTTP to HTTPS redirects fails locally
I have an ASP.NET Core web API running locally. It’s configured to do HTTP to HTTPS redirects. To test redirect behavior, I sent the following request:
var client = new HttpClient();
var response = await client.GetAsync("http://localhost:12345/movies/find");
Code language: C# (cs)
It fails with the following exception:
System.Net.Http.HttpRequestException: An error occurred while sending the request.
—> System.IO.IOException: The response ended prematurely.
This error doesn’t have to do with HttpClient’s auto-redirect behavior. The error is happening before that. I also tried using Postman and got the same error. So this problem isn’t specific to HttpClient. I also tried running the web API in a local Windows Sandbox and had the same issue.
This is mostly just a heads up. I didn’t dig deeper into this. If you’re just wanting to test HTTP to HTTPS redirects, be sure the programs (client and web API) are on different machines. And if you have two programs on the same machine that need to talk, use HTTPS (to make HTTP to HTTPS redirects a non-issue).
Awesome article
Thanks!
I liked your examples. It helped put me on the right track. I found it was easier to just turn off the AllowRedirect feature and look for a 302 status code if you’re expecting a redirect. Either way works. This was a little faster because you end up doing less requests since it stops at the redirect.
using var client = new HttpClient(new HttpClientHandler()
{
UseDefaultCredentials = true,
AllowAutoRedirect = false
});
using var HttpResponseMessage response = await client.GetAsync(uri);
if(response.StatusCode == HttpStatusCode.Redirect) {
Console.WriteLine(response.Headers.GetValues("Location").First());
}
Hi Charles. I’m glad this helped you. Thanks for sharing that helpful code snippet as well.
Your article helped me understand what was going on with a POST redirecting to a GET – was not what I was expecting.
I did a little digging and found the spec on http redirects:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#permanent_redirections
A status code of 301,302…and a few more will cause the behavior we’re seeing but if a 307, 308 is received then the redirect honors the original request – a POST redirects as a POST with the original body intact. I tested this using HttpClient and curl and getting a 308 does the correct redirect.
I’m glad this helped you point you in the right direction. “Forced GETs” can definitely be a little confusing!
In your bullet point
– Response.StatusCode is 300, 301, 303, 303, 307, or 308.
Was the first “303” intended to be 302?
Ah yes, thank you! I fixed it.