.NetCore HttpClient发送请求的时候为什么自动带上了一个RequestId头部?

奇怪的问题

最近在公司有个系统需要调用第三方的一个webservice。本来调用一个下很简单的事情,使用HttpClient构造一个SOAP请求发送出去拿到XML解析就是了。可奇怪的是我们的请求在运行一段时间后就会被服务器504给拒绝掉了。导致系统无法使用,用户叫苦连天。古怪就古怪在这个问题不是每次都会出现,是隔三差五的查询,每次修改完代码发布上去以为好了, 过了两天又不行了,简直让人奔溃。

Postman测试

在反复调试代码无果的情况下,我怀疑是对方服务器的问题。于是拿出Postman往对方服务器发送请求测试。postman测试一测就测出问题了,不管发送什么,服务器全部给出了504的响应。因为在浏览器里访问webservice的首页是可以的,但是为什么在postman上面就不行了呢?于是我开始反复检查postman的请求有何不同,到这里感觉离发现问题不远了。在反复查看下我开始怀疑是postman的一个头部的问题:

Postman-Token: 4d407574-636b-9343-8216-7f2845cbeef1

postman每次发送请求的时候都会带上一个叫做postman-token的头部。于是我把这个头部给禁用了再试一次,果断成功了。在反复测试下终于明白了,对方服务器应该有防护,只要http请求里带有自定义的头部就会直接给出504的响应,直接拒绝请求。至此服务器拒绝请求的原因终于明了了。

fiddler监控

但是,我们的代码发送请求的时候并没有带上任何自定义的头部啊。莫非.NET Core会在发送请求的时候带上什么头部吗?于是在服务器上安装fiddler,把请求通过fiddler代理转发出去,然后监控http请求的头部。当系统再次出现问题的时候 果断上去查看fiddler。一看果然发现了问题,所有被拒绝的请求都带上了一个叫“Request-Id”的头部。

当时我是震惊的,.NetCore居然会自说自话给我加上一个头部?如果不是亲身发现,打死我也不会相信的。或许你看到这里也还是不相信,心里在想一定是我搞错了吧。

Request-Id头部到底哪里来的?

这个问题真是百思不得其解,于是开始请教google。很快在.net core runtime的github上的issues发现一个同样的问题:
HttpClient automatically adds Request-Id HTTP header
提问的人说使用HttpClient发送请求的时候莫名其妙加上了一个Request-Id,跟我情况一毛一样。
于是乎有人开始讨论。有人说HttpClient不可能自己加上Request-Id这个头部的,下面的老哥直接打脸,说:事实上会的,还给出了源码的位置。笑哭!后来还有开发者回复这个功能是内置的,是为了分布式追踪。
既然源码都给出来了,直接从上面老哥给出的源码位置开始追源码。下面大概说一下源码:
HttpClient默认构造函数:

  public HttpClient(): this(new HttpClientHandler()){}

继续看里面的HttpClientHandler:

   protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,CancellationToken cancellationToken){return DiagnosticsHandler.IsEnabled() ?_diagnosticsHandler.SendAsync(request, cancellationToken) :_socketsHttpHandler.SendAsync(request, cancellationToken);}

HttpClientHandler发送请求的时候会判断是否使用diagnosticsHandler来发送请求。继续看diagnosticsHandler的代码:

  private static void InjectHeaders(Activity currentActivity, HttpRequestMessage request){if (currentActivity.IdFormat == ActivityIdFormat.W3C){if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName)){request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceParentHeaderName, currentActivity.Id);if (currentActivity.TraceStateString != null){request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.TraceStateHeaderName, currentActivity.TraceStateString);}}}else{if (!request.Headers.Contains(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName)){request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName, currentActivity.Id);}}// we expect baggage to be empty or contain a few itemsusing (IEnumerator<KeyValuePair<string, string?>> e = currentActivity.Baggage.GetEnumerator()){if (e.MoveNext()){var baggage = new List<string>();do{KeyValuePair<string, string?> item = e.Current;baggage.Add(new NameValueHeaderValue(WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)).ToString());}while (e.MoveNext());request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.CorrelationContextHeaderName, baggage);}}}private static readonly DiagnosticListener s_diagnosticListener =new DiagnosticListener(DiagnosticsHandlerLoggingStrings.DiagnosticListenerName);#endregion}

