< Summary

Class:Azure.Core.Pipeline.HttpClientTransport
Assembly:Azure.Core
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\HttpClientTransport.cs
Covered lines:131
Uncovered lines:5
Coverable lines:136
Total lines:400
Line coverage:96.3% (131 of 136)
Covered branches:67
Total branches:74
Branch coverage:90.5% (67 of 74)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor()-100%100%
.ctor(...)-100%50%
.cctor()-100%100%
CreateRequest()-100%100%
Process(...)-100%100%
ProcessAsync()-100%100%
CreateDefaultClient()-100%100%
BuildRequestMessage(...)-66.67%50%
TryGetHeader(...)-100%100%
TryGetHeader(...)-100%100%
GetHeaders()-100%100%
RemoveHeader(...)-100%100%
ContainsHeader(...)-100%100%
CopyHeaders(...)-75%75%
JoinHeaderValues(...)-100%100%
.ctor()-100%100%
get_Method()-100%100%
set_Method(...)-100%100%
get_Content()-100%100%
get_ClientRequestId()-100%100%
set_ClientRequestId(...)-100%100%
AddHeader(...)-83.33%75%
TryGetHeader(...)-100%100%
TryGetHeaderValues(...)-100%100%
ContainsHeader(...)-100%100%
RemoveHeader(...)-100%100%
EnumerateHeaders()-100%100%
BuildRequestMessage(...)-100%100%
Dispose()-100%100%
ToString()-0%100%
.cctor()-100%100%
ToHttpClientMethod(...)-100%100%
EnsureContentInitialized()-100%100%
get_PipelineContent()-100%100%
get_CancellationToken()-100%100%
SerializeToStreamAsync()-100%100%
TryComputeLength(...)-100%100%
.ctor(...)-100%50%
get_Status()-100%100%
get_ReasonPhrase()-100%100%
get_ContentStream()-100%100%
set_ContentStream(...)-100%100%
get_ClientRequestId()-100%100%
TryGetHeader(...)-100%100%
TryGetHeaderValues(...)-100%100%
ContainsHeader(...)-100%100%
EnumerateHeaders()-100%100%
Dispose()-100%50%
ToString()-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\HttpClientTransport.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.IO;
 9using System.Net;
 10using System.Net.Http;
 11using System.Net.Http.Headers;
 12using System.Threading;
 13using System.Threading.Tasks;
 14
 15namespace Azure.Core.Pipeline
 16{
 17    /// <summary>
 18    /// An <see cref="HttpPipelineTransport"/> implementation that uses <see cref="HttpClient"/> as the transport.
 19    /// </summary>
 20    public class HttpClientTransport : HttpPipelineTransport
 21    {
 22        private readonly HttpClient _client;
 23
 24        /// <summary>
 25        /// Creates a new <see cref="HttpClientTransport"/> instance using default configuration.
 26        /// </summary>
 67227        public HttpClientTransport() : this(CreateDefaultClient())
 28        {
 67229        }
 30
 31        /// <summary>
 32        /// Creates a new instance of <see cref="HttpClientTransport"/> using the provided client instance.
 33        /// </summary>
 34        /// <param name="client">The instance of <see cref="HttpClient"/> to use.</param>
 78835        public HttpClientTransport(HttpClient client)
 36        {
 78837            _client = client ?? throw new ArgumentNullException(nameof(client));
 78838        }
 39
 40        /// <summary>
 41        /// A shared instance of <see cref="HttpClientTransport"/> with default parameters.
 42        /// </summary>
 243        public static readonly HttpClientTransport Shared = new HttpClientTransport();
 44
 45        /// <inheritdoc />
 46        public sealed override Request CreateRequest()
 238247            => new PipelineRequest();
 48
 49        /// <inheritdoc />
 50        public override void Process(HttpMessage message)
 51        {
 52            // Intentionally blocking here
 136253            ProcessAsync(message).GetAwaiter().GetResult();
 135254        }
 55
 56        /// <inheritdoc />
 57        public sealed override async ValueTask ProcessAsync(HttpMessage message)
 58        {
 272459            using (HttpRequestMessage httpRequest = BuildRequestMessage(message))
 60            {
 61                HttpResponseMessage responseMessage;
 272462                Stream? contentStream = null;
 63                try
 64                {
 272465                    responseMessage = await _client.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, mes
 272466                        .ConfigureAwait(false);
 270467                    if (responseMessage.Content != null)
 68                    {
 264069                        contentStream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false);
 70                    }
 270471                }
 872                catch (HttpRequestException e)
 73                {
 874                    throw new RequestFailedException(e.Message, e);
 75                }
 76
 270477                message.Response = new PipelineResponse(message.Request.ClientRequestId, responseMessage, contentStream)
 270478            }
 270479        }
 80
 81        private static HttpClient CreateDefaultClient()
 82        {
 67283            var httpClientHandler = new HttpClientHandler();
 67284            if (HttpEnvironmentProxy.TryCreate(out IWebProxy webProxy))
 85            {
 886                httpClientHandler.Proxy = webProxy;
 87            }
 88
 67289            return new HttpClient(httpClientHandler);
 90        }
 91
 92        private static HttpRequestMessage BuildRequestMessage(HttpMessage message)
 93        {
 272494            if (!(message.Request is PipelineRequest pipelineRequest))
 95            {
 096                throw new InvalidOperationException("the request is not compatible with the transport");
 97            }
 272498            return pipelineRequest.BuildRequestMessage(message.CancellationToken);
 99        }
 100
 101        internal static bool TryGetHeader(HttpHeaders headers, HttpContent? content, string name, [NotNullWhen(true)] ou
 102        {
 7700103            if (TryGetHeader(headers, content, name, out IEnumerable<string>? values))
 104            {
 664105                value = JoinHeaderValues(values);
 664106                return true;
 107            }
 108
 7036109            value = null;
 7036110            return false;
 111        }
 112
 113        internal static bool TryGetHeader(HttpHeaders headers, HttpContent? content, string name, [NotNullWhen(true)] ou
 114        {
 8008115            return headers.TryGetValues(name, out values) || content?.Headers.TryGetValues(name, out values) == true;
 116        }
 117
 118        internal static IEnumerable<HttpHeader> GetHeaders(HttpHeaders headers, HttpContent? content)
 119        {
 33992120            foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
 121            {
 12520122                yield return new HttpHeader(header.Key, JoinHeaderValues(header.Value));
 123            }
 124
 4456125            if (content != null)
 126            {
 5292127                foreach (KeyValuePair<string, IEnumerable<string>> header in content.Headers)
 128                {
 388129                    yield return new HttpHeader(header.Key, JoinHeaderValues(header.Value));
 130                }
 131            }
 4332132        }
 133
 134        internal static bool RemoveHeader(HttpHeaders headers, HttpContent? content, string name)
 135        {
 136            // .Remove throws on invalid header name so use TryGet here to check
 3432137            if (headers.TryGetValues(name, out _) && headers.Remove(name))
 138            {
 44139                return true;
 140            }
 141
 3388142            return content?.Headers.TryGetValues(name, out _) == true && content.Headers.Remove(name);
 143        }
 144
 145        internal static bool ContainsHeader(HttpHeaders headers, HttpContent? content, string name)
 146        {
 147            // .Contains throws on invalid header name so use TryGet here
 276148            if (headers.TryGetValues(name, out _))
 149            {
 56150                return true;
 151            }
 152
 220153            return content?.Headers.TryGetValues(name, out _) == true;
 154        }
 155
 156        internal static void CopyHeaders(HttpHeaders from, HttpHeaders to)
 157        {
 3288158            foreach (KeyValuePair<string, IEnumerable<string>> header in from)
 159            {
 1228160                if (!to.TryAddWithoutValidation(header.Key, header.Value))
 161                {
 0162                    throw new InvalidOperationException($"Unable to add header {header} to header collection.");
 163                }
 164            }
 416165        }
 166
 167        private static string JoinHeaderValues(IEnumerable<string> values)
 168        {
 13572169            return string.Join(",", values);
 170        }
 171
 172        private sealed class PipelineRequest : Request
 173        {
 174            private bool _wasSent = false;
 175            private readonly HttpRequestMessage _requestMessage;
 176
 177            private PipelineContentAdapter? _requestContent;
 178            private string? _clientRequestId;
 179
 2382180            public PipelineRequest()
 181            {
 2382182                _requestMessage = new HttpRequestMessage();
 2382183            }
 184
 185            public override RequestMethod Method
 186            {
 2076187                get => RequestMethod.Parse(_requestMessage.Method.Method);
 654188                set => _requestMessage.Method = ToHttpClientMethod(value);
 189            }
 190
 7572191            public override RequestContent? Content { get; set; }
 192
 193            public override string ClientRequestId
 194            {
 8832195                get => _clientRequestId ??= Guid.NewGuid().ToString();
 196                set
 197                {
 8198                    Argument.AssertNotNull(value, nameof(value));
 4199                    _clientRequestId = value;
 4200                }
 201            }
 202
 203            protected internal override void AddHeader(string name, string value)
 204            {
 5404205                if (_requestMessage.Headers.TryAddWithoutValidation(name, value))
 206                {
 5054207                    return;
 208                }
 209
 350210                PipelineContentAdapter requestContent = EnsureContentInitialized();
 350211                if (!requestContent.Headers.TryAddWithoutValidation(name, value))
 212                {
 0213                    throw new InvalidOperationException("Unable to add header to request or content");
 214                }
 350215            }
 216
 4324217            protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => HttpCli
 218
 136219            protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable<string>
 220
 112221            protected internal override bool ContainsHeader(string name) => HttpClientTransport.ContainsHeader(_requestM
 222
 3432223            protected internal override bool RemoveHeader(string name) => HttpClientTransport.RemoveHeader(_requestMessa
 224
 2316225            protected internal override IEnumerable<HttpHeader> EnumerateHeaders() => GetHeaders(_requestMessage.Headers
 226
 227            public HttpRequestMessage BuildRequestMessage(CancellationToken cancellation)
 228            {
 229                HttpRequestMessage currentRequest;
 2724230                if (_wasSent)
 231                {
 232                    // A copy of a message needs to be made because HttpClient does not allow sending the same message t
 233                    // and so the retry logic fails.
 412234                    currentRequest = new HttpRequestMessage(_requestMessage.Method, Uri.ToUri());
 412235                    CopyHeaders(_requestMessage.Headers, currentRequest.Headers);
 236                }
 237                else
 238                {
 2312239                    currentRequest = _requestMessage;
 240                }
 241
 2724242                currentRequest.RequestUri = Uri.ToUri();
 243
 244
 2724245                if (Content != null)
 246                {
 247                    PipelineContentAdapter currentContent;
 496248                    if (_wasSent)
 249                    {
 4250                        currentContent = new PipelineContentAdapter();
 4251                        CopyHeaders(_requestContent!.Headers, currentContent.Headers);
 252                    }
 253                    else
 254                    {
 492255                        currentContent = EnsureContentInitialized();
 256                    }
 257
 496258                    currentContent.CancellationToken = cancellation;
 496259                    currentContent.PipelineContent = Content;
 496260                    currentRequest.Content = currentContent;
 261                }
 262
 2724263                _wasSent = true;
 2724264                return currentRequest;
 265            }
 266
 267            public override void Dispose()
 268            {
 1628269                Content?.Dispose();
 1628270                _requestMessage.Dispose();
 1628271            }
 272
 0273            public override string ToString() => _requestMessage.ToString();
 274
 2275            private static readonly HttpMethod s_patch = new HttpMethod("PATCH");
 276
 277            private static HttpMethod ToHttpClientMethod(RequestMethod requestMethod)
 278            {
 654279                var method = requestMethod.Method;
 280                // Fast-path common values
 654281                if (method.Length == 3)
 282                {
 150283                    if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
 284                    {
 142285                        return HttpMethod.Get;
 286                    }
 287
 8288                    if (string.Equals(method, "PUT", StringComparison.OrdinalIgnoreCase))
 289                    {
 8290                        return HttpMethod.Put;
 291                    }
 292                }
 504293                else if (method.Length == 4)
 294                {
 492295                    if (string.Equals(method, "POST", StringComparison.OrdinalIgnoreCase))
 296                    {
 488297                        return HttpMethod.Post;
 298                    }
 4299                    if (string.Equals(method, "HEAD", StringComparison.OrdinalIgnoreCase))
 300                    {
 4301                        return HttpMethod.Head;
 302                    }
 303                }
 304                else
 305                {
 12306                    if (string.Equals(method, "PATCH", StringComparison.OrdinalIgnoreCase))
 307                    {
 4308                        return s_patch;
 309                    }
 8310                    if (string.Equals(method, "DELETE", StringComparison.OrdinalIgnoreCase))
 311                    {
 4312                        return HttpMethod.Delete;
 313                    }
 314                }
 315
 4316                return new HttpMethod(method);
 317            }
 318
 319            private PipelineContentAdapter EnsureContentInitialized()
 320            {
 842321                if (_requestContent == null)
 322                {
 538323                    _requestContent = new PipelineContentAdapter();
 324                }
 325
 842326                return _requestContent;
 327            }
 328
 329            private sealed class PipelineContentAdapter : HttpContent
 330            {
 1468331                public RequestContent? PipelineContent { get; set; }
 332
 992333                public CancellationToken CancellationToken { get; set; }
 334
 335                protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
 336                {
 337                    Debug.Assert(PipelineContent != null);
 496338                    await PipelineContent!.WriteToAsync(stream, CancellationToken).ConfigureAwait(false);
 496339                }
 340
 341                protected override bool TryComputeLength(out long length)
 342                {
 343                    Debug.Assert(PipelineContent != null);
 344
 476345                    return PipelineContent!.TryComputeLength(out length);
 346                }
 347            }
 348        }
 349
 350        private sealed class PipelineResponse : Response
 351        {
 352            private readonly HttpResponseMessage _responseMessage;
 353
 354            private readonly HttpContent _responseContent;
 355
 356            private Stream? _contentStream;
 357
 2704358            public PipelineResponse(string requestId, HttpResponseMessage responseMessage, Stream? contentStream)
 359            {
 2704360                ClientRequestId = requestId ?? throw new ArgumentNullException(nameof(requestId));
 2704361                _responseMessage = responseMessage ?? throw new ArgumentNullException(nameof(responseMessage));
 2704362                _contentStream = contentStream;
 2704363                _responseContent = _responseMessage.Content;
 2704364            }
 365
 6480366            public override int Status => (int)_responseMessage.StatusCode;
 367
 2020368            public override string ReasonPhrase => _responseMessage.ReasonPhrase;
 369
 370            public override Stream? ContentStream
 371            {
 12080372                get => _contentStream;
 373                set
 374                {
 375                    // Make sure we don't dispose the content if the stream was replaced
 2872376                    _responseMessage.Content = null;
 377
 2872378                    _contentStream = value;
 2872379                }
 380            }
 381
 6744382            public override string ClientRequestId { get; set; }
 383
 3376384            protected internal override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) => HttpCli
 385
 172386            protected internal override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable<string>
 387
 164388            protected internal override bool ContainsHeader(string name) => HttpClientTransport.ContainsHeader(_response
 389
 2180390            protected internal override IEnumerable<HttpHeader> EnumerateHeaders() => GetHeaders(_responseMessage.Header
 391
 392            public override void Dispose()
 393            {
 1624394                _responseMessage?.Dispose();
 1624395            }
 396
 0397            public override string ToString() => _responseMessage.ToString();
 398        }
 399    }
 400}