usercount

浅从System.Web.Http.Owin的HttpMessageHandlerAdapter看适配器模式

本文版权归博客园和作者吴双本人共同所有 转载和爬虫请注明原文地址 www.cnblogs.com/tdws

一.写在前面

 适配器模式(Adapter)

可用来在现有接口和不兼容的类之间进行适配。有助于避免大规模改写现有客户代码,其工作机制是对现有类的接口进行包装,这样客户程序就能使用这个并非为其量身打造的类而又无需为此大动手术。                  ----《JS设计模式》

将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

                       ----《Head First设计模式》

这两本书中对适配器模式定义如此,适配器模式在多种设计模式当中属于比较容易理解的一种,其目的或者说可以解决的问题是新功能/新类型,不受原有类型/方法/功能的兼容,有了适配器这种巧妙地经验,我们可以保证对修改封闭,对拓展开放。而达到此目的,正需要面向接口,并保持职责的单一性。也许对C#开发者来说,见的最多的就是SqlDataAdapter。

                  

二.认识UseWebApi

本文所涉及OWIN,.NetFramework,Webapi 开源源码下载地址为:

https://github.com/aspnet/AspNetKatana

https://github.com/ASP-NET-MVC/aspnetwebstack

https://github.com/dotnet/corefx 

熟悉OWIN体系的小伙伴们,一定都在Startup.cs中见过也使用过app.UseWebApi吧。app是IAppBuilder的对象

Startup.cs是OWIN katana实现的启动类,刚说的UseWebApi顾名思义,就是将WebApi作为一个OWIN中间件放在整个处理流程中。app是IAppBuilder的对象,其创建由IAppBuilderFactory负责。IAppBuilder定义了三个方法,分别为Build,New和Use.   这三个方法分别负责什么呢?

Build,返回OWIN管道入口点的一个实例,由 Microsoft.Owin.Host.SystemWeb中的Init方法调用。其返回实例将被转换为AppFun类型,AppFun( using AppFunc = Func<IDictionary<string, object>, Task>;)是什么呢?它是OWIN服务器与应用程序交互的应用程序委托,我们看到这个方法在OWIN.Host中调用,应该就能大概猜到个所以然。

New,用于返回一个AppBuilder实例,由IAppBuilderFactory调用并返回。

Use,就是我们在OWIN体系中,经常使用到的方法,我们可以定义自己的OWIN中间件,按照其定义规范,并Use到处理管道中,比如用户操作日志中间件,用户身份校验中间件等。

说到这里,我们应该很清晰的了解到WebApi是OWIN的一个中间件而已了吧。举个栗子:

 1 public partial class Startup
 2     {
 3 
 4         public void Configuration(IAppBuilder app)
 5         {
 6             // This must happen FIRST otherwise CORS will not work. 
 7             // 引入OWin.Cors 解决跨域访问问题
 8             app.UseCors(CorsOptions.AllowAll);
 9 
10             GlobalConfiguration.Configure(WebApiConfig.Register);
11             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
12             
13             ConfigureAuth(app);
14 
15             app.Use<UserContextMiddleware>();
16             app.Use<UserOperationMiddleware>();
17             app.UseWebApi(GlobalConfiguration.Configuration);
18         }
19     }

三.UseWebApi的实现

 看到这里你一定会问,为什么IAppBuilder中没有定义UseWebapi方法呢,UseWebapi的实现在System.Web.Http.Owin的WebApiAppBuilderExtensions.cs中,UseWebApi是一个C# this拓展方法,和你所想到的答案并无差。在其实现中,调用了  builder.Use(typeof(HttpMessageHandlerAdapter), options);  

到这里,一定要啰嗦几句不要怪我,Adapter的实现步骤:为了使一个类或者一个功能,兼容已有类/接口,那么

1.被适配器实现目标客户的接口或抽象方法,以便参数的传入

2.所实现接口/抽象类的方法中调用目标客户的方法

HttpMessageHandlerAdapter 这个主角终于出现了,对Adapter模式了解后的小伙伴们也一定能想得到,既然是HttpMessageHandlerAdapter,那么 在其类中 一定定义了一个private的字段,并且类型为HttpMessageHandler,你也一定能想得到这个Adapter继承了OwinMiddleware这个抽象类型并且实现其Invoke抽象方法,在HttpMessageHandlerAdapter的一个方法中一定调用了HttpMessageHandler的方法。那么通过源码我们了解到HttpMessageHandler的字段名为_messageHandler。(是不是和上面所说的Adapter实现方式类似呢,实现方式可能概括的不好,建议参阅更多文章和范例)

