前言

最近在写WebClientApi这个组件,底层使用HttpClient,发现HttpClient有许多低级的错误,使用者一不小心就可能会正常的去调用它的这些错误,得不到预期的结果。本文我把我认为是问题或缺陷的地方指出(但不一定是问题或缺陷,可能是个人理解错误),后人也许可以跳过这些缺陷。

缺陷1

请求头Cookie与HttpClientHandler的CookieContainer水火不容

默认的,HttpClient会使用默认的HttpClientHandler,默认的HttpClientHandler的UseCookies是true,也就是说,默认情况下HttpClient就有间接的CookieContainer可以使用。但UseCookies为true了,请求头的Cookie就不会提交,请求头的Cookie就不会提交,请求头的Cookie就不会提交。所以注意了,如果把Cookie提交给服务器的话,当UseCookies为true时,只有把cookie值一一写入CookieContainer,提交的cookie才生效;否则只有写入请求头,提交的cookie才生效。

缺陷2

HttpClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)有问题

HttpClient.DefaultRequestHeaders,当请求头有设置("Connection", "keep-alive"),进行第一次请求的时候,参数request没问题,但执行SendAsync逻辑体之后,服务端收到的Connection不标准,收到请求头为Connection: keep-alive,Keep-Alive ,如果服务器兼容性不好,处理请求之后就会断开连接。奇葩的是,第二次以后都不会出现重复的keep-alive,如果设置为("Connection", ""),第一次ok,后面的都没有Connection请求头了,全部报断开...

由于HttpClient不是bcl,所以没找到源代码,反编译看了一下,想真正的重写这个SendAsync难度大,干脆就来个将错就错,错错得对的法子,绕开这个问题

/// <summary>
/// 修复keep-alive问题的HttpClientHandler
/// </summary>
class KeepAliveHandler : HttpClientHandler
{
/// <summary>
/// 发送次数
/// </summary>
private int sendTimes = ; /// <summary>
/// 是否keepAlive
/// </summary>
private readonly bool keepAlive; /// <summary>
/// keep-alive的HttpClientHandler
/// </summary>
/// <param name="keepAlive">keepAlive</param>
public KeepAliveHandler(bool keepAlive)
{
this.keepAlive = keepAlive;
} /// <summary>
/// 发送请求
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Remove("Connection");
if (this.keepAlive == true)
{
if (Interlocked.CompareExchange(ref this.sendTimes, , ) == )
{
request.Headers.Add("Connection", string.Empty);
}
else
{
request.Headers.Add("Connection", "keep-alive");
}
}
return base.SendAsync(request, cancellationToken);
}
}

缺陷3

MultipartContent的boundary问题

随便new 它一个实例,可以看到它的 Conent-Type与大众客户端不一样,符合不符合标准我不清楚,大概是这样Content-Type: multipart/form-data; boundary="boundary"

注意它的两个双引号了,我用PostMan没有引号,Postman如下图:

如果你想得到没用引号的boundary,可以这样修改:

var boundary = Guid.NewGuid().ToString();
var parameter = new NameValueHeaderValue("boundary", boundary);
httpContent = new MultipartContent("form-data", boundary); httpContent.Headers.ContentType.Parameters.Clear();
httpContent.Headers.ContentType.Parameters.Add(parameter);

缺陷4:

MultipartFormDataContent的Add(HttpContent content,  string xxx ...)的问题

这两个方法生成的表单,boundary问题继承了它老爸,自己生成内容时也有问题,PostMan是生成name="{name}"; filename="{filename}",每个都有又引号;但MultipartFormDataContent生成的是name={name}; filename={filename},双引号不见了,但它的IIS貌似能兼容,其它的就不知道了。

如果你想得到双引号的内容,MultipartFormDataContent这个类可以废了,用它的老爸MultipartContent吧。

要添加文件项,可以使用下面这个类,直接Add到MultipartContent对象:

/// <summary>
/// 表示文件内容
/// </summary>
class MulitpartFileContent : StreamContent
{
/// <summary>
/// 文件内容
/// </summary>
/// <param name="stream">文件流</param>
/// <param name="name">名称</param>
/// <param name="fileName">文件名</param>
/// <param name="contentType">文件Mime</param>
public MulitpartFileContent(Stream stream, string name, string fileName, string contentType)
: base(stream)
{
if (this.Headers.ContentDisposition == null)
{
var disposition = new ContentDispositionHeaderValue("form-data");
disposition.Name = string.Format("\"{0}\"", name);
disposition.FileName = string.Format("\"{0}\"", fileName);
this.Headers.ContentDisposition = disposition;
} if (string.IsNullOrEmpty(contentType))
{
contentType = "application/octet-stream";
}
this.Headers.ContentType = new MediaTypeHeaderValue(contentType);
}
}

class MulitpartFileContent

要添加文本项,可以使用下面这个类,直接Add到MultipartContent对象:

