/// <summary> /// Writes the error with fallback logic for XML cases where the writer is in an error state and a new writer must be created. /// </summary> /// <param name="messageWriter">The message writer.</param> /// <param name="encoding">The encoding to use for the error if we have to fallback.</param> /// <param name="responseStream">The response stream to write to in the fallback case.</param> /// <param name="args">The args for the error.</param> /// <param name="error">The error to write.</param> /// <param name="messageWriterBuilder">MessageWriterBuilder to use if a new ODataMessageWriter needs to be constructed.</param> private static void WriteErrorWithFallbackForXml(ODataMessageWriter messageWriter, Encoding encoding, Stream responseStream, HandleExceptionArgs args, ODataError error, MessageWriterBuilder messageWriterBuilder) { Debug.Assert(args != null, "args != null"); #if DEBUG Debug.Assert(args.ProcessExceptionWasCalled, "ProcessException was not called by the time we tried to serialze this error message with ODataLib."); #endif if (messageWriter != null) { try { // If the XmlWriter inside the ODataMessageWriter had entered Error state, ODataMessageWriter.WriteError would throw an InvalidOperationException // when we try to write to it. Note that XmlWriter doesn't always throw an XmlException when it enters Error state. // The right thing to do is we don't write any more because at this point we don't know what's been written to the underlying // stream. However we still should flush the writer to make sure that all the content that was written but is sitting in the buffers actually appears // in the stream before writing the instream error. Otherwise the buffer will be flushed when disposing the writer later and we would end up with // either content written after the instream error (this would also result in having the Xml declaration in the middle of the payload - // [Astoria-ODataLib-Integration] In-stream errors due to XmlExceptions are written out backwards (error before partial valid payload)) or, // hypothetically, the instream error in the middle of the other content that was already partially written. For example we can end up with a payload that // looks like <element attr="val<m:error... The XmlReader would not be able to parse the error payload in this case. Disposing the writer will flush the buffer. // It is fine to do it since the writer is not usable at this point anyways. Also note that the writer will be disposed more than once (e.g. in finally block // in ResponseBodySerializer) but only the first call will have any effect. // However since in the versions we shipped we always create a new XmlWriter to serialize the error payload when the existing // one is in error state, we will continue to do the same to avoid introducing any breaking change here. messageWriter.WriteError(error, args.UseVerboseErrors); } catch (ODataException e) { // Yikes, ODataLib threw while writing the error. This tends to happen if the service author did something invalid during custom // error handling, such as add an custom instance annotation to the error payload. In this dire case, we treat it almost like // an in-stream error, and abort the previous writing. We write out the new error. Note that this will produce an invalid payload like // the situation noted above with XmlWriter errors. WebUtil.Dispose(messageWriter); messageWriterBuilder.SetMessageForErrorInError(); var newErrorWriter = messageWriterBuilder.CreateWriter(); ODataError errorWhileWritingOtherError = new ODataError() { ErrorCode = "500", InnerError = new ODataInnerError(e), Message = Strings.ErrorHandler_ErrorWhileWritingError }; newErrorWriter.WriteError(errorWhileWritingOtherError, args.UseVerboseErrors); } catch (InvalidOperationException) { Debug.Assert(ContentTypeUtil.IsNotJson(args.ResponseContentType), "Should never get here for JSON responses"); WebUtil.Dispose(messageWriter); // if either an InvalidOperationException was encountered (see comment above) or the message writer was null, write the error out manually. Debug.Assert(responseStream != null, "responseStream != null"); using (XmlWriter xmlWriter = XmlWriter.Create(responseStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(xmlWriter, error, args.UseVerboseErrors, MaxInnerErrorDepth); } } } }
/// <summary>Handles an exception when processing a batch response.</summary> /// <param name='service'>Data service doing the processing.</param> /// <param name="requestMessage">requestMessage holding information about the request that caused an error</param> /// <param name="responseMessage">responseMessage to which we need to write the exception message</param> /// <param name='exception'>Exception thrown.</param> /// <param name='batchWriter'>Output writer for the batch.</param> /// <param name="responseStream">Underlying response stream.</param> /// <param name="defaultResponseVersion">The data service version to use for response, if it cannot be computed from the requestMessage.</param> internal static void HandleBatchOperationError(IDataService service, AstoriaRequestMessage requestMessage, IODataResponseMessage responseMessage, Exception exception, ODataBatchWriter batchWriter, Stream responseStream, Version defaultResponseVersion) { Debug.Assert(service != null, "service != null"); Debug.Assert(exception != null, "exception != null"); Debug.Assert(batchWriter != null, "batchWriter != null"); Debug.Assert(service.Configuration != null, "service.Configuration != null"); Debug.Assert(CommonUtil.IsCatchableExceptionType(exception), "CommonUtil.IsCatchableExceptionType(exception)"); ErrorHandler handler = CreateHandler(service, requestMessage, exception, defaultResponseVersion); service.InternalHandleException(handler.exceptionArgs); if (requestMessage != null && responseMessage != null) { responseMessage.SetHeader(XmlConstants.HttpODataVersion, handler.responseVersion.ToString(2) + ";"); requestMessage.ProcessException(handler.exceptionArgs); // if ProcessBenignException returns anything, we can safely not write to the stream. if (ProcessBenignException(exception, service) != null) { return; } } if (requestMessage != null) { responseMessage = requestMessage.BatchServiceHost.GetOperationResponseMessage(); WebUtil.SetResponseHeadersForBatchRequests(responseMessage, requestMessage.BatchServiceHost); } else { responseMessage = batchWriter.CreateOperationResponseMessage(null); responseMessage.StatusCode = handler.exceptionArgs.ResponseStatusCode; } MessageWriterBuilder messageWriterBuilder = MessageWriterBuilder.ForError( null, service, handler.responseVersion, responseMessage, handler.contentType, null /*acceptCharsetHeaderValue*/); using (ODataMessageWriter messageWriter = messageWriterBuilder.CreateWriter()) { ODataError error = handler.exceptionArgs.CreateODataError(); WriteErrorWithFallbackForXml(messageWriter, handler.encoding, responseStream, handler.exceptionArgs, error, messageWriterBuilder); } }
/// <summary>Handles an exception before the response has been written out.</summary> /// <param name='exception'>Exception thrown.</param> /// <param name='service'>Data service doing the processing.</param> /// <returns>An action that can serialize the exception into a stream.</returns> internal static Action <Stream> HandleBeforeWritingException(Exception exception, IDataService service) { Debug.Assert(CommonUtil.IsCatchableExceptionType(exception), "CommonUtil.IsCatchableExceptionType(exception)"); Debug.Assert(exception != null, "exception != null"); Debug.Assert(service != null, "service != null"); AstoriaRequestMessage requestMessage = service.OperationContext.RequestMessage; Debug.Assert(requestMessage != null, "requestMessage != null"); ErrorHandler handler = CreateHandler(service, requestMessage, exception, VersionUtil.DataServiceDefaultResponseVersion); service.InternalHandleException(handler.exceptionArgs); service.OperationContext.ResponseMessage.SetHeader(XmlConstants.HttpODataVersion, handler.responseVersion.ToString(2) + ";"); requestMessage.ProcessException(handler.exceptionArgs); Action <Stream> action = ProcessBenignException(exception, service); if (action != null) { return(action); } MessageWriterBuilder messageWriterBuilder = MessageWriterBuilder.ForError( service.OperationContext.RequestMessage.AbsoluteServiceUri, service, handler.responseVersion, service.OperationContext.ResponseMessage, handler.contentType, service.OperationContext.RequestMessage.GetRequestAcceptCharsetHeader()); ODataMessageWriter messageWriter = messageWriterBuilder.CreateWriter(); ODataUtils.SetHeadersForPayload(messageWriter, ODataPayloadKind.Error); return(stream => { service.OperationContext.ResponseMessage.SetStream(stream); ODataError error = handler.exceptionArgs.CreateODataError(); WriteErrorWithFallbackForXml(messageWriter, handler.encoding, stream, handler.exceptionArgs, error, messageWriterBuilder); }); }
/// <summary> /// Writes the error with fallback logic for XML cases where the writer is in an error state and a new writer must be created. /// </summary> /// <param name="messageWriter">The message writer.</param> /// <param name="encoding">The encoding to use for the error if we have to fallback.</param> /// <param name="responseStream">The response stream to write to in the fallback case.</param> /// <param name="args">The args for the error.</param> /// <param name="error">The error to write.</param> /// <param name="messageWriterBuilder">MessageWriterBuilder to use if a new ODataMessageWriter needs to be constructed.</param> private static void WriteErrorWithFallbackForXml(ODataMessageWriter messageWriter, Encoding encoding, Stream responseStream, HandleExceptionArgs args, ODataError error, MessageWriterBuilder messageWriterBuilder) { Debug.Assert(args != null, "args != null"); #if DEBUG Debug.Assert(args.ProcessExceptionWasCalled, "ProcessException was not called by the time we tried to serialze this error message with ODataLib."); #endif if (messageWriter != null) { try { // If the XmlWriter inside the ODataMessageWriter had entered Error state, ODataMessageWriter.WriteError would throw an InvalidOperationException // when we try to write to it. Note that XmlWriter doesn't always throw an XmlException when it enters Error state. // The right thing to do is we don't write any more because at this point we don't know what's been written to the underlying // stream. However we still should flush the writer to make sure that all the content that was written but is sitting in the buffers actually appears // in the stream before writing the instream error. Otherwise the buffer will be flushed when disposing the writer later and we would end up with // either content written after the instream error (this would also result in having the Xml declaration in the middle of the payload - // [Astoria-ODataLib-Integration] In-stream errors due to XmlExceptions are written out backwards (error before partial valid payload)) or, // hypothetically, the instream error in the middle of the other content that was already partially written. For example we can end up with a payload that // looks like <element attr="val<m:error... The XmlReader would not be able to parse the error payload in this case. Disposing the writer will flush the buffer. // It is fine to do it since the writer is not usable at this point anyways. Also note that the writer will be disposed more than once (e.g. in finally block // in ResponseBodySerializer) but only the first call will have any effect. // However since in the versions we shipped we always create a new XmlWriter to serialize the error payload when the existing // one is in error state, we will continue to do the same to avoid introducing any breaking change here. messageWriter.WriteError(error, args.UseVerboseErrors); } catch (ODataException e) { // Yikes, ODataLib threw while writing the error. This tends to happen if the service author did something invalid during custom // error handling, such as add an custom instance annotation to the error payload. In this dire case, we treat it almost like // an in-stream error, and abort the previous writing. We write out the new error. Note that this will produce an invalid payload like // the situation noted above with XmlWriter errors. WebUtil.Dispose(messageWriter); messageWriterBuilder.SetMessageForErrorInError(); var newErrorWriter = messageWriterBuilder.CreateWriter(); ODataError errorWhileWritingOtherError = new ODataError() { ErrorCode = "500", InnerError = new ODataInnerError(e), Message = Strings.ErrorHandler_ErrorWhileWritingError }; newErrorWriter.WriteError(errorWhileWritingOtherError, args.UseVerboseErrors); } catch (InvalidOperationException) { Debug.Assert(ContentTypeUtil.IsNotJson(args.ResponseContentType), "Should never get here for JSON responses"); WebUtil.Dispose(messageWriter); // if either an InvalidOperationException was encountered (see comment above) or the message writer was null, write the error out manually. Debug.Assert(responseStream != null, "responseStream != null"); using (XmlWriter xmlWriter = XmlWriter.Create(responseStream, XmlUtil.CreateXmlWriterSettings(encoding))) { ErrorUtils.WriteXmlError(xmlWriter, error, args.UseVerboseErrors, MaxInnerErrorDepth); } } } }