Asp.Net Webapi的消息处理管道是由HttpMessageHandler的委托链所组成的处理管道

HttpMessageHandler抽象类当中顶一个一个唯一的抽象方法用于实现,其入参为HttpRequestMessage,其出参为HttpResponseMessage。

1 protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

DelegatingHandler实现了HttpMessageHandler,其构造函数中传入HttpMessageHandler,并由同类对象innerHandler构成委托链。

推荐一篇MS文档 https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers,有兴趣可以稍微参照下。

 1         protected DelegatingHandler(HttpMessageHandler innerHandler)
 2         {
 3             InnerHandler = innerHandler;
 4         }
 5 
 6         protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 7         {
 8             if (request == null)
 9             {
10                 throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest);
11             }
12             SetOperationStarted();
13             return _innerHandler.SendAsync(request, cancellationToken);
14         }

中间啰嗦了一串,为了说明HttpMessageHandler的作用,这样我们能进一步理解,为什么要有HttpMessageHandlerAdapter的存在,并在Use (WebApi中间件)的时候,将该类型传入。

在HttpMessageHandlerAdapter构造函数中,_messageHandler被包装为HttpMessageInvoker类型,这个类型的目的是提供一个专门的类,用于调用SendAsync方法。

刚才我们已经了解到HttpMessageHandlerAdapter实现了OWinMiddleware, 那么我们从源码中了解下,在其实现的抽象方法Invoke中,做了什么事情:其调用同类下的InvokeCore方法,InvokeCore中Create了HttpRequestMessage,并将其对象作为SendAsync的入参,最后得到HttpResponseMessage对象。

四.写在最后

      就是这样,一次通过源码的阅读,再次对Adapter的理解,HttpMessageHandlerAdapter最终通过对OwinMiddleware的实现,在Invoke中通过HttpMessageInvoker调用执行SendAsync,丢入HttpRequestMessage,拿到ResponseMessage.最终附上HttpMessageHandlerAdapter源码,更多源码,还是从第二段的连接中下载吧。

  1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2 
  3 using System.Collections.Generic;
  4 using System.Diagnostics;
  5 using System.Diagnostics.CodeAnalysis;
  6 using System.Diagnostics.Contracts;
  7 using System.IO;
  8 using System.Net;
  9 using System.Net.Http;
 10 using System.Net.Http.Headers;
 11 using System.Runtime.ExceptionServices;
 12 using System.Security.Principal;
 13 using System.Threading;
 14 using System.Threading.Tasks;
 15 using System.Web.Http.Controllers;
 16 using System.Web.Http.ExceptionHandling;
 17 using System.Web.Http.Hosting;
 18 using System.Web.Http.Owin.ExceptionHandling;
 19 using System.Web.Http.Owin.Properties;
 20 using Microsoft.Owin;
 21 
 22 namespace System.Web.Http.Owin
 23 {
 24     /// <summary>
 25     /// Represents an OWIN component that submits requests to an <see cref="HttpMessageHandler"/> when invoked.
 26     /// </summary>
 27     public class HttpMessageHandlerAdapter : OwinMiddleware, IDisposable
 28     {
 29         private readonly HttpMessageHandler _messageHandler;
 30         private readonly HttpMessageInvoker _messageInvoker;
 31         private readonly IHostBufferPolicySelector _bufferPolicySelector;
 32         private readonly IExceptionLogger _exceptionLogger;
 33         private readonly IExceptionHandler _exceptionHandler;
 34         private readonly CancellationToken _appDisposing;
 35 
 36         private bool _disposed;
 37 
 38         /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary>
 39         /// <param name="next">The next component in the pipeline.</param>
 40         /// <param name="options">The options to configure this adapter.</param>
 41         public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandlerOptions options)
 42             : base(next)
 43         {
 44             if (options == null)
 45             {
 46                 throw new ArgumentNullException("options");
 47             }
 48 
 49             _messageHandler = options.MessageHandler;
 50 
 51             if (_messageHandler == null)
 52             {
 53                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 54                     typeof(HttpMessageHandlerOptions).Name, "MessageHandler"), "options");
 55             }
 56 
 57             _messageInvoker = new HttpMessageInvoker(_messageHandler);
 58             _bufferPolicySelector = options.BufferPolicySelector;
 59 
 60             if (_bufferPolicySelector == null)
 61             {
 62                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 63                     typeof(HttpMessageHandlerOptions).Name, "BufferPolicySelector"), "options");
 64             }
 65 
 66             _exceptionLogger = options.ExceptionLogger;
 67 
 68             if (_exceptionLogger == null)
 69             {
 70                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 71                     typeof(HttpMessageHandlerOptions).Name, "ExceptionLogger"), "options");
 72             }
 73 
 74             _exceptionHandler = options.ExceptionHandler;
 75 
 76             if (_exceptionHandler == null)
 77             {
 78                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 79                     typeof(HttpMessageHandlerOptions).Name, "ExceptionHandler"), "options");
 80             }
 81 
 82             _appDisposing = options.AppDisposing;
 83 
 84             if (_appDisposing.CanBeCanceled)
 85             {
 86                 _appDisposing.Register(OnAppDisposing);
 87             }
 88         }
 89 
 90         /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary>
 91         /// <param name="next">The next component in the pipeline.</param>
 92         /// <param name="messageHandler">The <see cref="HttpMessageHandler"/> to submit requests to.</param>
 93         /// <param name="bufferPolicySelector">
 94         /// The <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and
 95         /// responses.
 96         /// </param>
 97         /// <remarks>
 98         /// This constructor is retained for backwards compatibility. The constructor taking
 99         /// <see cref="HttpMessageHandlerOptions"/> should be used instead.
