• HTTP协议之Expect爬坑


    前言

    今天,在对接一个第三方平台开放接口时遇到一个很棘手的问题,根据接口文档组装好报文,使用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,测试,依然成功。

    本文为Gui.H原创文章,发布于公众号:dotnet之美,转载注明出处

    博客园首发:https://www.cnblogs.com/springhgui/p/16499439.html

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

    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超时~

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


    __EOF__

  • 本文作者: Gui.H
  • 本文链接: https://www.cnblogs.com/springhgui/p/16499439.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    【Linux】kernel与应用消息队列的一种设计
    C++ Qt零基础入门进阶与企业级项目实战教程与学习方法分享
    【诗歌】被讨厌的勇气
    【Flutter】包管理(2)Flutter 中 sqflite 的详细使用
    Threejs之射线拾取模型
    TODOS案例
    Day18:C++飞机大战
    面向对象-01我的世界
    人与人之间的单机五子棋 —— C语言实现
    【HTML+CSS】实现网页的导航栏和下拉菜单
  • 原文地址:https://www.cnblogs.com/springhgui/p/16499439.html