奇怪的问题

最近在公司有个系统需要调用第三方的一个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 items
using (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

    配置环境变量DOTNET_SYSTEM_NET_HTTP_ENABLEACTIVITYPROPAGATIO=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。

关注我的公众号一起玩转技术

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

  1. 使用HttpClient发送请求、接收响应

    使用HttpClient发送请求.接收响应很简单,只要如下几步即可. 1.创建HttpClient对象.  CloseableHttpClient httpclient = HttpClients.c ...

  2. httpClient 发送请求后解析流重用的问题(HttpEntity的重用:BufferedHttpEntity)

    使用场景: 项目中使用httpClient发送一次http请求,以流的方式处理返回结果,开始发现返回的流只能使用一次,再次使用就会出错,后来看了一些解决方案,EntityUtils.consume(r ...

  3. 使用HttpClient发送请求接收响应

    1.一般需要如下几步:(1) 创建HttpClient对象.(2)创建请求方法的实例,并指定请求URL.如果需要发送GET请求,创建HttpGet对象:如果需要发送POST请求,创建HttpPost对 ...

  4. HttpClient 发送请求和参数

    发送请求 没有参数 private static void getData() { String timeStamp = String.valueOf(System.currentTimeMillis ...

  5. 解决asp.net web api时间datetime自动带上带上的T和毫秒的问题

    今天用asp.net web api写微信小程序的接口时遇到一个问题. 返回的model中的datetime类型的字段自动转换成了“2014-11-08T01:50:06:234”这样的字符串,带上的 ...

  6. 记录下httpclient 发送请求 服务端用@RequestBody 自动接收参数 报415

    注解是post方式,那么检查以下内容:1. 你是否用了post请求2. 请求是否发送了数据3. 请求内容格式需要是 application/json .jquery 设置 contentType,-- ...

  7. httpclient发送请求的几种方式

    package asi; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; ...

  8. Android发送请求到不同的Servlet,但都是一个Servlet处理

    错误原因,在Servlet文件中 @WebServlet("/ServletForGETMethod") 与实际的ServletForQUERYMethod 文件名不符. @Web ...

  9. git-如何不写注释能自动带上修改文件信息

    背景:每次提交git,都要写注释,有些情况注释不太好写,或者根本没有必要写,这时可以通过自动加注释方法,比如可以追加修改了哪些文件 解决:通过shell脚本,在脚本里面写git命令,add commi ...

随机推荐

  1. 数字PLL,什么是数字PLL

    来源:http://www.elecfans.com/baike/bandaoti/bandaotiqijian/20100323203306.html 数字PLL,什么是数字PLL 数字PLL PL ...

  2. 【题解】CF413C Jeopardy!

    \(\color{blue}{Link}\) \(\text{Solution:}\) 首先,显然的策略是把一定不能翻倍的先加进来.继续考虑下一步操作. 考虑\(x,y\)两个可以翻倍的物品,且\(a ...

  3. apt-get 安装软件时出现:“文件尺寸不符” 问题

    报错信息 命中:1 http://packages.deepin.com/deepin panda InRelease 命中:2 http://linux.teamviewer.com/deb sta ...

  4. # vue 如何通过前端来导出excel表格

    在做一些简单的demo时,偶尔会遇到导出excel表格.如果请后端帮忙的话 比较浪费时间,那么前端如何导出excel表格,下面就来记录一下之前使用到的案例 一.安装依赖 npm i file-save ...

  5. git的项目完整操作

    今天来说下项目中git 的使用,针对常规操作: 然后执行  git status  可以看到目前的状态: 再执行添加操作      git add . 添加所有文件 接着执行提交命令  git com ...

  6. C语言中数组与指针的异同之处!你不知道的编程奥秘~

    C语言的数组和指针一直是两个容易混淆的东西,当初在学习的时候,也许为了通过考试会对指针和数组的一些考点进行突击,但是很多极其细节的东西也许并不是那么清楚.本篇侧重点在于分析数组与指针的关系,什么时候数 ...

  7. docker启动服务---------------kafka+zookeeper

    docker run -d --name zookeeper -p 2181:2181 wurstmeister/zookeeperdocker run -d --name kafka -p 9092 ...

  8. git 撤销push到远程仓库的无用commit

    一 回退代码 git reset <版本号> --soft // 软回退 - 所有的commit修改都被撤销了,且修改的代码统一撤回到暂存区 git reset <版本号> - ...

  9. IDEA 中项目代码修改后不自动生效,需要执行 mvn clean install 才生效

    问题描述 之前项目运行好好的,代码修改完之后会自动编译,编程体验很好. 有一天发现每次修改代码后需要重新使用mvn clean install命令重新编译,异常麻烦. 检查了 IDEA 的配置,已经配 ...

  10. centos 7安装搜狗输入法之失败案例

    最近打算在旧电脑上安装centos用,先用虚拟机做个测试 默认的intelligence pinyin不太好用,打算安装搜狗输入法.在网上找了几篇看起来还"不错"的, 基本上来第一 ...