/// <summary>
/// 表示文本内容
/// </summary>
class MulitpartTextContent : StringContent
{
/// <summary>
/// 文本内容
/// </summary>
/// <param name="name">名称</param>
/// <param name="value">文本</param>
public MulitpartTextContent(string name, string value)
: base(value == null ? string.Empty : value)
{
if (this.Headers.ContentDisposition == null)
{
var disposition = new ContentDispositionHeaderValue("form-data");
disposition.Name = string.Format("\"{0}\"", name);
this.Headers.ContentDisposition = disposition;
}
this.Headers.Remove("Content-Type");
}
}

MulitpartTextContent

当前状态

正在火力开WebApiClient中,关于HttpClient更多缺陷与绕过方法,正在发现的路上,欢迎使用我的WebApiClient

.Net45下HttpClient的几个缺陷的更多相关文章

  1. Cocos开发中Visual Studio下HttpClient开发环境设置

    Cocos2d-x 3.x将与网络通信相关的类集成到libNetwork类库工程中,这其中包括了HttpClient类.我们需要在Visual Studio解决方案中添加libNetwork类库工程. ...

  2. 9102年了,汇总下HttpClient问题,封印一个

    如果找的是core的HttpClientFactory 出门右转. 官方写法,高并发下,TCP连接不能快速释放,导致端口占完,无法连接 Dispose 不是马上关闭tcp连接 主动关闭的一方为什么不能 ...

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

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

  4. .net的retrofit--WebApiClient底层篇

    前言 本篇文章的内容是WebApiClient底层说明,也是WebApiClient系列接近尾声的一篇文章,如果你没有阅读过之前的的相关文章,可能会觉得本文章的内容断层,WebApiClient系列文 ...

  5. 使用WebApiClient请求和管理Restful Api

    前言 本篇文章的内容是WebApiClient应用说明篇,如果你没有了解过WebApiClient,可以先阅读以下相关文章: WebApi client 的面向切面编程 我来给.Net设计一款Http ...

  6. Cocos发育Visual Studio下一个HttpClient开发环境设置

    Cocos2d-x 3.x相关类集成到网络通信libNetwork图书馆project于.这其中包括:HttpClient分类. 我们需要在Visual Studio溶液中加入libNetwork图书 ...

  7. 程序员节应该写博客之.NET下使用HTTP请求的正确姿势

    程序员节应该写博客之.NET下使用HTTP请求的正确姿势 一.前言 去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的分析后对HttpClient有了一定的了 ...

  8. .NET下使用HTTP请求的正确姿势

    来源:Lewis.Zou cnblogs.com/modestmt/p/7724821.html 一.前言 去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的 ...

  9. HttpClient Coder Example

    Example 1:   HttpClient httpClient = new HttpClient();                 httpClient.getHostConfigurati ...

随机推荐

  1. MYSQL数据库相关操作---读书笔记分享

    ===================数据库软件: <DBMS,DataBase Management System >数据库: 保存特定类型数据的容器<DB,database> ...

  2. 进程管理之fork函数

    fork函数的定义 #include <unistd.h> #include <sys/types.h> pid_t fork(void); fork函数在父进程中返回子进程的 ...

  3. mysql-索引与优化

    写在前面:索引对查询的速度有着至关重要的影响,理解索引也是进行数据库性能调优的起点.考虑如下情况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录.如果没有索引,查询将 ...

  4. 抛开深层次底层,快速入门SpringMVC

    SpringMVC主要有三个核心部分组成,DispatcherServlet.Controller.ViewResolver.          DispatcherServlet:     请求输入 ...

  5. Flask-RESTful接口开发学习笔记一:实现简单的GET请求

    1.首先安装需要依赖的第三方包: (1):Flask (2):Flask-RESTful 如何安装就不在啰嗦,pip也好,下载到本地再安装也好,看你随意. 2.我们先实现GET请求. Q:什么是GET ...

  6. 程序猿表白练级之Hello World

    程序猿表白练级之Hello World ... 推荐一个优秀的编程网站:Hello World http://www.he11oworld.com/ The Hello World Collectio ...

  7. redux源码解读

    react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合react使用,保证数据的单向流动,可以很好的管理整个项目的状态,但是具体来说,下面是 ...

  8. CSS 常用样式 提高网页编写速度

    *{margin:0px;padding:0px;}/*内外边距初始化*/html, body, div, h1, h2, h3, h4, h5, h6, p, span, img, input, t ...

  9. Spring配置文件的xsd知识点

    今天在Spring配置文件中配置如下事务属性时,提示<tx is not bound(不受约束的),估计是配置文件的xsd没配置好. <!-- 2.配置事务属性 --> <tx ...

  10. 自己动手写java 字节流输入输出流

    数据流是一串连续不断的数据的集合,就象水管里的水流,在水管的一端一点一点地供水,而在水管的另一端看到的是一股连续不断的水流.   "流是磁盘或其它外围设备中存储的数据的源点或终点." ...