【问题标题】:How to throw a deserialized exception?如何抛出反序列化的异常?
【发布时间】:2021-03-19 11:17:06
【问题描述】:

我正在使用 JsonConvert.SerializeObject 在服务器上序列化 Exception,然后编码为 byte[] 并在客户端反序列化使用 JsonConvert.DeserializeObject。到目前为止一切正常......问题是当我抛出 Exception 被替换的堆栈跟踪时,让我演示一下:

public void HandleException(RpcException exp)
{
    // Get the exception byte[]
    string exceptionString = exp.Trailer.GetBytes("exception-bin");
    
    // Deserialize the exception
    Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new 
    JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
    
    // Log the Exception: The stacktrace is correct. Ex.: at ServerMethod()
    Console.WriteLine(exception);
    
    // Throw the same Exception: The stacktrace is changed. Ex.: at HandleException()
    ExceptionDispatchInfo.Capture(exception).Throw();
}

【问题讨论】:

  • 很遗憾,不,我使用的解决方案与建议的链接相同。
  • 从网络服务器传回一个完整的异常可能是个坏主意,因为它暴露了客户端可能不知道的细节
  • server 异常的堆栈跟踪在客户端上毫无意义。如果您需要记录异常,请在服务器上执行此操作。此外,此时您甚至没有异常 - 阻止正确执行程序的错误。您有一个从远程调用返回的数据对象,您的程序已准备好处理该对象。将该数据对象视为数据,而不是例外
  • 嗯,如果您反序列化 Exception 并设置 JsonSerializerSettings.Context = new StreamingContext(StreamingContextStates.CrossAppDomain),那么当抛出异常时,反序列化的异常回溯将附加到当前回溯。见dotnetfiddle.net/QUYHls。够了吗?

标签: c# exception json.net grpc


【解决方案1】:

如果您反序列化 Exception 并设置 JsonSerializerSettings.Context = new StreamingContext(StreamingContextStates.CrossAppDomain),那么即使在引发异常之后,反序列化的堆栈跟踪字符串也会被添加到显示的 StackTrace 之前:

var settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    Context = new StreamingContext(StreamingContextStates.CrossAppDomain),
};
var exception = JsonConvert.DeserializeObject<Exception>(exceptionString, settings);

注意事项:

  • 之所以有效,是因为在streaming constructor for Exception 中,反序列化的堆栈跟踪字符串被保存到_remoteStackTraceString 中,该_remoteStackTraceString 稍后会添加到常规堆栈跟踪之前:

    if (context.State == StreamingContextStates.CrossAppDomain)
    {
        // ...this new exception may get thrown.  It is logically a re-throw, but 
        //  physically a brand-new exception.  Since the stack trace is cleared 
        //  on a new exception, the "_remoteStackTraceString" is provided to 
        //  effectively import a stack trace from a "remote" exception.  So,
        //  move the _stackTraceString into the _remoteStackTraceString.  Note
        //  that if there is an existing _remoteStackTraceString, it will be 
        //  preserved at the head of the new string, so everything works as 
        //  expected.
        // Even if this exception is NOT thrown, things will still work as expected
        //  because the StackTrace property returns the concatenation of the
        //  _remoteStackTraceString and the _stackTraceString.
        _remoteStackTraceString = _remoteStackTraceString + _stackTraceString;
        _stackTraceString = null;
    }
    
  • 虽然Exception 的序列化流确实包含堆栈跟踪字符串,但它不会尝试捕获运行时使用的private Object _stackTrace,以识别在执行程序集中引发异常的位置。这似乎是ExceptionDispatchInfo 在抛出异常时无法复制和使用此信息的原因。因此,似乎不可能抛出反序列化的异常并从序列化流中恢复其“真实”堆栈跟踪。

  • 为了让 Json.NET 使用其流式构造函数反序列化一个类型(并因此根据需要设置远程跟踪字符串),该类型必须用[Serializable] 标记并实现ISerializableSystem.Exception 满足这两个要求,但Exception 的某些派生类并不总是添加[Serializable] 属性。如果您的特定序列化异常缺少该属性,请参阅 Deserializing custom exceptions in Newtonsoft.Json

  • 使用TypeNameHandling.All 反序列化异常是不安全的,并且在从不受信任的来源反序列化时可能导致注入攻击类型。请参阅:External json vulnerable because of Json.Net TypeNameHandling auto?,其答案专门讨论了异常的反序列化。

演示小提琴here.

【讨论】:

    【解决方案2】:

    我想指出一个小例子:我从两个应用程序中调用这种序列化/反序列化,一个是 Blazor (.net 5),另一个是 WinForms (.net framework 4.7)。在 blazor 中,接受答案的方法不起作用。在这种情况下,我所做的是通过反射设置 RemoteStackTrace。

    // Convert string para exception
    Exception exception = JsonConvert.DeserializeObject<Exception>(exceptionString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
    
    // Set RemoteStackTrace
    exception.GetType().GetField("_remoteStackTraceString", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(exception, exception.StackTrace);
    
    // Throw the Exception with original stacktrace
    ExceptionDispatchInfo.Capture(exception).Throw();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-27
      • 1970-01-01
      相关资源
      最近更新 更多