/// <summary> /// A callback that gets invoked after we disconnect from the IPEndpoint. /// </summary> /// <param name="result"></param> private void DisconnectCallback(IAsyncResult result) { Promise <ISockNetChannel> promise = (Promise <ISockNetChannel>)result.AsyncState; try { Socket.EndDisconnect(result); if (TryFlaggingAs(ClientSockNetChannelState.Disconnected, ClientSockNetChannelState.Disconnecting)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Disconnected from [{0}]", RemoteEndpoint); stream.Close(); Pipe.HandleClosed(); promise.CreateFulfiller().Fulfill(this); } else { promise.CreateFulfiller().Fulfill(new Exception("Unable to mark channel as disconnected. State was: " + State)); } } catch (Exception e) { promise.CreateFulfiller().Fulfill(e); } }
/// <summary> /// Flushes this stream and clears any read pooled memory chunks. /// </summary> public void Flush() { lock (_syncRoot) { ValidateBuffer(); MemoryChunkNode currentChunk = rootChunk; while (currentChunk != null) { if (currentChunk.count > ReadPosition) { break; } if (currentChunk.pooledObject != null && currentChunk.pooledObject.RefCount.Decrement() < 1) { if (currentChunk.pooledObject.State == PooledObjectState.USED) { currentChunk.pooledObject.Return(); } else { SockNetLogger.Log(SockNetLogger.LogLevel.WARN, this, "Potential resource leak found."); } } rootChunk = currentChunk.next; ReadPosition -= currentChunk.count; WritePosition = Math.Max(0, WritePosition - currentChunk.count); currentChunk = currentChunk.next; } } }
/// <summary> /// A callback that gets invoked after we bind. /// </summary> /// <param name="result"></param> private void AcceptCallback(IAsyncResult result) { if (!IsActive) { return; } Socket remoteSocket = null; try { remoteSocket = Socket.EndAccept(result); SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Accepted connection from: [{0}]", remoteSocket.RemoteEndPoint); } finally { if (ServerSockNetChannelState.Bound.Equals(State)) { Socket.BeginAccept(new AsyncCallback(AcceptCallback), Socket); } else { remoteSocket.Close(); remoteSocket = null; } } if (remoteSocket != null) { RemoteSockNetChannel channel = new RemoteSockNetChannel(this, remoteSocket, BufferPool, modules.Values); remoteChannels[channel.Id] = channel; } }
/// <summary> /// Handles an incoming raw HTTP response. /// </summary> /// <param name="channel"></param> /// <param name="obj"></param> private void HandleIncomingResponse(ISockNetChannel channel, ref object obj) { if (!(obj is ChunkedBuffer)) { return; } ChunkedBuffer data = (ChunkedBuffer)obj; if (currentIncoming == null) { currentIncoming = new HttpResponse(channel.BufferPool); } if (currentIncoming.Parse(data.Stream, channel.IsActive)) { if (SockNetLogger.DebugEnabled) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "Received HTTP Response: Command Line: [{0}], Body Size [{1}]", currentIncoming.CommandLine, currentIncoming.BodySize); } obj = currentIncoming; currentIncoming = null; } }
/// <summary> /// Binds to the configured endpoint and sets security variables. /// </summary> /// <param name="isSsl"></param> /// <param name="certificateValidationCallback"></param> /// <returns></returns> private Promise <ISockNetChannel> BindInternal(bool isSsl, X509Certificate serverCertificate, RemoteCertificateValidationCallback certificateValidationCallback) { Promise <ISockNetChannel> promise = new Promise <ISockNetChannel>(); if (TryFlaggingAs(ServerSockNetChannelState.Binding, ServerSockNetChannelState.Closed)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Binding to [{0}]...", bindEndpoint); this.IsSsl = isSsl; this.CertificateValidationCallback = certificateValidationCallback; this.ServerCertificate = serverCertificate; Socket.Bind(bindEndpoint); Socket.Listen(backlog); this.State = ServerSockNetChannelState.Bound; SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Bound to [{0}].", LocalEndpoint); Socket.BeginAccept(new AsyncCallback(AcceptCallback), Socket); promise.CreateFulfiller().Fulfill(this); } else { throw new Exception("The server is already bound."); } return(promise); }
/// <summary> /// A callback that gets invoked after we connect. /// </summary> /// <param name="result"></param> private void ConnectCallback(IAsyncResult result) { if (TryFlaggingAs(ClientSockNetChannelState.Connected, ClientSockNetChannelState.Connecting)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Connected to [{0}].", connectEndpoint); Socket.EndConnect(result); // create inital fake promise fulfilment delegate Promise <ISockNetChannel> .OnFulfilledDelegate onFulfilled = new Promise <ISockNetChannel> .OnFulfilledDelegate( (ISockNetChannel value, Exception e, Promise <ISockNetChannel> promise) => { }); onFulfilled = (ISockNetChannel value, Exception e, Promise <ISockNetChannel> promise) => { promise.OnFulfilled = null; Pipe.HandleOpened(); ((Promise <ISockNetChannel>)result.AsyncState).CreateFulfiller().Fulfill(this); }; if (isSsl) { AttachAsSslClient(certificateValidationCallback).OnFulfilled = onFulfilled; } else { Attach().OnFulfilled = onFulfilled; } } else { throw new Exception("The client isn't connecting."); } }
/// <summary> /// Handles incoming data. /// </summary> public void HandleIncomingData(ref object message) { lock (incomingHandlers) { foreach (IDelegateReference delegateRef in incomingHandlers) { try { if (delegateRef != null && delegateRef.DelegateType.IsAssignableFrom(message.GetType())) { object[] args = new object[2] { parent, message }; delegateRef.Delegate.DynamicInvoke(args); message = args[1]; } } catch (Exception e) { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, parent, "Pipe incoming invokation failed on: " + delegateRef.Delegate.Target + "." + delegateRef.Delegate.Method, e); } } } }
public void DebugLog() { lock (openedHandlers) { foreach (OnOpenedDelegate del in openedHandlers) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "OnOpen: " + del.Target + "." + del.Method); } } lock (closedHandlers) { foreach (OnClosedDelegate del in closedHandlers) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "OnClosed: " + del.Target + "." + del.Method); } } lock (incomingHandlers) { foreach (IDelegateReference del in incomingHandlers) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "OnIncoming: " + del.Delegate.Target + "." + del.Delegate.Method); } } lock (outgoingHandlers) { foreach (IDelegateReference del in outgoingHandlers) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "OnOutgoing: " + del.Delegate.Target + "." + del.Delegate.Method); } } }
/// <summary> /// Handles incoming raw frames and translates them into WebSocketFrame(s) /// </summary> /// <param name="channel"></param> /// <param name="request"></param> private void HandleIncomingFrames(ISockNetChannel channel, ref object data) { if (!(data is ChunkedBuffer)) { return; } ChunkedBuffer stream = (ChunkedBuffer)data; long startingPosition = stream.ReadPosition; try { WebSocketFrame frame = WebSocketFrame.ParseFrame(stream.Stream); if (combineContinuations) { if (frame.IsFinished) { UpdateContinuation(ref continuationFrame, frame); data = continuationFrame; continuationFrame = null; } else { UpdateContinuation(ref continuationFrame, frame); } } else { data = frame; } if (SockNetLogger.DebugEnabled) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "Received WebSocket message. Size: {0}, Type: {1}, IsFinished: {2}", frame.Data.Length, Enum.GetName(typeof(WebSocketFrame.WebSocketFrameOperation), frame.Operation), frame.IsFinished); } } catch (EndOfStreamException) { // websocket frame isn't done stream.ReadPosition = startingPosition; } catch (ArgumentOutOfRangeException) { // websocket frame isn't done stream.ReadPosition = startingPosition; } catch (Exception e) { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, this, "Unable to parse web-socket request", e); channel.Close(); } }
/// <summary> /// Handles an incomming raw Gds message. /// </summary> /// <param name="channel"></param> /// <param name="obj"></param> public void HandleIncoming(ISockNetChannel channel, ref object obj) { if (!(obj is ChunkedBuffer)) { return; } ChunkedBuffer stream = (ChunkedBuffer)obj; long startingPosition = stream.ReadPosition; try { GdsFrame frame = GdsFrame.ParseFrame(stream.Stream, channel.BufferPool); if (combineChunks) { if (frame.IsComplete) { UpdateChunk(ref chunkedFrame, frame, channel); obj = chunkedFrame; chunkedFrame = null; } else { UpdateChunk(ref chunkedFrame, frame, channel); } } else { obj = frame; } if (SockNetLogger.DebugEnabled) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "Received Gds message. Body Size: {0}, Type: {1}, IsComplete: {2}", frame.Body.AvailableBytesToRead, Enum.GetName(typeof(GdsFrame.GdsFrameType), frame.Type), frame.IsComplete); } } catch (EndOfStreamException) { // frame isn't done stream.ReadPosition = startingPosition; } catch (ArgumentOutOfRangeException) { // frame isn't done stream.ReadPosition = startingPosition; } catch (Exception e) { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, this, "Unable to parse Gds message", e); } }
/// <summary> /// Handles the WebSocket handshake. /// </summary> /// <param name="channel"></param> /// <param name="request"></param> private void HandleHandshake(ISockNetChannel channel, ref HttpRequest request) { string connection = request.Header["Connection"]; string upgrade = request.Header["Upgrade"]; string securityKey = request.Header[WebSocketUtil.WebSocketKeyHeader]; if (connection != null && upgrade != null && securityKey != null && "websocket".Equals(upgrade.Trim().ToLower()) && "upgrade".Equals(connection.Trim().ToLower())) { string[] requestProtocols = request.Headers[WebSocketUtil.WebSocketProtocolHeader]; List <string> handledProtocols = new List <string>(); if (requestProtocols != null && protocolDelegate != null) { for (int i = 0; i < requestProtocols.Length; i++) { if (protocolDelegate(channel, requestProtocols[i])) { handledProtocols.Add(requestProtocols[i]); } } } HttpResponse response = new HttpResponse(channel.BufferPool) { Version = "HTTP/1.1", Code = "101", Reason = "Switching Protocols" }; response.Header["Upgrade"] = "websocket"; response.Header["Connection"] = "Upgrade"; response.Header[WebSocketUtil.WebSocketAcceptHeader] = WebSocketUtil.GenerateAccept(securityKey); response.Header[WebSocketUtil.WebSocketProtocolHeader] = string.Join(",", handledProtocols.ToArray()); channel.Send(response); channel.RemoveModule(httpModule); channel.Pipe.AddIncomingFirst <object>(HandleIncomingFrames); channel.Pipe.AddOutgoingLast <object>(HandleOutgoingFrames); } else { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, this, "Expecting upgrade request."); channel.Close(); } }
/// <summary> /// Disconnects from the IPEndpoint. /// </summary> public Promise <ISockNetChannel> Disconnect() { Promise <ISockNetChannel> promise = new Promise <ISockNetChannel>(); if (TryFlaggingAs(ClientSockNetChannelState.Disconnecting, ClientSockNetChannelState.Connected)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Disconnecting from [{0}]...", RemoteEndpoint); Socket.Shutdown(SocketShutdown.Both); Socket.BeginDisconnect(true, new AsyncCallback(DisconnectCallback), promise); } else { promise.CreateFulfiller().Fulfill(this); } return(promise); }
/// <summary> /// Closes this server. /// </summary> /// <returns></returns> public override Promise <ISockNetChannel> Close() { if (TryFlaggingAs(ServerSockNetChannelState.Closing, ServerSockNetChannelState.Bound)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Unbinding from [{0}]...", LocalEndpoint); foreach (RemoteSockNetChannel remoteChannel in remoteChannels.Values) { remoteChannel.Close().WaitForValue(TimeSpan.FromSeconds(5)); } Socket.Close(); this.State = ServerSockNetChannelState.Closed; SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Not bound to [{0}].", bindEndpoint); } return(new Promise <ISockNetChannel>(this)); }
/// <summary> /// Connects to the configured endpoint and sets security variables. /// </summary> /// <param name="isSsl"></param> /// <param name="certificateValidationCallback"></param> /// <returns></returns> private Promise <ISockNetChannel> Connect(bool isSsl, RemoteCertificateValidationCallback certificateValidationCallback) { Promise <ISockNetChannel> promise = new Promise <ISockNetChannel>(); if (TryFlaggingAs(ClientSockNetChannelState.Connecting, ClientSockNetChannelState.Disconnected)) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Connecting to [{0}]...", connectEndpoint); this.isSsl = isSsl; this.certificateValidationCallback = certificateValidationCallback; Socket.BeginConnect((EndPoint)connectEndpoint, new AsyncCallback(ConnectCallback), promise); } else { throw new Exception("The client is already connected."); } return(promise); }
/// <summary> /// Handles opened channel. /// </summary> public void HandleClosed() { lock (closedHandlers) { foreach (OnClosedDelegate delegateRef in closedHandlers) { try { if (delegateRef != null) { delegateRef(parent); } } catch (Exception e) { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, parent, "Pipe closed invokation failed on: " + delegateRef.Target + "." + delegateRef.Method, e); } } } }
/// <summary> /// Invoked on channel connect. /// </summary> /// <param name="channel"></param> public void OnConnected(ISockNetChannel channel) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Sending WebSocket upgrade request."); channel.Pipe.AddIncomingLast <HttpResponse>(HandleHandshake); HttpRequest request = new HttpRequest(channel.BufferPool) { Action = "GET", Path = path, Version = "HTTP/1.1" }; request.Header["Host"] = hostname; request.Header["Upgrade"] = "websocket"; request.Header["Connection"] = "Upgrade"; request.Header[WebSocketUtil.WebSocketKeyHeader] = secKey; request.Header[WebSocketUtil.WebSocketVersionHeader] = "13"; channel.Send(request); }
/// <summary> /// Handles an outgoing HttpRequest and converts it to a raw buffer. /// </summary> /// <param name="channel"></param> /// <param name="obj"></param> private void HandleOutgoingRequest(ISockNetChannel channel, ref object obj) { if (!(obj is HttpRequest)) { return; } HttpRequest data = (HttpRequest)obj; if (SockNetLogger.DebugEnabled) { SockNetLogger.Log(SockNetLogger.LogLevel.DEBUG, this, "Sending HTTP Request: Command Line: [{0}], Body Size [{1}]", data.CommandLine, data.BodySize); } ChunkedBuffer buffer = new ChunkedBuffer(channel.BufferPool); data.Write(buffer.Stream); data.Dispose(); obj = buffer; }
/// <summary> /// Handles the WebSocket handshake. /// </summary> /// <param name="channel"></param> /// <param name="request"></param> private void HandleHandshake(ISockNetChannel channel, ref HttpResponse data) { if (expectedAccept.Equals(data.Header[WebSocketUtil.WebSocketAcceptHeader])) { SockNetLogger.Log(SockNetLogger.LogLevel.INFO, this, "Established Web-Socket connection."); channel.Pipe.RemoveIncoming <HttpResponse>(HandleHandshake); channel.Pipe.AddIncomingFirst <object>(HandleIncomingFrames); channel.Pipe.AddOutgoingLast <object>(HandleOutgoingFrames); channel.RemoveModule(httpModule); if (onWebSocketEstablished != null) { onWebSocketEstablished(channel); } } else { SockNetLogger.Log(SockNetLogger.LogLevel.ERROR, this, "Web-Socket handshake incomplete."); channel.Close(); } }