前言

今天,在对接一个第三方平台开放接口时遇到一个很棘手的问题,根据接口文档组装好报文,使用HttpClient发起POST请求时一直超时,对方服务器一直不给任何响应。

发起请求的代码如下:

using (var httpClient = new HttpClient())
{
var msg = new HttpRequestMessage()
{
Content = new StringContent(postJson, Encoding.UTF8, "application/json"),
Method = HttpMethod.Post,
RequestUri = new Uri(apiUrl),
}; // 这里会一直阻塞,直到超时
var res = httpClient.SendAsync(msg).ConfigureAwait(false).GetAwaiter().GetResult(); if (res.StatusCode != HttpStatusCode.OK)
{
throw new Exception(res.StatusCode.ToString());
} return res.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
}

异步请求超时取消错误如下:



这种情况首先怀疑对方服务是不是有问题

然而经过确认,对方服务没问题,并且使用将请求的url报文粘贴到PostMan进行请求,迅速得到返回报文,一切正常。

排除了对方服务的问题,那是我们的代码问题?

可是上面HttpClient发起Post请求的代码写了不知道多少遍,一直都没问题,今天怎么就不行了呢,我敢保证这么写没毛病。

遇到这种情况该如何解决呢?

爬坑过程

遇到这种问题,相比大部分人开始各种参数换来换去,各种库换来换去,可能最终蒙成了。但是这里我相信PostMan可以请求成功,强大的HttpClient一定可以,一定是是哪个参数问题,有经验的老手首先就会想到: 接口的协议中是不是对Header有什么特别的要求,这里查询文档,没有什么特别要求。

控制变量法

既然我们不知道为什么,也猜不到,那就控制变量法去解决。这里能想到的就是抓包,抓取PostMan成功的请求报文以及我们失败的报文,对比差异。

抓包工具使用的是Fiddler

Postman报文

POST http://xxx.xxx.xxx.xxx:30000/parking/carin/V1 HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.29.2
Accept: */*
Postman-Token: 14547b64-d8f6-4b0b-9fa9-48c9ec74a8f6
Host: xxx.xxx.xxx.xxx:30000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 563 {"data": ...这里省略了具体json内容}

HttpClient报文

POST http://118.31.110.35:30000/parking/carin/V1 HTTP/1.1
Content-Type: application/json; charset=utf-8
Host: 118.31.110.35:30000
Content-Length: 563
Expect: 100-continue
Connection: Keep-Alive {"data": ...这里省略了具体json内容}

差异排查

  1. 因为body中的内容是一样的,这里就不用对比了。
  2. 两个请求的Header存在差异,那我们就将差异一个一个抹平。
  3. Content-TypeHttpClient中多了charset=utf-8,这个应该不影响,http协议默认就是utf8。
  4. User-AgentHttpClient中没有,那我们加上一模一样的User-Agent,测试,依旧超时。
  5. AcceptHttpClient中没有,抹平,测试,依旧超时。
  6. Postman-TokenHttpClient中没有,抹平,测试,依旧超时。
  7. Accept-EncodingHttpClient中没有,抹平,测试,依旧超时。

到这里Postman中有的,我们HttpClient中都有了,竟然还超时,这里虽然已经保证大部分参数都一样了,但是控制变量法要求所有参数都一样,这里还没有保证,因为HttpClient多了一个Expect头,我们还没保证一致。

  1. HttpClient的请求头中Expect: 100-continuePostman报文中不存在,去掉Expect,测试,成功了!!
  2. 那我们锁定Expect: 100-continue导致了我们的请求无响应,还原之前所有的抹平操作,仅仅移除Expect: 100-continue,测试,依然成功。

最终解决前言中的问题,仅仅需要添加一行代码

msg.Headers.ExpectContinue = false;

ExpectContinues属性文档:

至此问题解决,控制变量yyds

Expect是什么

参考Expect的定义

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect

Expect 是一个请求消息头,包含一个期望条件,表示服务器只有在满足此期望条件的情况下才能妥善地处理请求。

Expect

规范中只规定了一个期望条件,即 Expect: 100-continue, 对此服务器可以做出如下回应:

  • 100 如果消息头中的期望条件可以得到满足,使得请求可以顺利进行的话,
  • 417 (Expectation Failed) 如果服务器不能满足期望条件的话;也可以是其他任意表示客户端错误的状态码(4xx)。

例如,如果请求中 Content-Length 的值太大的话,可能会遭到服务器的拒绝。

Expect有啥好处

让客户端在发送请求数据之前去判断服务器是否愿意接收该数据,如果服务器愿意接收,客户端才会真正发送数据,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。

Expect有啥坑

不是所有的服务器都会正确应答100-continue, 比如lighttpd, 就会返回417 Expectation Failed。

超时的原因

HttpClient默认携带了Expect头,我们请求带上了Expect: 100-continue的话是不会立刻发送body中的报文给服务器,需要服务器需要对Expect: 100-continue做出响应,然而对方服务器不支持Expect当然不能做出响应,在前言说的问题中,也就是HttpClient在等对方服务器响应Expect,然后再发送报文,而对方服务器看来,我们怎么还不发送报文过来,双方都在等数据,最终HttpClient超时~

以上纯属个人理解,有不正确之处,还请指正~

HTTP协议之Expect爬坑的更多相关文章

  1. mint-ui之picker爬坑记

    picker的数据来源为动态获取时,数据无法正常渲染!因为方法不对,所以坑大了!深刻地体会到'业不精,我之过',谨以此文,深刻地记录一下踩坑及爬坑的整个过程,以便日后不再入坑,也给后来者提供一下参考 ...

  2. 【爬坑系列】之vxlan网络实现

    linux 内核从3.7之后就内部集成了vxlan功能,所以可以使用linux内核提供的vxlan功能,经过配置创建vxlan网络. 而从Docker自Docker Engine 1.9之后,就自带o ...

  3. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  4. 安卓易学,爬坑不易——腾讯老司机的RecyclerView局部刷新爬坑之路

    针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验.目前功能还在免费开放中. 点击地址:http://wetest ...

  5. Android爬坑之路

    做了那么久前端,现在终于可以回到我的老本行, 今天我用了一天的时间配置里Android开发环境,mac和windows双平台,eclipse和IDEA双平台,别问为什么,我就喜欢,中间大坑不断,再加上 ...

  6. kali linux安装virtualbox虚拟机之爬坑经历

    很多kali爱好者想把kali linux作为系统使用,但是有些win下的程序有时候也需要用到,此时需要虚拟机. kali系统在安装虚拟机的时候也会遇到一大堆坑,接下来是我的爬坑过程. 一波三折. 环 ...

  7. 从Ueditor跨域上传,总结的一次跨域上传的爬坑经历

    项目内其中一个管理后台需要发布文章,需要一个富文本编辑器,经过一番选择后,最终选择了百度的Ueditor. 由于上传的文件是上传到另一台专门存放图片等静态资源的服务器上面的,所以就涉及到了跨域上传. ...

  8. AngularJs (二) 搭建Deployd 服务爬坑

    Deployd 爬坑 按照书上的教程,介绍Deployd 这个东东,首先进入其deployd.com/网页,发现这个东东着实厉害. THE SIMPLEST WAY TO BUILD AN API 按 ...

  9. 前端工作日常爬坑之——单页面微信开发Jssdk相关,以及jssdk图片直传自己服务器的实现。

    日常爬坑 遇到的情况大致说明: 项目基于Vue2全家桶实现,vue-router控制前端路由,路由模式是History(主要是领导追求太高,觉得hash带#号太丑,然后遇到了小坑...),主要是服务于 ...

随机推荐

  1. [题解] trip

    题目大意 给定一颗大小为 \(N\) 的树, \(1\)的度数不小于 \(2\) .每个点有一个颜色,要么为黑色要么为白色. 从 \(1\) 号点开始游走,计数器初始为 \(0\). 如果当前为黑点计 ...

  2. mysql5.6 innodb_large_prefix引起的一个异常

    phenomenon: Specified key was too long; max key length is 3072 bytes 在修改一个数据库字段时,字段容量被限制为了表前缀的大小而不是本 ...

  3. Python 散列表查询_进入<哈希函数>为结界的世界

    1. 前言 哈希表或称为散列表,是一种常见的.使用频率非常高的数据存储方案. 哈希表属于抽象数据结构,需要开发者按哈希表数据结构的存储要求进行 API 定制,对于大部分高级语言而言,都会提供已经实现好 ...

  4. python 动态规划(背包问题和最长公共子串)

    背包问题 现在要往一个可以装4个单位重量的背包里怎么装价值最高:A重量1个单位,价值15:B重量3个单位,价值20:C重量4个重量,价值30 使用动态规划填充空格 class SolutionBag: ...

  5. unity---脚本创建按钮

    脚本创建按钮 新建文件夹 Resources 方便引用图片 在文件Resources中新建Images,并且下载一个图片 没有图片,按钮内容无法显示 图片需要处理一下 Textrue Type 改为 ...

  6. 学习Java的第十七天——大数字运算

    学习内容:大数字运算 代码实现: package 数字处理类; import java.math.BigInteger; public class BigIntegerDemo { public st ...

  7. 「Java分享客栈」Nacos配置中心称王称霸,我Apollo一生也不弱于人!

    前言 Apollo又称阿波罗配置中心,在前两年还是挺火的,但阿里SpringCloud套件席卷国内之后,nacos就成为了最被亲睐的分布式配置中心,nacos是配置中心和注册中心二合一的产品,单纯功能 ...

  8. 技术分享 | app自动化测试(Android)--元素定位方式与隐式等待

    原文链接 元素定位是 UI 自动化测试中最关键的一步,假如没有定位到元素,也就无法完成对页面的操作.那么在页面中如何定位到想要的元素,本小节讨论 Appium 元素定位方式. Appium的元素定位方 ...

  9. ABP Framework 5.3.0 版本新增功能和变更说明

    ABP Framework 5.3.0 稳定版已在2022年6月14日正式发布. 以下是本版本的新增功能: "开始"页面提供创建单层项目选项 启动模板提供 PWA 支持 Volo. ...

  10. 2.C++标准库函数:getline函数 定界流输入截取函数 -windows编程

    引言:今天工作遇到了一个需要按行读取txt文件数据的需求,查询了一下getline()函数,发现这竟然是一个C++的标准库函数,而且设计的很好,特地做一下记录.getline本质是一个定界流输入截取函 ...