前言

最近在写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

最新文章

  1. web报表工具FineReport常用函数的用法总结(报表函数)
  2. JavaScript中的变量及数据类型
  3. 《生活就像练习》读书笔记(一)——AQAL理论和象限
  4. error C2065: “CMainFrame”: 未声明的标识符
  5. SharePoint 2013 Nintex Workflow 工作流帮助(二)
  6. NOIP-2003 加分二叉树
  7. QTP自传之web常用对象
  8. CSS 入门
  9. Oracle学习笔记之游标详解
  10. 重启nginx后丢失nginx.pid的解决方法
  11. Java_流程控制
  12. PHP共享内存yac操作类
  13. CentOS 6 RPM安装包下载地址
  14. mongo 字段重命名
  15. C#打印图片
  16. Java Calendar,Date,DateFormat,TimeZone,Locale等时间相关内容的认知和使用(4) DateFormat
  17. python从sqlite中提取数据到excel
  18. 前端PHP入门-033-连接数据库-天龙八步
  19. sql server 测试delete后数据空间情况
  20. 加密shell

热门文章

  1. python2 接口测试一般方法.
  2. 再说php依赖注入
  3. String、StringBuffer与StringBuilder
  4. Spring与Quartz的整合实现定时任务调度(转)
  5. Java获取精确到秒的时间戳(转)
  6. Vim 命令图解-Gvim使用笔记
  7. Python | 多种编码文件(中文)乱码问题解决
  8. Hibernate注解-类级别注解
  9. 求链表内环的入口节点-Java
  10. Varnish后端主机的健康状态检查