我很难理解ExecutionContext背后的机制。
根据我在网上读到的内容,上下文敏感的项目,如安全性(线程主体)、区域性等,应该在执行工作单元的范围内跨异步线程流动。
不过,我遇到了非常令人困惑和潜在危险的bug。我注意到我的线程的CurrentPrincipal在异步执行中丢失了。
下面是ASP.NET Web API场景的示例:
首先,让我们设置一个简单的Web API配置,其中包含两个用于测试的委托处理程序。
除了第一个“DummyHandler”设置线程的主体以及要在上下文中共享的一段数据(请求的关联ID)之外,他们所做的只是写出调试信息并将请求/响应传递出去。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new DummyHandler());
config.MessageHandlers.Add(new AnotherDummyHandler());
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
public class DummyHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
CallContext.LogicalSetData("rcid", request.GetCorrelationId());
Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));
Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
Debug.WriteLine("Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine("RCID: {0}", CallContext.LogicalGetData("rcid"));
return task.Result;
});
}
}
public class AnotherDummyHandler : MessageProcessingHandler
{
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine(" Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine(" RCID: {0}", CallContext.LogicalGetData("rcid"));
return request;
}
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
Debug.WriteLine(" Another Dummy Handler Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine(" RCID: {0}", CallContext.LogicalGetData("rcid"));
return response;
}
}
很简单。接下来,让我们添加一个ApiController来处理HTTPPOST,就好像您在上传文件一样。
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFile()
{
Debug.WriteLine(" Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine(" RCID: {0}", CallContext.LogicalGetData("rcid"));
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
try
{
await Request.Content.ReadAsMultipartAsync(
new MultipartFormDataStreamProvider(
HttpRuntime.AppDomainAppPath + @"upload\temp"));
Debug.WriteLine(" Thread: {0}", Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine(" User: {0}", (Object)Thread.CurrentPrincipal.Identity.Name);
Debug.WriteLine(" RCID: {0}", CallContext.LogicalGetData("rcid"));
return new HttpResponseMessage(HttpStatusCode.Created);
}
catch (Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
在使用Fiddler运行测试时,这是我收到的输出:
Dummy Handler Thread: 63
User: dgdev
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
Another Dummy Handler Thread: 63
User: dgdev
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
Thread: 63
User: dgdev
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
Thread: 77
User: <<< PRINCIPAL IS LOST AFTER ASYNC
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
Another Dummy Handler Thread: 63
User: <<< PRINCIPAL IS STILL LOST
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
Dummy Handler Thread: 65
User: dgdev <<< PRINCIPAL IS BACK?!?
RCID: 6d542847-4ceb-4511-85e5-d1b5bf3be476
更令人困惑的是,当我将以下内容附加到异步行时:
await Request.Content.ReadAsMultipartAsync(
new MultipartFormDataStreamProvider(..same as before..))
.ConfigureAwait(false); <<<<<<
我现在收到这个输出:
Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
Another Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
Thread: 65
User: dgdev <<< PRINCIPAL IS HERE!
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
Another Dummy Handler Thread: 65
User: <<< PRINCIPAL IS LOST
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
Dummy Handler Thread: 40
User: dgdev
RCID: 8d944500-cb52-4362-8537-dab405fa12a2
这里的重点是这个。async my后面的代码实际上调用了我的业务逻辑,或者只是要求正确设置安全上下文。存在潜在的完整性问题。
有人能帮助我们了解一下正在发生的事情吗?
提前谢谢。