Skip to content

Instantly share code, notes, and snippets.

@19317362
Forked from prabirshrestha/socket.io.cs
Created June 3, 2016 23:42
Show Gist options
  • Save 19317362/9aa18fbbc71b6323ee0ef8e572c03e8a to your computer and use it in GitHub Desktop.
Save 19317362/9aa18fbbc71b6323ee0ef8e572c03e8a to your computer and use it in GitHub Desktop.
socket.io 4 .net
namespace WebSocketHelloEchoServer.SocketIo
{
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;
public abstract class SocketIoHttpHandler : HttpTaskAsyncHandler
{
public override Task ProcessRequestAsync(HttpContext context)
{
return HandleRequest(new HttpContextWrapper(context));
}
public virtual bool IsReusable
{
get { return false; }
}
public int Protocol { get { return 1; } }
public virtual async Task HandleRequest(HttpContextBase context)
{
var response = context.Response;
var data = CheckRequest(context.Request);
if (data == null)
return;
if (data.IsStatic || string.IsNullOrEmpty(data.Transport) && data.Protocol == 0)
{
bool browserClient = true;
if (data.IsStatic && browserClient)
{
if (data.Path == "/socket.io.js")
await SendSocketIoJs(context);
else if (data.Path == "/socket.io.min.js")
await SendSocketIoMinJs(context);
return;
}
else
{
response.StatusCode = 200;
response.Write("Welcome to socket.io.");
// log unhandled socket.io url
return;
}
}
if (data.Protocol != Protocol)
{
response.StatusCode = 500;
response.Write("Protocol version not supported.");
// this.log.info('client protocol version unsupported');
return;
}
else
{
if (!string.IsNullOrEmpty(data.Id))
{
await HandleHttpRequest(data, context);
}
else
{
await HandleHandshake(data, context);
}
}
}
protected virtual async Task HandleUpgrade(HttpContextBase context, AspNetWebSocketContext socketContext)
{
var data = CheckRequest(context.Request);
if (data == null)
{
throw new NotImplementedException();
}
else
{
await HandleClient(data, context);
}
}
protected virtual async Task HandleUpgrade(HttpContextBase context)
{
}
private const string Resource = "/socket.io";
protected virtual SocketIoData CheckRequest(HttpRequestBase request)
{
var data = new SocketIoData { Request = request };
var path = request.PathInfo;
var match = path.IndexOf(Resource) >= 0;
data.Query = string.Empty;
var headers = new Dictionary<string, string>();
foreach (var header in request.Headers.AllKeys)
headers[header] = request.Headers[header];
data.Headers = headers;
data.Path = request.PathInfo;
var pieces = request.PathInfo.Split('/');
if (request.PathInfo.IndexOf('/') == 0)
{
//if (request.RequestContext.HttpContext.IsWebSocketRequest && pieces.Length == 4)
// return null;
if (!match)
{
int protocol = 0;
int.TryParse(pieces[1], out protocol);
data.Protocol = protocol;
if (pieces.Length > 2)
{
data.Transport = pieces[2];
if (pieces.Length > 3)
data.Id = pieces[3];
}
}
data.IsStatic = match;
}
return data;
}
protected async Task HandleHandshake(SocketIoData data, HttpContextBase context)
{
var request = data.Request;
var response = context.Response;
response.ContentType = "text/plain";
var handshakeData = await HandshakeData(data);
// todo: handle origin cross domain requests
await Authorize(handshakeData);
}
protected async virtual Task<SocketIoData> HandshakeData(SocketIoData data)
{
var tcs = new TaskCompletionSource<SocketIoData>();
tcs.TrySetResult(data);
return await tcs.Task;
}
protected async virtual Task Authorize(SocketIoData handshakeData)
{
await OnAuthorize(null, true, null, handshakeData);
}
protected async virtual Task OnAuthorize(string error, bool authorized, SocketIoData newData, SocketIoData data)
{
var context = data.Request.RequestContext.HttpContext;
var response = context.Response;
response.ContentType = "text/plain";
if (!string.IsNullOrEmpty(error))
{
response.StatusCode = 500;
response.Write(error);
return;
}
if (authorized)
{
var id = await GenerateId(context);
var hs = string.Join(":",
id,
"25", // self.enabled('heartbeats') ? self.get('heartbeat timeout') || '' : ''
"60", // self.get('close timeout') || ''
string.Join(",", Transports.Select(t => t.Key)));
bool jsonP = false;
// todo: update response headers
response.StatusCode = 200;
if (jsonP)
{
// todo
response.ContentType = "application/javascript";
}
else
{
//
}
response.Write(hs);
await OnHandshake(id, newData ?? data);
await Store.Publish("handshake", id, newData ?? data);
// log handshake authorized
}
else
{
response.StatusCode = 403;
response.Write("handshake unauthorized");
response.End();
}
}
public static String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey)
{
const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String secWebSocketAccept = String.Empty;
// 1. Combine the request Sec-WebSocket-Key with magic key.
String ret = secWebSocketKey + MagicKEY;
// 2. Compute the SHA1 hash
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
// 3. Base64 encode the hash
secWebSocketAccept = Convert.ToBase64String(sha1Hash);
return secWebSocketAccept;
}
private static readonly IDictionary<string, bool> _handshaken = new ConcurrentDictionary<string, bool>();
protected virtual async Task OnHandshake(string id, SocketIoData data)
{
_handshaken[id] = true;
}
protected async virtual Task HandleHttpRequest(SocketIoData data, HttpContextBase context)
{
await HandleClient(data, context);
}
protected virtual async Task HandleClient(SocketIoData data, HttpContextBase context)
{
var store = Store;
var response = context.Response;
Func<SocketIoHttpHandler, SocketIoData, HttpRequestBase, ISocketIoTransport> transportFactory;
if (!Transports.TryGetValue(data.Transport, out transportFactory))
{
response.Write("unknown transport: " + data.Transport);
return;
}
bool handShaken = await HasHandshaken(data.Id);
if (handShaken)
{
var transport = transportFactory(this, data, context.Request);
await transport.Handle();
}
else
{
throw new NotImplementedException();
}
}
protected async Task<bool> HasHandshaken(string id)
{
return _handshaken.ContainsKey(id);
}
private static readonly char[] Base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".ToCharArray();
private const int SessionIdLength = 20;
private static readonly Random Random = new Random();
protected async Task<string> GenerateId(HttpContextBase context)
{
var result = new StringBuilder(SessionIdLength);
var bytes = new byte[SessionIdLength];
Random.NextBytes(bytes);
foreach (var t in bytes)
result.Append(Base64Alphabet[t & 0x3F]);
var tcs = new TaskCompletionSource<string>();
tcs.TrySetResult(result.ToString());
return await tcs.Task;
}
protected async Task CorsRequest(HttpContextBase context)
{
throw new NotImplementedException();
}
protected async Task SendSocketIoJs(HttpContextBase context)
{
var response = context.Response;
response.ContentType = "text/javascript";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("WebSocketHelloEchoServer.SocketIo.assets.socket.io.js"))
{
response.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
await stream.CopyToAsync(response.OutputStream);
}
}
protected async Task SendSocketIoMinJs(HttpContextBase context)
{
var response = context.Response;
response.ContentType = "text/javascript";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("WebSocketHelloEchoServer.SocketIo.assets.socket.io.min.js"))
{
response.Headers["Content-Length"] = stream.Length.ToString(CultureInfo.InvariantCulture);
await stream.CopyToAsync(response.OutputStream);
}
}
protected async Task SendError(HttpContextBase context, int statusCode, string message)
{
var response = context.Response;
response.StatusCode = statusCode;
response.Write(message);
response.End();
}
public abstract IDictionary<string, Func<SocketIoHttpHandler, SocketIoData, HttpRequestBase, ISocketIoTransport>> Transports { get; }
public abstract ISocketIoStore Store { get; }
}
public interface ISocketIoSettings
{
}
public class SocketIoSettings : ISocketIoSettings
{
}
public class SocketIoData
{
public string Query { get; set; }
public string Path { get; set; }
public bool IsStatic { get; set; }
public IEnumerable<KeyValuePair<string, string>> Headers { get; set; }
public HttpRequestBase Request { get; set; }
public int Protocol { get; set; }
public string Transport { get; set; }
public string Id { get; set; }
}
public interface ISocketIoTransport
{
string Name { get; }
Task Handle();
}
public interface ISocketIoStoreClient
{
ISocketIoStore Store { get; }
string Id { get; }
}
public interface ISocketIoStore
{
Task<ISocketIoStoreClient> GetClientStore(string id);
Task DestoryClientStore(string id);
Task Publish(string topic, string id, SocketIoData data);
}
public class MemoryStore : ISocketIoStore
{
protected IDictionary<string, ISocketIoStoreClient> _clients = new Dictionary<string, ISocketIoStoreClient>();
public async Task<ISocketIoStoreClient> GetClientStore(string id)
{
ISocketIoStoreClient store;
_clients.TryGetValue(id, out store);
if (store == null)
_clients[id] = new SocketIoStoreClient(this, id);
var tcs = new TaskCompletionSource<ISocketIoStoreClient>();
tcs.TrySetResult(store);
return await tcs.Task;
}
public async Task DestoryClientStore(string id)
{
_clients.Remove(id);
}
public async Task Publish(string topic, string id, SocketIoData data)
{
}
}
public class SocketIoStoreClient : ISocketIoStoreClient
{
private readonly ISocketIoStore _store;
private readonly string _id;
public SocketIoStoreClient(ISocketIoStore store, string id)
{
_store = store;
_id = id;
}
public ISocketIoStore Store
{
get { return _store; }
}
public string Id
{
get { return _id; }
}
}
public class SocketIoWebSocketTransports : ISocketIoTransport
{
private readonly SocketIoHttpHandler _manager;
private readonly SocketIoData _data;
private readonly HttpRequestBase _request;
public SocketIoWebSocketTransports(SocketIoHttpHandler manager, SocketIoData data, HttpRequestBase request)
{
_manager = manager;
_data = data;
_request = request;
}
public const string NAME = "websocket";
public string Name
{
get { return NAME; }
}
public async Task Handle()
{
var context = _request.RequestContext.HttpContext;
if (context.IsWebSocketRequestUpgrading)
{
var response = context.Response;
}
if (context.IsWebSocketRequest || context.IsWebSocketRequestUpgrading)
context.AcceptWebSocketRequest(socketContext => ProcessSocketRequest(socketContext, context));
else
{
throw new InvalidOperationException("Not a web socket request");
}
}
protected virtual async Task ProcessSocketRequest(AspNetWebSocketContext socketContext, HttpContextBase httpContext)
{
const int maxMessageSize = 1024;
byte[] receiveBuffer = new byte[maxMessageSize];
var socket = socketContext.WebSocket as AspNetWebSocket;
if (httpContext.IsWebSocketRequestUpgrading)
{
//var sb = new StringBuilder();
//sb.Append("HTTP/1.1 101 Switching Protocols\r\n");
//sb.Append("Upgrade: WebSocket\r\n");
//sb.Append("Connection: Upgrade\r\n");
//sb.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", ComputeWebSocketHandshakeSecurityHash09(socketContext.SecWebSocketKey));
////sb.AppendFormat("WebSocket-Origin: {0}\r\n", socketContext.Origin);
////sb.AppendFormat("WebSocket-Location: {0}\r\n", "ws://localhost/WebSocketHelloEchoServer/socketiosample.ashx");
//var keyHash = ComputeWebSocketHandshakeSecurityHash09(socketContext.SecWebSocketKey);
//var buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(sb.ToString()));
//await socket.SendAsync(buffer, WebSocketMessageType.Close, true, CancellationToken.None);
}
else
{
// maintain socket
while (true)
{
if (socket.State == WebSocketState.Open)
{
}
else
{
}
}
}
}
public String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey)
{
const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String secWebSocketAccept = String.Empty;
// 1. Combine the request Sec-WebSocket-Key with magic key.
String ret = secWebSocketKey + MagicKEY;
// 2. Compute the SHA1 hash
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));
// 3. Base64 encode the hash
secWebSocketAccept = Convert.ToBase64String(sha1Hash);
return secWebSocketAccept;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment