dotnet / wcf

This repo contains the client-oriented WCF libraries that enable applications built on .NET Core to communicate with WCF services.
MIT License
1.7k stars 560 forks source link

NullReferenceException parsing response of One-Way operation #4821

Open bartschotten opened 2 years ago

bartschotten commented 2 years ago

I'm trying to migrate an existing SOAP/WCF client from .NET Framework to .NET 6. I haven't changed the actual client code (which used to work) at all. It's calling an endpoint that's not under my control so it's difficult to pinpoint exactly where it goes wrong, but here's what I'm observing. First the stack trace:

at async Task<ArraySegment<byte>> System.ServiceModel.Channels.MessageEncoder.BufferMessageStreamAsync(Stream stream, BufferManager bufferManager, int maxBufferSize, CancellationToken cancellationToken)
at async Task<Message> System.ServiceModel.Channels.MessageEncoder.ReadMessageAsync(Stream stream, BufferManager bufferManager, int maxBufferSize, string contentType, CancellationToken cancellationToken)
at async Task<Message> System.ServiceModel.Channels.HttpResponseMessageHelper.ReadChunkedBufferedMessageAsync(Task<Stream> inputStreamTask, TimeoutHelper timeoutHelper)
at async Task<Message> System.ServiceModel.Channels.HttpResponseMessageHelper.ParseIncomingResponse(TimeoutHelper timeoutHelper)
at async Task<Message> System.ServiceModel.Channels.HttpChannelFactory<TChannel>+HttpClientRequestChannel+HttpClientChannelAsyncRequest.ReceiveReplyAsync(TimeoutHelper timeoutHelper)
at async Task<Message> System.ServiceModel.Channels.RequestChannel.RequestAsync(Message message, TimeSpan timeout)
at async Task<Message> System.ServiceModel.Channels.RequestChannel.RequestAsyncInternal(Message message, TimeSpan timeout)
at Message System.ServiceModel.Channels.RequestChannel.Request(Message message, TimeSpan timeout)
at void System.ServiceModel.Dispatcher.RequestChannelBinder.Send(Message message, TimeSpan timeout)
at object System.ServiceModel.Channels.ServiceChannel.Call(string action, bool oneway, ProxyOperationRuntime operation, object[] ins, object[] outs, TimeSpan timeout)
at object System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(MethodCall methodCall, ProxyOperationRuntime operation)
at object System.ServiceModel.Channels.ServiceChannelProxy.Invoke(MethodInfo targetMethod, object[] args)

This is a One-Way operation so I'm already a bit surprised that it tries to read the response content at all, but this is what I'm getting from the response while debugging:

{StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  X-Backside-Transport: OK OK
  Connection: Keep-Alive
  Transfer-Encoding: chunked
  Date: Mon, 09 May 2022 06:00:28 GMT
  Server: Apache/2.4.6 (CentOS)
  X-ORACLE-DMS-ECID: 9a9cf930-e8af-4996-99cb-56ff8ddda4aa-00003fdd
  X-ORACLE-DMS-RID: 0
  X-Global-Transaction-ID: 8b42c99e6278adfc066011d1
  Content-Type: text/xml; charset=utf-8
}, Trailing Headers:
{
}}

So I see that HttpResponseMessageHelper.GetStreamAsync finds no Content-Length header (because of chunked encoding), then tries to read the first byte of the contentStream but finds it empty. It then returns a null contentStream.

This null stream is then passed onto MessageEncoder.ReadMessageAsync, which throws a NullReferenceException trying to read it.

I don't know if it's a bug or if the way that I have it set up is simply not supported, but does anyone know what is supposed to happen here? The way this path is handled doesn't seem very robust to me. I would have expected it to either ignore the response (because it's one-way), or to return some kind of empty response.

.NET 6 System.ServiceModel 4.9.0

bartschotten commented 2 years ago

I think I've found the problem. In HttpResponseMessageHelper.ParseIncomingResponse WCF only treats it as a NullMessage when there is no Content-Type. As we can see in the response above there is a Content-Type header in this case. Since this seems to be an Oracle system I even found this ancient issue that describes exactly this behavior: https://community.oracle.com/tech/developers/discussion/1670521/oneway-web-service-operation-has-text-html-response-content-type

I'll leave this here as a feature request to throw a more descriptive error than a NullReferenceException when there is a content-type, but no content. May be there should be a counterpart of the SR.HttpContentTypeHeaderRequired ProtocolException, which is thrown when there is content, but no content-type.