侧边栏壁纸
博主头像
komi

Bona Fides

  • 累计撰写 11 篇文章
  • 累计创建 20 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Http Status Code 100 Continue

komi
2024-01-01 / 0 评论 / 0 点赞 / 855 阅读 / 1,094 字

起因

之前在对接第三方接口时 发现用.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的请求头下 必须做出以下任意一种处理方式

  1. 向客户端返回 100 Continue 状态码 以便客户端发送body 请求体
  2. 向客户端直接返回结束状态码

不过有些服务端未必能够合理地处理100-Continue这样的请求头 所以导致我们的请求在没有发送body之前一直等待对方的响应…

解决

如果想要直接解决这样的问题 其实 只需一行代码:

cli.DefaultRequestHeaders.ExpectContinue = false;

但 这是为什么 我们可以继续往下看源码

ExpectContinue

        // 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();
                }
            }
        }

ExpectCore

        private HttpHeaderValueCollection<NameValueWithParametersHeaderValue> ExpectCore
        {
            get
            {
                if (expect == null)
                {
                    expect = new HttpHeaderValueCollection<NameValueWithParametersHeaderValue>(
                        HttpKnownHeaderNames.Expect, this, HeaderUtilities.ExpectContinue);
                }
                return expect;
            }
        }

DefaultRequestHeaders

        public HttpRequestHeaders DefaultRequestHeaders
        {
            get
            {
                if (defaultRequestHeaders == null)
                {
                    defaultRequestHeaders = new HttpRequestHeaders();
                }
                return defaultRequestHeaders;
            }
        }

PrepareRequestMessage

        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);
            }
        }

SendAsync

        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;
        }

参考

https://selfboot.cn/2023/08/08/http_100_continue/

0

评论区