终于找到关键的位置了有个叫InjectHeaders的方法里面有这么一句 request.Headers.TryAddWithoutValidation(DiagnosticsHandlerLoggingStrings.RequestIdHeaderName, currentActivity.Id);其中DiagnosticsHandlerLoggingStrings.RequestIdHeaderName是个常量,它的值就是"Request-Id"。
到这里是谁带上的Request-Id头部的问题终于石锤了。

复现问题

原因找到了,于是开始测试解决办法。解决问题的第一步是先复现问题。正常情况下你使用HttpClient发送请求时不会带上这个头部的。要让本地发送的请求也带上这个头部也不是件容易的事。经过查看源代码发现其实是跟.net core的Diagnostics机制有关。由于源码逻辑比较复杂,直接给出会带上头部的代码:
首先定义一个Observer:

    public class MyObserver<T> : IObserver<T>{private Action<T> _next;public MyObserver(Action<T> next){_next = next;}public void OnCompleted(){}public void OnError(Exception error){}public void OnNext(T value) => _next(value);}

订阅HttpHandlerDiagnosticListener:

    DiagnosticListener.AllListeners.Subscribe(new MyObserver<DiagnosticListener>(listener =>{//判断发布者的名字if (listener.Name == "HttpHandlerDiagnosticListener"){//获取订阅信息listener.Subscribe(new MyObserver<KeyValuePair<string, object>>(listenerData =>{System.Console.WriteLine($"监听名称:{listenerData.Key}");dynamic data = listenerData.Value;}));}}));

当我们订阅HttpHandlerDiagnosticListener的时候HttpClient发送的请求就会带上这个头部。这个设计的真的比较变态,因为DiagnosticListener.AllListeners是静态的,所以它的影响是全局的。也就是说我这里订阅了一个监听,会导致整个程序中所有的HttpClient都开始带上这个头部。
这也解释了为何我们的程序运行一段时间之后才带上Request-Id的头部。因为我们程序中其它模块,或者引用的三方库的在达到某种状态的时候会开始订阅HttpHandlerDiagnosticListener这个监听,导致我请求webservice的代码也带上了这个头部。

解决问题

问题的原因也找到了,本地也复现了,现在我们要开始真正的解决问题了。经过google跟查看源码,要让HttpClient不发送这个Request-Id头部有几种办法。

  1. 方法1

设置System.Net.Http.EnableActivityPropagation开关为false

string switchName = "System.Net.Http.EnableActivityPropagation";
AppContext.SetSwitch(switchName, false);
  1. 方法2 配置环境变量DOTNETSYSTEMNETHTTPENABLEACTIVITYPROPAGATIO=false

  2. 方法3

    public class DisableActivityHandler : DelegatingHandler{public DisableActivityHandler(HttpMessageHandler innerHandler) : base(innerHandler){}protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){Activity.Current = null;return await base.SendAsync(request, cancellationToken);}}var httpClient = new HttpClient(new DisableActivityHandler(new HttpClientHandler()));

该方法定义一个DisableActivityHandler再构造HttpClient,在每次发送请求的时候都把Activity.Current置空。

总结

最近被这个Request-Id折腾了很久。这里忍不住要吐槽下,这个内置的功能真的好吗,强力插入自定义头部,有考虑过防火墙的感受吗?或者是不是可以让开发者主动选择是否计入Diagnostic统计,而不是某一处开始订阅就全部请求都添加头部,毕竟我们无法控制第三方的库是否有什么骚操作。如果要关闭这个Diagnostic是不是可以在HttpClient实例上直接给出一个明确的开关让开发者关闭它,而不是需要配置什么环境变量。

ps:如果是使用HttpWebRequest类发送请求同样有这个问题,因为HttpWebRequest发送请求的时候就是用的HttpClient。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NETCore小技巧:使用测试用户中间件

哈喽大家好&#xff0c;这篇文章其实很早就想写了&#xff0c;因为一直会有小伙伴问到&#xff0c;但是我却始终拿不到好的方案&#xff0c;最近在录制《eShopOnContainer微服务架构》的视频&#xff0c;碰巧就看到了微软官方的代码中也有这方面的需求&#xff0c;而且和我的需…

【招聘(深圳)】华强方特文化科技集团 .NET工程师

.NET高级开发工程师&#xff08;18-25K&#xff09;岗位职责&#xff1a;负责系统需求分析与设计&#xff1b;根据业务确定实现方案&#xff1b;对现有系统缺陷提出优化方案&#xff1b;负责系统关键功能开发及维护&#xff0c;保障系统的正常运行&#xff1b;带领指导团队开发…

11座城市,58个.NET最新岗位速览,内推直通面试官!

十一月风雪客&#xff0c;十二月乘衣归&#xff01;各个大厂秋招进行时&#xff0c;你行动了吗&#xff1f;借着这阵风&#xff0c;今天为大家提供一批.NET开发岗位内推&#xff01;58个优质的.NET开发岗位年薪过万到百万不等&#xff0c;总有一个适合你&#xff01;包含全国各…

pdo mysql_PDO MySQL

PDO MySQL如果文章有成千上万篇&#xff0c;该怎样保存&#xff1f;数据保存有多种方式&#xff0c;比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择&#xff0c;做Web一般采用MySQL&#xff0c;本书也以MySQL为例。自学&#xff1a;1天。假…

C# 8: 可变结构体中的只读实例成员

在之前的文章中我们介绍了 C# 中的 只读结构体&#xff08;readonly struct&#xff09;[1] 和与其紧密相关的 in 参数[2]。今天我们来讨论一下从 C# 8 开始引入的一个特性&#xff1a;可变结构体中的只读实例成员&#xff08;当结构体可变时&#xff0c;将不会改变结构体状态的…

部署Dotnet Core应用到Kubernetes(一)

最近闲了点&#xff0c;写个大活&#xff1a;部署Dotnet应用到K8s。写在前边的话一直想完成这个主题。但这个主题实在太大了&#xff0c;各种拖延症的小宇宙不时爆发一下&#xff0c;结果就拖到了现在。这个主题&#xff0c;会是一个系列。在这个系列中&#xff0c;我会讨论将应…

JAVA实验报告九异常处理_Java课后练习9(异常处理)

动手动脑1:import javax.swing.*;class AboutException {public static void main(String[] a){int i1, j0, k;ki/j;try{k i/j; // Causes division-by-zero exception//throw new Exception("Hello.Exception!");}catch ( ArithmeticException e){System.out.print…

java 实现 指派_TAP任务指派问题的汇编实现

近六周的课程设计&#xff0c;编了一个四百行的汇编程序&#xff0c;编的过程很不顺利&#xff0c;遇到种种意想不到的困难&#xff0c;但最终能够实现&#xff0c;可谓欣喜若狂&#xff0c;这期间学到了好多好多&#xff0c;遇到问题怎么精下心来解决&#xff0c;同时对汇编的…

.NET 5.0正式发布,有什么功能特性(翻译)

我们很高兴今天.NET5.0正式发布。这是一个重要的版本—其中也包括了C# 9和F# 5大量新特性和优秀的改进。微软和其他公司的团队已经在生产和性能测试环境中开始使用了。这些团队向我们反馈的结果比较令人满意&#xff0c;它证明了对性能提升及降低Web应用托管成本的机会有积极的…

简单聊聊C#中lock关键字

为了避免多个线程同时操作同一资源&#xff0c;引起数据错误&#xff0c;通常我们会将这个资源加上锁&#xff0c;这样在同一时间只能有一个线程操作资源。在C#中我们使用lock关键字来锁定资源&#xff0c;那lock关键字是如何实现锁定的呢&#xff1f;我们先看一段代码&#xf…

idea如何导入java工程_Eclipse java web项目 ,导入IntelliJ IDEA 完整操作!

或许你用惯了Eclipse&#xff0c;有点排斥其他工具了&#xff0c;你写框架的时候&#xff0c;编译速度是不是特别慢啊&#xff1f;有时候还超过45秒&#xff0c;自动取消运行&#xff01;有时候代码是正常的&#xff0c;却无端端报错&#xff1f;下午吃个饭回来又好了&#xff…

行业思考 | 互联网对传统行业的降维打击

【行业思考】| 作者 / Edison Zhou这是EdisonTalk的第301篇原创内容在周一发布的推文《我在传统行业做数字化转型之预告篇》中&#xff0c;我提到互联网的发展和和竞争对传统行业起到了降维打击的作用&#xff0c;于是就有童鞋私下问我&#xff0c;为何这么说。今天就跟你聊聊这…

BCVP开发者说第一期:Destiny.Core.Flow

沉静岁月&#xff0c;淡忘流年1项目简介Destiny.Core.FlowDestiny.Core.Flow是基于.NetCore平台&#xff0c;轻量级的模块化开发框架&#xff0c;Admin管理应用框架&#xff0c;旨在提升团队的快速开发输出能力&#xff0c;由常用公共操作类&#xff08;工具类、帮助类&#xf…

.NET Core 取消令牌:CancellationToken

在 .NET 开发中&#xff0c;CancellationToken&#xff08;取消令牌&#xff09;是一项比较重要的功能&#xff0c;掌握并合理的使用 CancellationToken 可以提升服务的性能。特别在异步编程中&#xff0c;我常常会以创建 Task 的方式利用多线程执行一些耗时或非核心业务逻辑&a…

java char short区别_java 彻底理解 byte char short int float long double

遇到过很多关于 数值类型范围的问题了&#xff0c;在这做一个总结&#xff0c;我们可以从多方面理解不同数值类型的所能表示的数值范围在这里我们只谈论 java中的数值类型首先说byte&#xff1a;这段是摘自jdk中 Byte.java中的源代码从这里可以看出 byte的取值范围&#xff1a;…

程序员过关斩将--从未停止过的系统架构设计步伐

“首先&#xff0c;这篇文章肯定会得罪一些人“其次&#xff0c;此文只代表我个人的意见&#xff0c;仅供参考从分层说起谈到系统架构的分层和系统领域边界的划分&#xff0c;每个架构师&#xff0c;每个技术经理&#xff0c;甚至每个程序员都有自己的一套想法。无论是怎么样的…

BCVP第2期:项目已完成升级.NET5.0

(是时候拿出来这种图了)1开心的锣鼓想必这两天最热闹的几个词语&#xff0c;就是c#9.0、.net5.0还有conf大会了吧&#xff0c;当然还有大一统。其实&#xff0c;早在2019年年中&#xff0c;就已经引入了.NET5.0了&#xff0c;然后从2020-03-16开始&#xff0c;就一直在说.NET5.…

如何在ASP.NetCore增加文件上传大小

关注架构师高级俱乐部开启架构之路不定期福利发放哦~架构师高级俱乐部读完需要7分钟速读仅需 3 分钟/ 如何在核心中增加文件 ASP.NET 大小 /从ASP.NET 2.0开始最大请求正文大小限制为30MB &#xff08;28.6 MiB&#xff09;。在正常情况下&#xff0c;无需增加 HTTP 请求 body …

java完全二叉树最小堆_Java实现最小堆一

Java实现最小堆一堆是一种经过排序的完全二叉树&#xff0c;其中任一非终端节点的数据值均不大于(或不小于)其左孩子和右孩子节点的值。最大堆和最小堆是二叉堆的两种形式。最大堆&#xff1a;根结点的键值是所有堆结点键值中最大者。最小堆&#xff1a;根结点的键值是所有堆结…

一个 Task 不够,又来一个 ValueTask ,真的学懵了!

一&#xff1a;背景 1. 讲故事前几天在项目中用 MemoryStream 的时候意外发现 ReadAsync 方法多了一个返回 ValueTask 的重载&#xff0c;真是日了狗了&#xff0c;一个 Task 已经够学了&#xff0c;又来一个 ValueTask&#xff0c;晕&#xff0c;方法签名如下&#xff1a;publ…