100         /// </remarks>
101         [Obsolete("Use the HttpMessageHandlerAdapter(OwinMiddleware, HttpMessageHandlerOptions) constructor instead.")]
102         public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandler messageHandler,
103             IHostBufferPolicySelector bufferPolicySelector)
104             : this(next, CreateOptions(messageHandler, bufferPolicySelector))
105         {
106         }
107 
108         /// <summary>Gets the <see cref="HttpMessageHandler"/> to submit requests to.</summary>
109         public HttpMessageHandler MessageHandler
110         {
111             get { return _messageHandler; }
112         }
113 
114         /// <summary>
115         /// Gets the <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and
116         /// responses.
117         /// </summary>
118         public IHostBufferPolicySelector BufferPolicySelector
119         {
120             get { return _bufferPolicySelector; }
121         }
122 
123         /// <summary>Gets the <see cref="IExceptionLogger"/> to use to log unhandled exceptions.</summary>
124         public IExceptionLogger ExceptionLogger
125         {
126             get { return _exceptionLogger; }
127         }
128 
129         /// <summary>Gets the <see cref="IExceptionHandler"/> to use to process unhandled exceptions.</summary>
130         public IExceptionHandler ExceptionHandler
131         {
132             get { return _exceptionHandler; }
133         }
134 
135         /// <summary>Gets the <see cref="CancellationToken"/> that triggers cleanup of this component.</summary>
136         public CancellationToken AppDisposing
137         {
138             get { return _appDisposing; }
139         }
140 
141         /// <inheritdoc />
142         public override Task Invoke(IOwinContext context)
143         {
144             if (context == null)
145             {
146                 throw new ArgumentNullException("context");
147             }
148 
149             IOwinRequest owinRequest = context.Request;
150             IOwinResponse owinResponse = context.Response;
151 
152             if (owinRequest == null)
153             {
154                 throw Error.InvalidOperation(OwinResources.OwinContext_NullRequest);
155             }
156             if (owinResponse == null)
157             {
158                 throw Error.InvalidOperation(OwinResources.OwinContext_NullResponse);
159             }
160 
161             return InvokeCore(context, owinRequest, owinResponse);
162         }
163 
164         private async Task InvokeCore(IOwinContext context, IOwinRequest owinRequest, IOwinResponse owinResponse)
165         {
166             CancellationToken cancellationToken = owinRequest.CallCancelled;
167             HttpContent requestContent;
168 
169             bool bufferInput = _bufferPolicySelector.UseBufferedInputStream(hostContext: context);
170 
171             if (!bufferInput)
172             {
173                 owinRequest.DisableBuffering();
174             }
175 
176             if (!owinRequest.Body.CanSeek && bufferInput)
177             {
178                 requestContent = await CreateBufferedRequestContentAsync(owinRequest, cancellationToken);
179             }
180             else
181             {
182                 requestContent = CreateStreamedRequestContent(owinRequest);
183             }
184 
185             HttpRequestMessage request = CreateRequestMessage(owinRequest, requestContent);
186             MapRequestProperties(request, context);
187 
188             SetPrincipal(owinRequest.User);
189 
190             HttpResponseMessage response = null;
191             bool callNext;
192 
193             try
194             {
195                 response = await _messageInvoker.SendAsync(request, cancellationToken);
196 
197                 // Handle null responses
198                 if (response == null)
199                 {
200                     throw Error.InvalidOperation(OwinResources.SendAsync_ReturnedNull);
201                 }
202 
203                 // Handle soft 404s where no route matched - call the next component
204                 if (IsSoftNotFound(request, response))
205                 {
206                     callNext = true;
207                 }
208                 else
209                 {
210                     callNext = false;
211 
212                     // Compute Content-Length before calling UseBufferedOutputStream because the default implementation
213                     // accesses that header and we want to catch any exceptions calling TryComputeLength here.
214 
215                     if (response.Content == null
216                         || await ComputeContentLengthAsync(request, response, owinResponse, cancellationToken))
217                     {
218                         bool bufferOutput = _bufferPolicySelector.UseBufferedOutputStream(response);
219 
220                         if (!bufferOutput)
221                         {
222                             owinResponse.DisableBuffering();
223                         }
224                         else if (response.Content != null)
225                         {
226                             response = await BufferResponseContentAsync(request, response, cancellationToken);
227                         }
228 
229                         if (await PrepareHeadersAsync(request, response, owinResponse, cancellationToken))
230                         {
231                             await SendResponseMessageAsync(request, response, owinResponse, cancellationToken);
232                         }
233                     }
234                 }
235             }
236             finally
237             {
238                 request.DisposeRequestResources();
239                 request.Dispose();
240                 if (response != null)
241                 {
242                     response.Dispose();
243                 }
244             }
245 
246             // Call the next component if no route matched
247             if (callNext && Next != null)
248             {
249                 await Next.Invoke(context);
250             }
251         }
252 
253         private static HttpContent CreateStreamedRequestContent(IOwinRequest owinRequest)
254         {
255             // Note that we must NOT dispose owinRequest.Body in this case. Disposing it would close the input
256             // stream and prevent cascaded components from accessing it. The server MUST handle any necessary
257             // cleanup upon request completion. NonOwnedStream prevents StreamContent (or its callers including
258             // HttpRequestMessage) from calling Close or Dispose on owinRequest.Body.
259             return new StreamContent(new NonOwnedStream(owinRequest.Body));
260         }
261 
262         private static async Task<HttpContent> CreateBufferedRequestContentAsync(IOwinRequest owinRequest,
263             CancellationToken cancellationToken)
264         {
265             // We need to replace the request body with a buffered stream so that other components can read the stream.
266             // For this stream to be useful, it must NOT be diposed along with the request. Streams created by
267             // StreamContent do get disposed along with the request, so use MemoryStream to buffer separately.
268             MemoryStream buffer;
269             int? contentLength = owinRequest.GetContentLength();
270 
271             if (!contentLength.HasValue)
272             {
273                 buffer = new MemoryStream();
274             }
275             else
276             {
277                 buffer = new MemoryStream(contentLength.Value);
278             }
279 
280             cancellationToken.ThrowIfCancellationRequested();
281 
282             using (StreamContent copier = new StreamContent(owinRequest.Body))
283             {
284                 await copier.CopyToAsync(buffer);
285             }
286 
287             // Provide the non-disposing, buffered stream to later OWIN components (set to the stream's beginning).
288             buffer.Position = 0;
289             owinRequest.Body = buffer;
290 
291             // For MemoryStream, Length is guaranteed to be an int.
292             return new ByteArrayContent(buffer.GetBuffer(), 0, (int)buffer.Length);
293         }
294 
295         private static HttpRequestMessage CreateRequestMessage(IOwinRequest owinRequest, HttpContent requestContent)
296         {
297             // Create the request
298             HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(owinRequest.Method), owinRequest.Uri);
299 
300             try
301             {
302                 // Set the body
303                 request.Content = requestContent;
304 
305                 // Copy the headers
306                 foreach (KeyValuePair<string, string[]> header in owinRequest.Headers)
307                 {
308                     if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
309                     {
310                         bool success = requestContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
311                         Contract.Assert(success,
312                             "Every header can be added either to the request headers or to the content headers");
313                     }
314                 }
315             }
316             catch
317             {
318                 request.Dispose();
319                 throw;
320             }
321 
322             return request;
323         }
324 
325         private static void MapRequestProperties(HttpRequestMessage request, IOwinContext context)
326         {
327             // Set the OWIN context on the request
328             request.SetOwinContext(context);
329 
330             // Set a request context on the request that lazily populates each property.
331             HttpRequestContext requestContext = new OwinHttpRequestContext(context, request);
332             request.SetRequestContext(requestContext);
333         }
334 
335         private static void SetPrincipal(IPrincipal user)
336         {
337             if (user != null)
338             {
339                 Thread.CurrentPrincipal = user;
340             }
341         }
342 
343         private static bool IsSoftNotFound(HttpRequestMessage request, HttpResponseMessage response)
344         {
345             if (response.StatusCode == HttpStatusCode.NotFound)
346             {
347                 bool routingFailure;
348                 if (request.Properties.TryGetValue<bool>(HttpPropertyKeys.NoRouteMatched, out routingFailure)
349                     && routingFailure)
350                 {
351                     return true;
352                 }
353             }
354             return false;
355         }
356 
357         private async Task<HttpResponseMessage> BufferResponseContentAsync(HttpRequestMessage request,
358             HttpResponseMessage response, CancellationToken cancellationToken)
359         {
360             ExceptionDispatchInfo exceptionInfo;
361 
362             cancellationToken.ThrowIfCancellationRequested();
363 
364             try
365             {
366                 await response.Content.LoadIntoBufferAsync();
367                 return response;
368             }
369             catch (OperationCanceledException)
370             {
371                 // Propogate the canceled task without calling exception loggers or handlers.
372                 throw;
373             }
374             catch (Exception exception)
375             {
376                 exceptionInfo = ExceptionDispatchInfo.Capture(exception);
377             }
378 
379             // If the content can't be buffered, create a buffered error response for the exception
380             // This code will commonly run when a formatter throws during the process of serialization
381 
382             Debug.Assert(exceptionInfo.SourceException != null);
383 
384             ExceptionContext exceptionContext = new ExceptionContext(exceptionInfo.SourceException,
385                 OwinExceptionCatchBlocks.HttpMessageHandlerAdapterBufferContent, request, response);
386 
387             await _exceptionLogger.LogAsync(exceptionContext, cancellationToken);
388             HttpResponseMessage errorResponse = await _exceptionHandler.HandleAsync(exceptionContext,
389                 cancellationToken);
390 
391             response.Dispose();
392 
393             if (errorResponse == null)
394             {
395                 exceptionInfo.Throw();
396                 return null;
397             }
398 
399             // We have an error response to try to buffer and send back.
400 
401             response = errorResponse;
402             cancellationToken.ThrowIfCancellationRequested();
403 
404             Exception errorException;
405 
406             try
407             {
408                 // Try to buffer the error response and send it back.
409                 await response.Content.LoadIntoBufferAsync();
410                 return response;
411             }
412             catch (OperationCanceledException)
413             {
414                 // Propogate the canceled task without calling exception loggers.
415                 throw;
416             }
417             catch (Exception exception)
418             {
419                 errorException = exception;
420             }
421 
422             // We tried to send back an error response with content, but we couldn't. It's an edge case; the best we
423             // can do is to log that exception and send back an empty 500.
424 
425             ExceptionContext errorExceptionContext = new ExceptionContext(errorException,
426                 OwinExceptionCatchBlocks.HttpMessageHandlerAdapterBufferError, request, response);
427             await _exceptionLogger.LogAsync(errorExceptionContext, cancellationToken);
428 
429             response.Dispose();
430             return request.CreateResponse(HttpStatusCode.InternalServerError);
431         }
432 
433         // Prepares Content-Length and Transfer-Encoding headers.
434         private Task<bool> PrepareHeadersAsync(HttpRequestMessage request, HttpResponseMessage response,
435             IOwinResponse owinResponse, CancellationToken cancellationToken)
436         {
437             Contract.Assert(response != null);
438             HttpResponseHeaders responseHeaders = response.Headers;
439             Contract.Assert(responseHeaders != null);
440             HttpContent content = response.Content;
441             bool isTransferEncodingChunked = responseHeaders.TransferEncodingChunked == true;
442             HttpHeaderValueCollection<TransferCodingHeaderValue> transferEncoding = responseHeaders.TransferEncoding;
443 
444             if (content != null)
445             {
446                 HttpContentHeaders contentHeaders = content.Headers;
447                 Contract.Assert(contentHeaders != null);
448 
449                 if (isTransferEncodingChunked)
450                 {
451                     // According to section 4.4 of the HTTP 1.1 spec, HTTP responses that use chunked transfer
452                     // encoding must not have a content length set. Chunked should take precedence over content
453                     // length in this case because chunked is always set explicitly by users while the Content-Length
454                     // header can be added implicitly by System.Net.Http.
455                     contentHeaders.ContentLength = null;
456                 }
457                 else
458                 {
459                     // Copy the response content headers only after ensuring they are complete.
460                     // We ask for Content-Length first because HttpContent lazily computes this header and only
461                     // afterwards writes the value into the content headers.
462                     return ComputeContentLengthAsync(request, response, owinResponse, cancellationToken);
463                 }
464             }
465 
466             // Ignore the Transfer-Encoding header if it is just "chunked"; the host will likely provide it when no
467             // Content-Length is present (and if the host does not, there's not much better this code could do to
468             // transmit the current response, since HttpContent is assumed to be unframed; in that case, silently drop
469             // the Transfer-Encoding: chunked header).
470             // HttpClient sets this header when it receives chunked content, but HttpContent does not include the
471             // frames. The OWIN contract is to set this header only when writing chunked frames to the stream.
472             // A Web API caller who desires custom framing would need to do a different Transfer-Encoding (such as
473             // "identity, chunked").
474             if (isTransferEncodingChunked && transferEncoding.Count == 1)
475             {
476                 transferEncoding.Clear();
477             }
478 
479             return Task.FromResult(true);
480         }
481 
482         [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "unused",
483             Justification = "unused variable necessary to call getter")]
484         [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
485             Justification = "Exception is turned into an error response.")]
486         private Task<bool> ComputeContentLengthAsync(HttpRequestMessage request, HttpResponseMessage response,
487             IOwinResponse owinResponse, CancellationToken cancellationToken)
488         {
489             Contract.Assert(response != null);
490             HttpResponseHeaders responseHeaders = response.Headers;
491             Contract.Assert(responseHeaders != null);
492             HttpContent content = response.Content;
493             Contract.Assert(content != null);
494             HttpContentHeaders contentHeaders = content.Headers;
495             Contract.Assert(contentHeaders != null);
496 
497             Exception exception;
498 
499             try
500             {
501                 var unused = contentHeaders.ContentLength;
502 
503                 return Task.FromResult(true);
504             }
505             catch (Exception ex)
506             {
507                 exception = ex;
508             }
509 
510             return HandleTryComputeLengthExceptionAsync(exception, request, response, owinResponse, cancellationToken);
511         }
512 
513         private async Task<bool> HandleTryComputeLengthExceptionAsync(Exception exception, HttpRequestMessage request,
514             HttpResponseMessage response, IOwinResponse owinResponse, CancellationToken cancellationToken)
515         {
516             Contract.Assert(owinResponse != null);
517 
518             ExceptionContext exceptionContext = new ExceptionContext(exception,
519                 OwinExceptionCatchBlocks.HttpMessageHandlerAdapterComputeContentLength, request, response);
520             await _exceptionLogger.LogAsync(exceptionContext, cancellationToken);
521 
522             // Send back an empty error response if TryComputeLength throws.
523             owinResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
524             SetHeadersForEmptyResponse(owinResponse.Headers);
525             return false;
526         }
527 
528         private Task SendResponseMessageAsync(HttpRequestMessage request, HttpResponseMessage response,
529             IOwinResponse owinResponse, CancellationToken cancellationToken)
530         {
531             owinResponse.StatusCode = (int)response.StatusCode;
532             owinResponse.ReasonPhrase = response.ReasonPhrase;
533 
534             // Copy non-content headers
535             IDictionary<string, string[]> responseHeaders = owinResponse.Headers;
536             foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers)
537             {
538                 responseHeaders[header.Key] = header.Value.AsArray();
539             }
540 
541             HttpContent responseContent = response.Content;
542             if (responseContent == null)
543             {
544                 SetHeadersForEmptyResponse(responseHeaders);
545                 return TaskHelpers.Completed();
546             }
547             else
548             {
549                 // Copy content headers
550                 foreach (KeyValuePair<string, IEnumerable<string>> contentHeader in responseContent.Headers)
551                 {
552                     responseHeaders[contentHeader.Key] = contentHeader.Value.AsArray();
553                 }
554 
555                 // Copy body
556                 return SendResponseContentAsync(request, response, owinResponse, cancellationToken);
557             }
558         }
559 
560         private static void SetHeadersForEmptyResponse(IDictionary<string, string[]> headers)
561         {
562             // Set the content-length to 0 to prevent the server from sending back the response chunked
563             headers["Content-Length"] = new string[] { "0" };
564         }
565 
566         private async Task SendResponseContentAsync(HttpRequestMessage request, HttpResponseMessage response,
567             IOwinResponse owinResponse, CancellationToken cancellationToken)
568         {
569             Contract.Assert(response != null);
570             Contract.Assert(response.Content != null);
571 
572             Exception exception;
573             cancellationToken.ThrowIfCancellationRequested();
574 
575             try
576             {
577                 await response.Content.CopyToAsync(owinResponse.Body);
578                 return;
579             }
580             catch (OperationCanceledException)
581             {
582                 // Propogate the canceled task without calling exception loggers;
583                 throw;
584             }
585             catch (Exception ex)
586             {
587                 exception = ex;
588             }
589 
590             // We're streaming content, so we can only call loggers, not handlers, as we've already (possibly) send the
591             // status code and headers across the wire. Log the exception, but then just abort.
592             ExceptionContext exceptionContext = new ExceptionContext(exception,
593                 OwinExceptionCatchBlocks.HttpMessageHandlerAdapterStreamContent, request, response);
594             await _exceptionLogger.LogAsync(exceptionContext, cancellationToken);
595             await AbortResponseAsync();
596         }
597 
598         private static Task AbortResponseAsync()
599         {
600             // OWIN doesn't yet support an explicit Abort event. Returning a canceled task is the best contract at the
601             // moment.
602             return TaskHelpers.Canceled();
603         }
604 
605         // Provides HttpMessageHandlerOptions for callers using the old constructor.
606         private static HttpMessageHandlerOptions CreateOptions(HttpMessageHandler messageHandler,
607             IHostBufferPolicySelector bufferPolicySelector)
608         {
609             if (messageHandler == null)
610             {
611                 throw new ArgumentNullException("messageHandler");
612             }
613 
614             if (bufferPolicySelector == null)
615             {
616                 throw new ArgumentNullException("bufferPolicySelector");
617             }
618 
619             // Callers using the old constructor get the default exception handler, no exception logging support, and no
620             // app cleanup support.
621 
622             return new HttpMessageHandlerOptions
623             {
624                 MessageHandler = messageHandler,
625                 BufferPolicySelector = bufferPolicySelector,
626                 ExceptionLogger = new EmptyExceptionLogger(),
627                 ExceptionHandler = new DefaultExceptionHandler(),
628                 AppDisposing = CancellationToken.None
629             };
630         }
631 
632         /// <summary>
633         /// Releases unmanaged and optionally managed resources.
634         /// </summary>
635         /// <param name="disposing">
636         /// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release
637         /// only unmanaged resources.
638         /// </param>
639         /// <remarks>
640         /// This class implements <see cref="IDisposable"/> for legacy reasons. New callers should instead provide a
641         /// cancellation token via <see cref="AppDisposing"/> using the constructor that takes
642         /// <see cref="HttpMessageHandlerOptions"/>.
643         /// </remarks>
644         protected virtual void Dispose(bool disposing)
645         {
646             if (disposing)
647             {
648                 OnAppDisposing();
649             }
650         }
651 
652         /// <inheritdoc />
653         /// <remarks>
654         /// This class implements <see cref="IDisposable"/> for legacy reasons. New callers should instead provide a
655         /// cancellation token via <see cref="AppDisposing"/> using the constructor that takes
656         /// <see cref="HttpMessageHandlerOptions"/>.
657         /// </remarks>
658         public void Dispose()
659         {
660             Dispose(true);
661             GC.SuppressFinalize(this);
662         }
663 
664         private void OnAppDisposing()
665         {
666             if (!_disposed)
667             {
668                 _messageInvoker.Dispose();
669                 _disposed = true;
670             }
671         }
672     }
673 }
View Code

 

posted @ 2017-06-28 00:09  坦荡  阅读(1480)  评论(0编辑  收藏  举报