Files
DotNetty/examples/WebSockets.Server/WebSocketServerHandler.cs
2018-08-13 10:58:52 -07:00

161 lines
5.5 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Server
{
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using Examples.Common;
using static DotNetty.Codecs.Http.HttpVersion;
using static DotNetty.Codecs.Http.HttpResponseStatus;
public sealed class WebSocketServerHandler : SimpleChannelInboundHandler<object>
{
const string WebsocketPath = "/websocket";
WebSocketServerHandshaker handshaker;
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
{
if (msg is IFullHttpRequest request)
{
this.HandleHttpRequest(ctx, request);
}
else if (msg is WebSocketFrame frame)
{
this.HandleWebSocketFrame(ctx, frame);
}
}
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
void HandleHttpRequest(IChannelHandlerContext ctx, IFullHttpRequest req)
{
// Handle a bad request.
if (!req.Result.IsSuccess)
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest));
return;
}
// Allow only GET methods.
if (!Equals(req.Method, HttpMethod.Get))
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden));
return;
}
// Send the demo page and favicon.ico
if ("/".Equals(req.Uri))
{
IByteBuffer content = WebSocketServerBenchmarkPage.GetContent(GetWebSocketLocation(req));
var res = new DefaultFullHttpResponse(Http11, OK, content);
res.Headers.Set(HttpHeaderNames.ContentType, "text/html; charset=UTF-8");
HttpUtil.SetContentLength(res, content.ReadableBytes);
SendHttpResponse(ctx, req, res);
return;
}
if ("/favicon.ico".Equals(req.Uri))
{
var res = new DefaultFullHttpResponse(Http11, NotFound);
SendHttpResponse(ctx, req, res);
return;
}
// Handshake
var wsFactory = new WebSocketServerHandshakerFactory(
GetWebSocketLocation(req), null, true, 5 * 1024 * 1024);
this.handshaker = wsFactory.NewHandshaker(req);
if (this.handshaker == null)
{
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel);
}
else
{
this.handshaker.HandshakeAsync(ctx.Channel, req);
}
}
void HandleWebSocketFrame(IChannelHandlerContext ctx, WebSocketFrame frame)
{
// Check for closing frame
if (frame is CloseWebSocketFrame)
{
this.handshaker.CloseAsync(ctx.Channel, (CloseWebSocketFrame)frame.Retain());
return;
}
if (frame is PingWebSocketFrame)
{
ctx.WriteAsync(new PongWebSocketFrame((IByteBuffer)frame.Content.Retain()));
return;
}
if (frame is TextWebSocketFrame)
{
// Echo the frame
ctx.WriteAsync(frame.Retain());
return;
}
if (frame is BinaryWebSocketFrame)
{
// Echo the frame
ctx.WriteAsync(frame.Retain());
}
}
static void SendHttpResponse(IChannelHandlerContext ctx, IFullHttpRequest req, IFullHttpResponse res)
{
// Generate an error page if response getStatus code is not OK (200).
if (res.Status.Code != 200)
{
IByteBuffer buf = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(res.Status.ToString()));
res.Content.WriteBytes(buf);
buf.Release();
HttpUtil.SetContentLength(res, res.Content.ReadableBytes);
}
// Send the response and close the connection if necessary.
Task task = ctx.Channel.WriteAndFlushAsync(res);
if (!HttpUtil.IsKeepAlive(req) || res.Status.Code != 200)
{
task.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(),
ctx, TaskContinuationOptions.ExecuteSynchronously);
}
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception e)
{
Console.WriteLine($"{nameof(WebSocketServerHandler)} {0}", e);
ctx.CloseAsync();
}
static string GetWebSocketLocation(IFullHttpRequest req)
{
bool result = req.Headers.TryGet(HttpHeaderNames.Host, out ICharSequence value);
Debug.Assert(result, "Host header does not exist.");
string location= value.ToString() + WebsocketPath;
if (ServerSettings.IsSsl)
{
return "wss://" + location;
}
else
{
return "ws://" + location;
}
}
}
}