/
MessageProcessingHandler.cs
150 lines (132 loc) · 6.69 KB
/
MessageProcessingHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Http
{
public abstract class MessageProcessingHandler : DelegatingHandler
{
protected MessageProcessingHandler()
{
}
protected MessageProcessingHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected abstract HttpRequestMessage ProcessRequest(HttpRequestMessage request,
CancellationToken cancellationToken);
protected abstract HttpResponseMessage ProcessResponse(HttpResponseMessage response,
CancellationToken cancellationToken);
protected internal sealed override HttpResponseMessage Send(HttpRequestMessage request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
// Since most of the SendAsync code is just Task handling, there's no reason to share the code.
HttpRequestMessage newRequestMessage = ProcessRequest(request, cancellationToken);
HttpResponseMessage response = base.Send(newRequestMessage, cancellationToken);
HttpResponseMessage newResponseMessage = ProcessResponse(response, cancellationToken);
return newResponseMessage;
}
protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
// ProcessRequest() and ProcessResponse() are supposed to be fast, so we call ProcessRequest() on the same
// thread SendAsync() was invoked to avoid context switches. However, if ProcessRequest() throws, we have
// to catch the exception since the caller doesn't expect exceptions when calling SendAsync(): The
// expectation is that the returned task will get faulted on errors, but the async call to SendAsync()
// should complete.
var tcs = new SendState(this, cancellationToken);
try
{
HttpRequestMessage newRequestMessage = ProcessRequest(request, cancellationToken);
Task<HttpResponseMessage> sendAsyncTask = base.SendAsync(newRequestMessage, cancellationToken);
// We schedule a continuation task once the inner handler completes in order to trigger the response
// processing method. ProcessResponse() is only called if the task wasn't canceled before.
sendAsyncTask.ContinueWith(static (task, state) =>
{
var sendState = (SendState)state!;
MessageProcessingHandler self = sendState._handler;
CancellationToken token = sendState._token;
if (task.IsFaulted)
{
sendState.TrySetException(task.Exception!.GetBaseException());
return;
}
if (task.IsCanceled)
{
sendState.TrySetCanceled(token);
return;
}
if (task.Result == null)
{
sendState.TrySetException(ExceptionDispatchInfo.SetCurrentStackTrace(new InvalidOperationException(SR.net_http_handler_noresponse)));
return;
}
try
{
HttpResponseMessage responseMessage = self.ProcessResponse(task.Result, token);
sendState.TrySetResult(responseMessage);
}
catch (OperationCanceledException e)
{
// If ProcessResponse() throws an OperationCanceledException check whether it is related to
// the cancellation token we received from the user. If so, cancel the Task.
HandleCanceledOperations(token, sendState, e);
}
catch (Exception e)
{
sendState.TrySetException(e);
}
// We don't pass the cancellation token to the continuation task, since we want to get called even
// if the operation was canceled: We'll set the Task returned to the user to canceled. Passing the
// cancellation token here would result in the continuation task to not be called at all. I.e. we
// would never complete the task returned to the caller of SendAsync().
}, tcs, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
catch (OperationCanceledException e)
{
HandleCanceledOperations(cancellationToken, tcs, e);
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return tcs.Task;
}
private static void HandleCanceledOperations(CancellationToken cancellationToken,
TaskCompletionSource<HttpResponseMessage> tcs, OperationCanceledException e)
{
// Check if the exception was due to a cancellation. If so, check if the OperationCanceledException is
// related to our CancellationToken. If it was indeed caused due to our cancellation token being
// canceled, set the Task as canceled. Set it to faulted otherwise, since the OperationCanceledException
// is not related to our cancellation token.
if (cancellationToken.IsCancellationRequested && (e.CancellationToken == cancellationToken))
{
tcs.TrySetCanceled(cancellationToken);
}
else
{
tcs.TrySetException(e);
}
}
// Private class used to capture the SendAsync state in
// a closure, while simultaneously avoiding a tuple allocation.
private sealed class SendState : TaskCompletionSource<HttpResponseMessage>
{
internal readonly MessageProcessingHandler _handler;
internal readonly CancellationToken _token;
public SendState(MessageProcessingHandler handler, CancellationToken token)
{
Debug.Assert(handler != null);
_handler = handler;
_token = token;
}
}
}
}