0
votes

I'm currently developing yet another custom middleware for websocket handling and got a weird problem, I need to sleep after the socket acceptation, either way i can't connect anymore after the first successful connection...

In fact i receive a Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content exception

Here is how it works ( i'll post snippet after )

  1. The middleware handles the connection if it detects it is a websocket one
  2. The middleware finds the custom handler/controller according to the URI path ( reflection on CustomHandlerClass derivated attributes, more or less like how works the mvc pattern )
  3. The middleware accepts the socket ( await httpContext.WebSockets.AcceptWebSocketAsync() powa! )
  4. The middleware creates an instance of the handler, and invoke the right method and pass as arguement the socket and httpContext ( usefull to get the ServiceProvider ;) ). The called method is an async task
  5. The derivated CustomHandlerClass listen for some data
  6. Upon data reception, performs its heavy task and send progress status back
  7. Go back in middleware which close the socket

The exception message The remote party closed the WebSocket connection without completing the close handshake.

Inner Exception

{Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content.
   at Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.PipeCompletion.ThrowFailed()
   at Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.Pipe.GetResult(ReadResult& result)
   at Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.Pipe.Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.IReadableBufferAwaiter.GetResult()
   at Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.ReadableBufferAwaitable.GetResult()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.MessageBody.<ReadAsync>d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameRequestStream.<ReadAsyncInternal>d__21.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.WebSockets.ManagedWebSocket.<EnsureBufferContainsAsync>d__70.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.WebSockets.ManagedWebSocket.<ReceiveAsyncPrivate>d__61.MoveNext()}

The stack Trace

at System.Net.WebSockets.ManagedWebSocket.<ReceiveAsyncPrivate>d__61.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Andra.MiddleWares.WebSocket.WebSocketHandler.<ReceiveStringAsync>d__5.MoveNext() in C:\\Users\\BillGates\\WindowsContinuum\\MiddleWares\\WebSocket\\WebSocketHandler.cs:line 79
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at EDIBorneo.Controllers.ValidatorController.<ValidateFileService>d__3.MoveNext() in C:\\Users\\BillGates\\WindowsContinuum\\Controllers\\ValidatorController.cs:line 54

I tried to wait for the

    public class WebSocketMiddleWare
    {
    private readonly RequestDelegate _nextMiddleWare;

    public WebSocketMiddleWare(RequestDelegate next) {
        this._nextMiddleWare = next;
    }

       public async Task Invoke(HttpContext httpContext) {

        if (httpContext.WebSockets.IsWebSocketRequest) {
            // Handle websocket request
            await this.ProcessWebSocketRequest(httpContext);
        } else { 
            await this._nextMiddleWare.Invoke(httpContext);
        }
    }

    private async Task ProcessWebSocketRequest(HttpContext httpContext) {
        string basePath;
        // Find handler derivated class which can handle the request
        Type handlerType = this.getHandlerType(httpContext.Request.Path, out basePath);

        if ( null != handlerType) {
            // Find the method in the found handler that will perform the service
            PathString servicePath = httpContext.Request.Path.ToString().Substring(basePath.Length);
            MethodInfo method = this.getHandlerMethod(handlerType, servicePath);

            if ( null != method) {
                // Handler type and method found, the socket can now be accepted
                System.Net.WebSockets.WebSocket socket = await httpContext.WebSockets.AcceptWebSocketAsync();
                //Thread.Sleep(1000);  <== Needed to handle more than one connection.... :'(
                // Creation of an handler instance
                WebSocketHandler handler = (WebSocketHandler)Activator.CreateInstance(handlerType, httpContext, socket);

                // Invoking the right method ( which is a async Task )
                method.Invoke(handler, null);

                // Job is done, we close the socket
                await handler.CloseSocket();
            }

        }

    }


    private Type getHandlerType(PathString path, out string attributePath) {
        attributePath = "";
        List<Type> handlerTypeList = Reflection.GetInstances<WebSocketHandler>();
        foreach (Type handlerType in handlerTypeList) {
            foreach (var classAttribute in handlerType.GetCustomAttributes(false)) {
                if (classAttribute.GetType() == typeof(WebSocketPathAttribute)) {
                    WebSocketPathAttribute attr = (WebSocketPathAttribute)classAttribute;
                    if (path.StartsWithSegments(attr.Path)) {
                        attributePath = attr.Path.ToString();
                        return handlerType;
                    }
                }
            }
        }
        return null;
    }

    private MethodInfo getHandlerMethod(Type handlerType, PathString path) {
        Type objType = handlerType;
        MethodInfo foundMethod=null;
        foreach (MethodInfo method in objType.GetMethods()) {
            WebSocketPathAttribute attr = method.GetCustomAttribute<WebSocketPathAttribute>(false);
            if (attr.Path.Equals(path)) {
                foundMethod = method;
                break;
            }
        }
        return foundMethod;
    }
    }
    }

The client side is pure javascript and there is no specific timeout configuration:

var ws = new WebSocket(myUri);
ws.onopen = function (evt) {
        $this._handler.onSocketOpen(evt);
    };
ws.onclose = function (evt) {
        $this._handler.onSocketClose(evt);
    };
ws.onmessage = function (evt) {
        $this._handler.onSocketMessage(evt);
    };
ws.onerror = function (evt) {
        $this._handler.onSocketError(evt);
    };

The uploaded file size is 60kB and test is done locally. I do not understand the exception because there is no differences in the call between a successful connection and a failed one.

The problem appear whenever i try another websocket call from the same page or after refreshing ( CTRL+F5 ) the page

If i should suspect anything, it would be the threads with the method invocation not being explicitly waited.

Further debugging didn't show me anything special

Found some SO posts about this exception, but it concerns timeout so i don't think it apply to my problem

The sleep fix works just fine, but is ugly and a bit unreliable ( i shouldn't need to do this...). So i seek some help on this matter =)

Won't be able to test any fix until monday, got a charity this weekend ;)

Regards

1
Can you share your code via git, its very interesting.Wasyster

1 Answers

0
votes

As i suspected, the problem was coming from the fact i was invoking a method retrieved by reflection in a non threaded way ( aka i was not waiting for it ).

This method was receiving and sending data using async method, and my call of the parent method would break middle with the thread context.

So i just needed to call it in a proper async manner.

Thanks to Scott in this post How to call a generic async method using reflection, i just had to use the extension he provides and replace my method.Invoke(handler, null) by await method.InvokeAsync(handler, null).ConfigureAwait(false);

Regards all