建设部网站注册查询,怎样免费建公司网站,好玩的电脑网页游戏,申请自助建站奇怪的问题最近在公司有个系统需要调用第三方的一个webservice。本来调用一个下很简单的事情#xff0c;使用HttpClient构造一个SOAP请求发送出去拿到XML解析就是了。可奇怪的是我们的请求在运行一段时间后就会被服务器504给拒绝掉了。导致系统无法使用#xff0c;用户叫苦连… 奇怪的问题最近在公司有个系统需要调用第三方的一个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 TaskHttpResponseMessage 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 (IEnumeratorKeyValuePairstring, string? e currentActivity.Baggage.GetEnumerator()){if (e.MoveNext()){var baggage new Liststring();do{KeyValuePairstring, 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 MyObserverT : IObserverT{private ActionT _next;public MyObserver(ActionT next){_next next;}public void OnCompleted(){}public void OnError(Exception error){}public void OnNext(T value) _next(value);}
订阅HttpHandlerDiagnosticListener DiagnosticListener.AllListeners.Subscribe(new MyObserverDiagnosticListener(listener {//判断发布者的名字if (listener.Name HttpHandlerDiagnosticListener){//获取订阅信息listener.Subscribe(new MyObserverKeyValuePairstring, 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设置System.Net.Http.EnableActivityPropagation开关为falsestring switchName System.Net.Http.EnableActivityPropagation;
AppContext.SetSwitch(switchName, false);
方法2 配置环境变量DOTNETSYSTEMNETHTTPENABLEACTIVITYPROPAGATIOfalse方法3 public class DisableActivityHandler : DelegatingHandler{public DisableActivityHandler(HttpMessageHandler innerHandler) : base(innerHandler){}protected override async TaskHttpResponseMessage 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。