Skip to content

Instantly share code, notes, and snippets.

@OliverLeitner
Created July 11, 2018 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OliverLeitner/0e70906e46b2315ee943a56ab6296c1e to your computer and use it in GitHub Desktop.
Save OliverLeitner/0e70906e46b2315ee943a56ab6296c1e to your computer and use it in GitHub Desktop.
better websocket sample, based upon https://github.com/gpeipman/AspNetCoreChatRoom
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - AspNetCoreChatRoom</title>
<environment include="Development">
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<div class="container body-content">
@RenderBody()
</div>
</body>
</html>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.1.1" />
</ItemGroup>
</Project>
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace AspNetCoreChatRoom {
public class ChatWebSocketMiddleware {
private static ConcurrentDictionary<string, WebSocket> _sockets = new ConcurrentDictionary<string, WebSocket> ();
private readonly RequestDelegate _next;
public ChatWebSocketMiddleware (RequestDelegate next) {
_next = next;
}
public async Task Invoke (HttpContext context) {
if (!context.WebSockets.IsWebSocketRequest) {
await _next.Invoke (context);
return;
}
CancellationToken ct = context.RequestAborted;
WebSocket currentSocket;
//trying to build a connection
try {
currentSocket = await context.WebSockets.AcceptWebSocketAsync ();
} catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
var socketId = Guid.NewGuid ().ToString ();
_sockets.TryAdd (socketId, currentSocket);
while (true) {
if (ct.IsCancellationRequested) {
break;
}
var response = await ReceiveStringAsync (currentSocket, ct);
if (string.IsNullOrEmpty (response)) {
if (currentSocket.State != WebSocketState.Open) {
break;
}
continue;
}
foreach (var socket in _sockets) {
if (socket.Value.State != WebSocketState.Open) {
continue;
}
await SendStringAsync (socket.Value, response, ct, socketId);
}
}
WebSocket dummy;
_sockets.TryRemove (socketId, out dummy);
//trying to normally close the connection
try {
await currentSocket.CloseAsync (WebSocketCloseStatus.NormalClosure, "Closing", ct);
currentSocket.Dispose ();
} catch (Exception ex) {
//if anything goes wrong...
//output error
Console.WriteLine (ex.Message);
currentSocket.Abort ();
}
}
private static Task SendStringAsync (WebSocket socket, string data, CancellationToken ct = default (CancellationToken), string socketId = "deadbeef") {
//append the socketid to the message for debugging purposes
//couldve taken it globally, this is just a poc
var buffer = Encoding.UTF8.GetBytes (data + ' ' + socketId);
var segment = new ArraySegment<byte> (buffer);
return socket.SendAsync (segment, WebSocketMessageType.Text, true, ct);
}
private static async Task<string> ReceiveStringAsync (WebSocket socket, CancellationToken ct = default (CancellationToken)) {
var buffer = new ArraySegment<byte> (new byte[8192]);
using (var ms = new MemoryStream ()) {
WebSocketReceiveResult result;
do {
ct.ThrowIfCancellationRequested ();
result = await socket.ReceiveAsync (buffer, ct);
ms.Write (buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek (0, SeekOrigin.Begin);
if (result.MessageType != WebSocketMessageType.Text) {
return null;
}
// Encoding UTF8: https://tools.ietf.org/html/rfc6455#section-5.6
using (var reader = new StreamReader (ms, Encoding.UTF8)) {
return await reader.ReadToEndAsync ();
}
}
}
}
}
using Microsoft.AspNetCore.Mvc;
namespace AspNetCoreChatRoom.Controllers {
public class HomeController : Controller {
[HttpGet]
public IActionResult Index () {
return View ("InsertUserName");
}
[HttpPost]
public IActionResult Index (string username) {
return View ("Index", username);
}
public IActionResult Error () {
return View ();
}
}
}
@model string
@{
ViewData["Title"] = "Home Page";
}
<div class="msg">
<div id="msgs"></div>
</div>
<div>
<input type="text" id="MessageField" placeholder="type message and press enter" />
</div>
<script>
const userName = '@Model';
</script>
<environment include="Development">
<script src="~/js/site.js"></script>
</environment>
<environment exclude="Development">
<script src="~/js/site.min.js"></script>
</environment>
<form action="@Url.Action("Index")" method="post">
<input type="text" placeholder="Insert user name" name="userName" />
<input type="submit" value="Eńter" />
</form>
body {
margin: 0;
padding: 0;
float:left;
width: 100%;
}
.msg {
display:block;
white-space:nowrap;
padding-left: 30px;
padding-top: 10px;
}
input#MessageField {
position: absolute;
bottom: 0;
min-width: 320px;
width: calc(100% - 23px);
height: 30px;
padding-left: 20px;
}
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const wsUri = protocol + "//" + window.location.host;
const socket = new WebSocket(wsUri);
socket.onopen = e => {
console.log("socket opened", e);
};
socket.onclose = ((e) => {
console.log("socket closed", e);
});
socket.onmessage = ((e) => {
console.log(e);
document.getElementById('msgs').innerHTML += e.data + '<br/>';
});
socket.onerror = ((e) => {
console.error(e.data);
});
window.addEventListener('keydown', ((e) => {
//13 == enter key
//and if nothing is in the input field -> no reason to submit it
if (e.keyCode != 13 || e.target.value === undefined || e.target.value === '') {
return;
}
e.preventDefault();
let message = userName + ": " + e.target.value;
socket.send(message);
e.target.value = '';
}));
//needed for timespan function
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
//enable useresponsecompression
using Microsoft.AspNetCore.ResponseCompression;
//for proxying
using Microsoft.AspNetCore.HttpOverrides;
namespace AspNetCoreChatRoom {
public class Startup {
public Startup (IHostingEnvironment env) {
var builder = new ConfigurationBuilder ()
.SetBasePath (env.ContentRootPath)
.AddJsonFile ("appsettings.json", optional : false, reloadOnChange : true)
.AddJsonFile ($"appsettings.{env.EnvironmentName}.json", optional : true)
.AddEnvironmentVariables ();
Configuration = builder.Build ();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices (IServiceCollection services) {
// Add framework services.
services.AddMvc ();
//deflate as a service
services.AddResponseCompression ();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {
loggerFactory.AddConsole (Configuration.GetSection ("Logging"));
loggerFactory.AddDebug ();
if (env.IsDevelopment ()) {
app.UseDeveloperExceptionPage ();
} else {
app.UseExceptionHandler ("/Shared/Error");
}
app.UseStaticFiles ();
//use deflate
app.UseResponseCompression ();
var wsOptions = new WebSocketOptions () {
KeepAliveInterval = TimeSpan.FromSeconds (120),
ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets (wsOptions);
app.UseMiddleware<ChatWebSocketMiddleware> ();
app.UseForwardedHeaders (new ForwardedHeadersOptions {
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseMvc (routes => {
routes.MapRoute (
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment