HttpClient 禁用自动重定向
Intro
前段时间写了一个小工具来帮助我们简化一个每个月一次的小任务,每个月我们公司的 BI Team 会给我们上个月访问量比较高的博客文章的 url,然后我们会根据 BI 提供博客的 url 去找到对应的博客 id,然后更新到配置中,网站会读取这个配置来显示比较受欢迎的博客文章
Redirection
我们的博客文章有一个特点,如果访问的地址只有博客文章的路径,会自动跳转到带 id 的地址,比如你访问:
https://cn.iherb.com/blog/8-natural-remedies-for-heartburn-and-acid-reflux
会自动跳转到 https://cn.iherb.com/blog/8-natural-remedies-for-heartburn-and-acid-reflux/347
,后面新增的 347
就是对应的博客文章的 id,BI team 的同事给到我们的就是示例中的 8-natural-remedies-for-heartburn-and-acid-reflux
就是服务器端做了一个重定向,通过 浏览器的 Network 我们可以看到发生了一个 302 重定向
HttpClient AutoRedirect
根据上面的重定向,我们使用代码是不是也可以做呢,这样我们就可以做到比较自动化了,来尝试一下
var articlePath = "/8-natural-remedies-for-heartburn-and-acid-reflux/";
var httpClient = new HttpClient()
{BaseAddress = new Uri("https://cn.iherb.com/blog/")
};
using var res = await httpClient.GetAsync(articlePath[0].TrimStart('/'));
Console.WriteLine(res.RequestMessage.RequestUri.ToString());
Console.WriteLine(res.StatusCode);
猜一猜上面的代码输出结果是什么?
输出结果如下:
和你预想的结果一样吗,可以看到输出的请求地址实际上并不是我们请求的地址,这是因为默认地,HttpClient 会自动跟随重定向,如果服务器重定向了一个请求,HttpClient 会读取 response header 中的 Location 来请求下一个地址,请求地址和 response 显示的是最终一次请求的信息。
你也可以选择禁用自动重定向,下面就是一个禁用重定向的示例:
using var httpClient = new HttpClient(new HttpClientHandler()
{AllowAutoRedirect = false
})
{BaseAddress = new Uri(BaseUrl)
};
using var res1 = await httpClient.GetAsync(articlePath[0].TrimStart('/'));
Console.WriteLine(res1.RequestMessage.RequestUri.ToString());
Console.WriteLine(res1.StatusCode);
我们可以通过指定 HttpClientHandler
的 AllowAutoRedirect
属性来禁用自动重定向,禁用后的输出结果如下:
可以看到这个请求的请求地址是我们实际请求的地址,没有发生重定向
那么针对哪些请求会做重定向处理呢?通过查阅源码发现对于这些响应会尝试重定向 300
/301
/302
/307
/308
, 可以参考:https://github.com/dotnet/runtime/blob/74246130d505c9243396f4a8837634e8ab3065bb/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs#L87
Practice
我们已经知道 HttpClient 会自动重定向,而且我们只需要拿到重定向后的地址即可,那我们就可以禁用自动重定向,直接读取 Response Header 里的 Location 中的值,就可以拿到要重定向的 URL 了,于是就有了下面的代码来获取博客文章的 id
private async Task<int> GetArticleId(string path)
{using var response = await httpClient.GetAsync(path.TrimStart('/'));var statusCode = (int)response.StatusCode;if(statusCode != 302){return -1;}var newLocation = response.Headers.Location.ToString();int.TryParse(newLocation[(newLocation.LastIndexOf('/') + 1)..], out var articleId);return articleId;
}
通过上面的代码我们就可以相对高效的拿到博客文章的 id,再进一步整理一下构造成我们最终配置所需要的格式
var articleIds = await Task.WhenAll(articlePath.Select(path => GetArticleId(path)));
var json = new
{articlelist = articleIds.Select(x=> new{ id=x })
}.ToJson();
这样以后我们再统计只需要拷贝一下新的数据,跑一下脚本就可以了~~
More
因为我们只关注 Response Header ,所以我们也可以把上面的 GET 请求换成 HEAD 请求,这样返回的响应就只有 Header,没有 Body 更加简洁一些
从业务的角度来看,我们可以尝试让别的 Team 同事直接提供一个 API 来返回这样的数据,这样我们就不必这样搞了 ^^
References
https://github.com/WeihanLi/SamplesInPractice/blob/master/HttpClientTest/NoAutoRedirectSample.cs
https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Net/Http/HttpHandlerDefaults.cs
https://github.com/dotnet/runtime/blob/74246130d505c9243396f4a8837634e8ab3065bb/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs#L87
https://stackoverflow.com/questions/14731980/using-httpclient-how-would-i-prevent-automatic-redirects-and-get-original-statu
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.allowautoredirect?WT.mc_id=DT-MVP-5004222&view=net-5.0
https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.maxautomaticredirections?view=net-5.0&WT.mc_id=DT-MVP-5004222