浅从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模式了解后的小伙伴们也一定能想得到,既然是HttpMessageHandler的Adapter,那么 在其类中 一定定义了一个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 }