aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
diff options
context:
space:
mode:
authorJoshua M. Boniface <joshua@boniface.me>2019-01-07 00:47:06 -0500
committerGitHub <noreply@github.com>2019-01-07 00:47:06 -0500
commitc986340c02cb0b7fe06569d25a1b5d1c8375f7f6 (patch)
tree4971c970bbdb797cc5b3da2d8bae506231b173a5 /Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
parent76b647e0a8eddd65dc9c4de7b887a3faf6e12bcc (diff)
parent0b804629b85498370c882f5562dfc7acd84bfd11 (diff)
Merge pull request #419 from jellyfin/dev
Master 10.0.0
Diffstat (limited to 'Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs')
-rw-r--r--Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs260
1 files changed, 260 insertions, 0 deletions
diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
new file mode 100644
index 000000000..c360a8fce
--- /dev/null
+++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.Net;
+using Emby.Server.Implementations.HttpServer;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Services;
+using MediaBrowser.Model.System;
+using MediaBrowser.Model.Text;
+using Microsoft.Extensions.Logging;
+using SocketHttpListener.Net;
+
+namespace Jellyfin.SocketSharp
+{
+ public class WebSocketSharpListener : IHttpListener
+ {
+ private HttpListener _listener;
+
+ private readonly ILogger _logger;
+ private readonly X509Certificate _certificate;
+ private readonly IStreamHelper _streamHelper;
+ private readonly ITextEncoding _textEncoding;
+ private readonly INetworkManager _networkManager;
+ private readonly ISocketFactory _socketFactory;
+ private readonly ICryptoProvider _cryptoProvider;
+ private readonly IFileSystem _fileSystem;
+ private readonly bool _enableDualMode;
+ private readonly IEnvironmentInfo _environment;
+
+ private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
+ private CancellationToken _disposeCancellationToken;
+
+ public WebSocketSharpListener(ILogger logger, X509Certificate certificate, IStreamHelper streamHelper, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, bool enableDualMode, IFileSystem fileSystem, IEnvironmentInfo environment)
+ {
+ _logger = logger;
+ _certificate = certificate;
+ _streamHelper = streamHelper;
+ _textEncoding = textEncoding;
+ _networkManager = networkManager;
+ _socketFactory = socketFactory;
+ _cryptoProvider = cryptoProvider;
+ _enableDualMode = enableDualMode;
+ _fileSystem = fileSystem;
+ _environment = environment;
+
+ _disposeCancellationToken = _disposeCancellationTokenSource.Token;
+ }
+
+ public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
+ public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
+
+ public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
+
+ public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
+
+ public void Start(IEnumerable<string> urlPrefixes)
+ {
+ if (_listener == null)
+ _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _networkManager, _textEncoding, _streamHelper, _fileSystem, _environment);
+
+ _listener.EnableDualMode = _enableDualMode;
+
+ if (_certificate != null)
+ {
+ _listener.LoadCert(_certificate);
+ }
+
+ foreach (var prefix in urlPrefixes)
+ {
+ _logger.LogInformation("Adding HttpListener prefix " + prefix);
+ _listener.Prefixes.Add(prefix);
+ }
+
+ _listener.OnContext = ProcessContext;
+
+ _listener.Start();
+ }
+
+ private void ProcessContext(HttpListenerContext context)
+ {
+ //InitTask(context, _disposeCancellationToken);
+ Task.Run(() => InitTask(context, _disposeCancellationToken));
+ }
+
+ private void LogRequest(ILogger logger, HttpListenerRequest request)
+ {
+ var url = request.Url.ToString();
+
+ logger.LogInformation("{0} {1}. UserAgent: {2}", request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, url, request.UserAgent ?? string.Empty);
+ }
+
+ private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
+ {
+ IHttpRequest httpReq = null;
+ var request = context.Request;
+
+ try
+ {
+ if (request.IsWebSocketRequest)
+ {
+ LogRequest(_logger, request);
+
+ return ProcessWebSocketRequest(context);
+ }
+
+ httpReq = GetRequest(context);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing request");
+
+ httpReq = httpReq ?? GetRequest(context);
+ return ErrorHandler(ex, httpReq, true, true);
+ }
+
+ var uri = request.Url;
+
+ return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken);
+ }
+
+ private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
+ {
+ try
+ {
+ var endpoint = ctx.Request.RemoteEndPoint.ToString();
+ var url = ctx.Request.RawUrl;
+
+ var queryString = ctx.Request.QueryString;
+
+ var connectingArgs = new WebSocketConnectingEventArgs
+ {
+ Url = url,
+ QueryString = queryString,
+ Endpoint = endpoint
+ };
+
+ if (WebSocketConnecting != null)
+ {
+ WebSocketConnecting(connectingArgs);
+ }
+
+ if (connectingArgs.AllowConnection)
+ {
+ _logger.LogDebug("Web socket connection allowed");
+
+ var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
+
+ if (WebSocketConnected != null)
+ {
+ var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger);
+
+ WebSocketConnected(new WebSocketConnectEventArgs
+ {
+ Url = url,
+ QueryString = queryString,
+ WebSocket = socket,
+ Endpoint = endpoint
+ });
+
+ await ReceiveWebSocket(ctx, socket).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ _logger.LogWarning("Web socket connection not allowed");
+ ctx.Response.StatusCode = 401;
+ ctx.Response.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "AcceptWebSocketAsync error");
+ ctx.Response.StatusCode = 500;
+ ctx.Response.Close();
+ }
+ }
+
+ private async Task ReceiveWebSocket(HttpListenerContext ctx, SharpWebSocket socket)
+ {
+ try
+ {
+ await socket.StartReceive().ConfigureAwait(false);
+ }
+ finally
+ {
+ TryClose(ctx, 200);
+ }
+ }
+
+ private void TryClose(HttpListenerContext ctx, int statusCode)
+ {
+ try
+ {
+ ctx.Response.StatusCode = 200;
+ ctx.Response.Close();
+ }
+ catch (ObjectDisposedException)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error closing web socket response");
+ }
+ }
+
+ private IHttpRequest GetRequest(HttpListenerContext httpContext)
+ {
+ var urlSegments = httpContext.Request.Url.Segments;
+
+ var operationName = urlSegments[urlSegments.Length - 1];
+
+ var req = new WebSocketSharpRequest(httpContext, operationName, _logger);
+
+ return req;
+ }
+
+ public Task Stop()
+ {
+ _disposeCancellationTokenSource.Cancel();
+
+ if (_listener != null)
+ {
+ _listener.Close();
+ }
+
+ return Task.CompletedTask;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ private bool _disposed;
+ private readonly object _disposeLock = new object();
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed) return;
+
+ lock (_disposeLock)
+ {
+ if (_disposed) return;
+
+ if (disposing)
+ {
+ Stop();
+ }
+
+ //release unmanaged resources here...
+ _disposed = true;
+ }
+ }
+ }
+}