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 )
- The middleware handles the connection if it detects it is a websocket one
- 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 )
- The middleware accepts the socket ( await httpContext.WebSockets.AcceptWebSocketAsync() powa! )
- 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
- The derivated CustomHandlerClass listen for some data
- Upon data reception, performs its heavy task and send progress status back
- 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