本文版權歸博客園和做者吳雙本人共同全部 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdwsgit
適配器模式(Adapter)github
可用來在現有接口和不兼容的類之間進行適配。有助於避免大規模改寫現有客戶代碼,其工做機制是對現有類的接口進行包裝,這樣客戶程序就能使用這個並不是爲其量身打造的類而又無需爲此大動手術。 ----《JS設計模式》web
將一個類的接口,轉換成客戶指望的另外一個接口。適配器讓本來接口不兼容的類能夠合做無間。設計模式
----《Head First設計模式》api
這兩本書中對適配器模式定義如此,適配器模式在多種設計模式當中屬於比較容易理解的一種,其目的或者說能夠解決的問題是新功能/新類型,不受原有類型/方法/功能的兼容,有了適配器這種巧妙地經驗,咱們能夠保證對修改封閉,對拓展開放。而達到此目的,正須要面向接口,並保持職責的單一性。也許對C#開發者來講,見的最多的就是SqlDataAdapter。跨域
本文所涉及OWIN,.NetFramework,Webapi 開源源碼下載地址爲:服務器
https://github.com/aspnet/AspNetKatanaapp
https://github.com/ASP-NET-MVC/aspnetwebstackasync
https://github.com/dotnet/corefx ide
熟悉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 }
看到這裏你必定會問,爲何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 }