起因
之前在对接第三方接口时 发现用.Net 内置的 HttpClient 直接请求等了半天 最后竟然超时了 很是纳闷 之前对接的所有第三方接口都是能够在50ms内反馈回来的 现在等了足足一分钟却没有动静 后来在借助搜索引擎 发现了这篇文章 原来是请求头里的默认header(Expect: 100-continue
)导致的
100-Continue
在RFC2616中对100 Continue 有着这样的说明
Requirements for HTTP/1.1 clients:
- If a client will wait for a 100 (Continue) response before
sending the request body, it MUST send an Expect request-header
field (section 14.20) with the "100-continue" expectation.
- A client MUST NOT send an Expect request-header field (section
14.20) with the "100-continue" expectation if it does not intend
to send a request body.
对于客户端来说 在默认发送body前务必需要先对服务端发送100-continue的请求头内容
并且客户端在没有意图发送body的请求下避免发送100-continue的请求头内容
Requirements for HTTP/1.1 origin servers:
- Upon receiving a request which includes an Expect request-header
field with the "100-continue" expectation, an origin server MUST
either respond with 100 (Continue) status and continue to read
from the input stream, or respond with a final status code. The
origin server MUST NOT wait for the request body before sending
the 100 (Continue) response. If it responds with a final status
code, it MAY close the transport connection or it MAY continue
to read and discard the rest of the request. It MUST NOT
perform the requested method if it returns a final status code.
对于服务端来说 当接收到100-continue的请求头下 必须做出以下任意一种处理方式
- 向客户端返回 100 Continue 状态码 以便客户端发送body 请求体
- 向客户端直接返回结束状态码
不过有些服务端未必能够合理地处理100-Continue这样的请求头 所以导致我们的请求在没有发送body之前一直等待对方的响应…
解决
如果想要直接解决这样的问题 其实 只需一行代码:
cli.DefaultRequestHeaders.ExpectContinue = false;
但 这是为什么 我们可以继续往下看源码
// Note that ServicePoint.Expect100Continue is on by default. However, by default we don't set any header
// value. I.e. ExpectContinue returns 'null' by default. The fact that HWR will add "Expect: 100-continue"
// anyways is a transport channel feature and can be turned off by setting ExpectContinue to 'false'.
// Remember: these headers are also used on the server side, where ExpectContinue should only be true
// if the received request message actually has this header value set.
public bool? ExpectContinue
{
get
{
if (ExpectCore.IsSpecialValueSet)
{
return true;
}
if (expectContinueSet)
{
return false;
}
return null;
}
set
{
if (value == true)
{
expectContinueSet = true;
ExpectCore.SetSpecialValue();
}
else
{
expectContinueSet = value != null;
ExpectCore.RemoveSpecialValue();
}
}
}
private HttpHeaderValueCollection<NameValueWithParametersHeaderValue> ExpectCore
{
get
{
if (expect == null)
{
expect = new HttpHeaderValueCollection<NameValueWithParametersHeaderValue>(
HttpKnownHeaderNames.Expect, this, HeaderUtilities.ExpectContinue);
}
return expect;
}
}
public HttpRequestHeaders DefaultRequestHeaders
{
get
{
if (defaultRequestHeaders == null)
{
defaultRequestHeaders = new HttpRequestHeaders();
}
return defaultRequestHeaders;
}
}
private void PrepareRequestMessage(HttpRequestMessage request)
{
Uri requestUri = null;
if ((request.RequestUri == null) && (baseAddress == null))
{
throw new InvalidOperationException(SR.net_http_client_invalid_requesturi);
}
if (request.RequestUri == null)
{
requestUri = baseAddress;
}
else
{
// If the request Uri is an absolute Uri, just use it. Otherwise try to combine it with the base Uri.
if (!request.RequestUri.IsAbsoluteUri)
{
if (baseAddress == null)
{
throw new InvalidOperationException(SR.net_http_client_invalid_requesturi);
}
else
{
requestUri = new Uri(baseAddress, request.RequestUri);
}
}
}
// We modified the original request Uri. Assign the new Uri to the request message.
if (requestUri != null)
{
request.RequestUri = requestUri;
}
// Add default headers
if (defaultRequestHeaders != null)
{
request.Headers.AddHeaders(defaultRequestHeaders);
}
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
CheckDisposed();
CheckRequestMessage(request);
SetOperationStarted();
PrepareRequestMessage(request);
// PrepareRequestMessage will resolve the request address against the base address.
CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken,
pendingRequestsCts.Token);
#if NET_4
TimerThread.Timer timeoutTimer = SetTimeout(linkedCts);
#else
SetTimeout(linkedCts);
#endif
TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
#if NET_4
try
{
#endif
base.SendAsync(request, linkedCts.Token).ContinueWithStandard(task =>
{
try
{
// The request is completed. Dispose the request content.
DisposeRequestContent(request);
if (task.IsFaulted)
{
#if NET_4
SetTaskFaulted(request, linkedCts, tcs, task.Exception.GetBaseException(), timeoutTimer);
#else
SetTaskFaulted(request, linkedCts, tcs, task.Exception.GetBaseException());
#endif
return;
}
if (task.IsCanceled)
{
#if NET_4
SetTaskCanceled(request, linkedCts, tcs, timeoutTimer);
#else
SetTaskCanceled(request, linkedCts, tcs);
#endif
return;
}
HttpResponseMessage response = task.Result;
if (response == null)
{
#if NET_4
SetTaskFaulted(request, linkedCts, tcs,
new InvalidOperationException(SR.net_http_handler_noresponse), timeoutTimer);
#else
SetTaskFaulted(request, linkedCts, tcs,
new InvalidOperationException(SR.net_http_handler_noresponse));
#endif
return;
}
// If we don't have a response content, just return the response message.
if ((response.Content == null) || (completionOption == HttpCompletionOption.ResponseHeadersRead))
{
#if NET_4
SetTaskCompleted(request, linkedCts, tcs, response, timeoutTimer);
#else
SetTaskCompleted(request, linkedCts, tcs, response);
#endif
return;
}
Contract.Assert(completionOption == HttpCompletionOption.ResponseContentRead,
"Unknown completion option.");
// We have an assigned content. Start loading it into a buffer and return response message once
// the whole content is buffered.
#if NET_4
StartContentBuffering(request, linkedCts, tcs, response, timeoutTimer);
#else
StartContentBuffering(request, linkedCts, tcs, response);
#endif
}
catch (Exception e)
{
// Make sure we catch any exception, otherwise the task will catch it and throw in the finalizer.
if (Logging.On) Logging.Exception(Logging.Http, this, "SendAsync", e);
tcs.TrySetException(e);
}
});
#if NET_4
}
catch
{
DisposeTimer(timeoutTimer);
throw;
}
#endif
return tcs.Task;
}
评论区