-
-
Save 19317362/9aa18fbbc71b6323ee0ef8e572c03e8a to your computer and use it in GitHub Desktop.
socket.io 4 .net
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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