consolidate web socket onto one port

This commit is contained in:
Luke Pulverenti 2014-07-18 21:28:40 -04:00
parent ea559a6e27
commit bc657237aa
20 changed files with 1900 additions and 478 deletions

View file

@ -78,7 +78,6 @@
<Compile Include="Net\INetworkManager.cs" />
<Compile Include="Net\IWebSocket.cs" />
<Compile Include="Net\IWebSocketConnection.cs" />
<Compile Include="Net\IWebSocketServer.cs" />
<Compile Include="Net\MimeTypes.cs" />
<Compile Include="Net\WebSocketConnectEventArgs.cs" />
<Compile Include="Net\WebSocketMessageInfo.cs" />

View file

@ -10,12 +10,6 @@ namespace MediaBrowser.Common.Net
/// </summary>
public interface IServerManager : IDisposable
{
/// <summary>
/// Gets a value indicating whether [supports web socket].
/// </summary>
/// <value><c>true</c> if [supports web socket]; otherwise, <c>false</c>.</value>
bool SupportsNativeWebSocket { get; }
/// <summary>
/// Gets the web socket port number.
/// </summary>
@ -28,11 +22,6 @@ namespace MediaBrowser.Common.Net
/// <param name="urlPrefixes">The URL prefixes.</param>
void Start(IEnumerable<string> urlPrefixes);
/// <summary>
/// Starts the web socket server.
/// </summary>
void StartWebSocketServer();
/// <summary>
/// Sends a message to all clients currently connected via a web socket
/// </summary>

View file

@ -1,32 +0,0 @@
using System;
namespace MediaBrowser.Common.Net
{
/// <summary>
/// Interface IWebSocketServer
/// </summary>
public interface IWebSocketServer : IDisposable
{
/// <summary>
/// Starts the specified port number.
/// </summary>
/// <param name="portNumber">The port number.</param>
void Start(int portNumber);
/// <summary>
/// Stops this instance.
/// </summary>
void Stop();
/// <summary>
/// Occurs when [web socket connected].
/// </summary>
event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
/// Gets the port.
/// </summary>
/// <value>The port.</value>
int Port { get; }
}
}

View file

@ -21,12 +21,6 @@ namespace MediaBrowser.Controller.Net
/// <param name="urlPrefixes">The URL prefixes.</param>
void StartServer(IEnumerable<string> urlPrefixes);
/// <summary>
/// Gets a value indicating whether [supports web sockets].
/// </summary>
/// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
bool SupportsWebSockets { get; }
/// <summary>
/// Gets the local end points.
/// </summary>

View file

@ -6,6 +6,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations.HttpServer.NetListener;
using MediaBrowser.Server.Implementations.HttpServer.SocketSharp;
using ServiceStack;
using ServiceStack.Api.Swagger;
using ServiceStack.Host;
@ -18,7 +19,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@ -42,7 +42,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
private readonly ContainerAdapter _containerAdapter;
private readonly ConcurrentDictionary<string, string> _localEndPoints = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
@ -157,12 +156,13 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
_listener = new HttpListenerServer(_logger, _threadPoolManager)
{
WebSocketHandler = WebSocketHandler,
ErrorHandler = ErrorHandler,
RequestHandler = RequestHandler
};
_listener = NativeWebSocket.IsSupported
? _listener = new HttpListenerServer(_logger, _threadPoolManager)
: _listener = new WebSocketSharpListener(_logger, _threadPoolManager);
_listener.WebSocketHandler = WebSocketHandler;
_listener.ErrorHandler = ErrorHandler;
_listener.RequestHandler = RequestHandler;
_listener.Start(UrlPrefixes);
}
@ -175,24 +175,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
}
}
/// <summary>
/// Logs the HTTP request.
/// </summary>
/// <param name="request">The request.</param>
private void LogHttpRequest(HttpListenerRequest request)
{
var endpoint = request.LocalEndPoint;
if (endpoint != null)
{
var address = endpoint.ToString();
_localEndPoints.GetOrAdd(address, address);
}
LoggerUtils.LogRequest(_logger, request);
}
private void ErrorHandler(Exception ex, IRequest httpReq)
{
try
@ -261,6 +243,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <param name="url">The URL.</param>
/// <returns>Task.</returns>
protected Task RequestHandler(IHttpRequest httpReq, Uri url)
{
@ -310,17 +293,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer
task.ContinueWith(x => httpRes.Close(), TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.AttachedToParent);
//Matches Exceptions handled in HttpListenerBase.InitTask()
var statusCode = httpRes.StatusCode;
var urlString = url.ToString();
task.ContinueWith(x =>
{
var statusCode = httpRes.StatusCode;
var duration = DateTime.Now - date;
LoggerUtils.LogResponse(_logger, statusCode, urlString, remoteIp, duration);
}, TaskContinuationOptions.None);
return task;
}
@ -393,10 +376,5 @@ namespace MediaBrowser.Server.Implementations.HttpServer
UrlPrefixes = urlPrefixes.ToList();
Start(UrlPrefixes.First());
}
public bool SupportsWebSockets
{
get { return NativeWebSocket.IsSupported; }
}
}
}

View file

@ -7,24 +7,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
{
public static class LoggerUtils
{
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
public static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var log = new StringBuilder();
//var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
//log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
}
/// <summary>
/// Logs the response.
/// </summary>

View file

@ -1,4 +1,5 @@
using Amib.Threading;
using System.Text;
using Amib.Threading;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
@ -132,7 +133,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
_threadPoolManager.QueueWorkItem(() => InitTask(context));
}
public virtual void InitTask(HttpListenerContext context)
private void InitTask(HttpListenerContext context)
{
try
{
@ -150,7 +151,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
}
}
protected Task ProcessRequestAsync(HttpListenerContext context)
private Task ProcessRequestAsync(HttpListenerContext context)
{
var request = context.Request;
@ -235,7 +236,25 @@ namespace MediaBrowser.Server.Implementations.HttpServer.NetListener
_localEndPoints.GetOrAdd(address, address);
}
LoggerUtils.LogRequest(_logger, request);
LogRequest(_logger, request);
}
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var log = new StringBuilder();
//var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
//log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
}
public void Stop()

View file

@ -1,4 +1,5 @@
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations.HttpServer.SocketSharp;
using ServiceStack;
using ServiceStack.Web;
using System;
@ -66,13 +67,23 @@ namespace MediaBrowser.Server.Implementations.HttpServer
if (length > 0)
{
var response = (HttpListenerResponse)res.OriginalResponse;
res.SetContentLength(length);
var listenerResponse = res.OriginalResponse as HttpListenerResponse;
response.ContentLength64 = length;
// Disable chunked encoding. Technically this is only needed when using Content-Range, but
// anytime we know the content length there's no need for it
response.SendChunked = false;
if (listenerResponse != null)
{
// Disable chunked encoding. Technically this is only needed when using Content-Range, but
// anytime we know the content length there's no need for it
listenerResponse.SendChunked = false;
return;
}
var sharpResponse = res as WebSocketSharpResponse;
if (sharpResponse != null)
{
sharpResponse.SendChunked = false;
}
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using MediaBrowser.Model.Logging;
using WebSocketSharp.Net;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public static class Extensions
{
public static string GetOperationName(this HttpListenerRequest request)
{
return request.Url.Segments[request.Url.Segments.Length - 1];
}
public static void CloseOutputStream(this HttpListenerResponse response, ILogger logger)
{
try
{
response.OutputStream.Flush();
response.OutputStream.Close();
response.Close();
}
catch (Exception ex)
{
logger.ErrorException("Error in HttpListenerResponseWrapper: " + ex.Message, ex);
}
}
}
}

View file

@ -0,0 +1,918 @@
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
using System.Web;
using ServiceStack.Web;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
static internal string GetParameter(string header, string attr)
{
int ap = header.IndexOf(attr);
if (ap == -1)
return null;
ap += attr.Length;
if (ap >= header.Length)
return null;
char ending = header[ap];
if (ending != '"')
ending = ' ';
int end = header.IndexOf(ending, ap + 1);
if (end == -1)
return (ending == '"') ? null : header.Substring(ap);
return header.Substring(ap + 1, end - ap - 1);
}
void LoadMultiPart()
{
string boundary = GetParameter(ContentType, "; boundary=");
if (boundary == null)
return;
var input = GetSubStream(InputStream);
//DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
//Not ending with \r\n?
var ms = new MemoryStream(32 * 1024);
input.CopyTo(ms);
input = ms;
ms.WriteByte((byte)'\r');
ms.WriteByte((byte)'\n');
input.Position = 0;
//Uncomment to debug
//var content = new StreamReader(ms).ReadToEnd();
//Console.WriteLine(boundary + "::" + content);
//input.Position = 0;
var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
HttpMultipart.Element e;
while ((e = multi_part.ReadNextElement()) != null)
{
if (e.Filename == null)
{
byte[] copy = new byte[e.Length];
input.Position = e.Start;
input.Read(copy, 0, (int)e.Length);
form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy));
}
else
{
//
// We use a substream, as in 2.x we will support large uploads streamed to disk,
//
HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
files.AddFile(e.Name, sub);
}
}
EndSubStream(input);
}
public NameValueCollection Form
{
get
{
if (form == null)
{
form = new WebROCollection();
files = new HttpFileCollection();
if (IsContentType("multipart/form-data", true))
LoadMultiPart();
else if (
IsContentType("application/x-www-form-urlencoded", true))
LoadWwwForm();
form.Protect();
}
#if NET_4_0
if (validateRequestNewMode && !checked_form) {
// Setting this before calling the validator prevents
// possible endless recursion
checked_form = true;
ValidateNameValueCollection ("Form", query_string_nvc, RequestValidationSource.Form);
} else
#endif
if (validate_form && !checked_form)
{
checked_form = true;
ValidateNameValueCollection("Form", form);
}
return form;
}
}
protected bool validate_cookies, validate_query_string, validate_form;
protected bool checked_cookies, checked_query_string, checked_form;
static void ThrowValidationException(string name, string key, string value)
{
string v = "\"" + value + "\"";
if (v.Length > 20)
v = v.Substring(0, 16) + "...\"";
string msg = String.Format("A potentially dangerous Request.{0} value was " +
"detected from the client ({1}={2}).", name, key, v);
throw new HttpRequestValidationException(msg);
}
static void ValidateNameValueCollection(string name, NameValueCollection coll)
{
if (coll == null)
return;
foreach (string key in coll.Keys)
{
string val = coll[key];
if (val != null && val.Length > 0 && IsInvalidString(val))
ThrowValidationException(name, key, val);
}
}
internal static bool IsInvalidString(string val)
{
int validationFailureIndex;
return IsInvalidString(val, out validationFailureIndex);
}
internal static bool IsInvalidString(string val, out int validationFailureIndex)
{
validationFailureIndex = 0;
int len = val.Length;
if (len < 2)
return false;
char current = val[0];
for (int idx = 1; idx < len; idx++)
{
char next = val[idx];
// See http://secunia.com/advisories/14325
if (current == '<' || current == '\xff1c')
{
if (next == '!' || next < ' '
|| (next >= 'a' && next <= 'z')
|| (next >= 'A' && next <= 'Z'))
{
validationFailureIndex = idx - 1;
return true;
}
}
else if (current == '&' && next == '#')
{
validationFailureIndex = idx - 1;
return true;
}
current = next;
}
return false;
}
public void ValidateInput()
{
validate_cookies = true;
validate_query_string = true;
validate_form = true;
}
bool IsContentType(string ct, bool starts_with)
{
if (ct == null || ContentType == null) return false;
if (starts_with)
return StrUtils.StartsWith(ContentType, ct, true);
return String.Compare(ContentType, ct, true, Helpers.InvariantCulture) == 0;
}
void LoadWwwForm()
{
using (Stream input = GetSubStream(InputStream))
{
using (StreamReader s = new StreamReader(input, ContentEncoding))
{
StringBuilder key = new StringBuilder();
StringBuilder value = new StringBuilder();
int c;
while ((c = s.Read()) != -1)
{
if (c == '=')
{
value.Length = 0;
while ((c = s.Read()) != -1)
{
if (c == '&')
{
AddRawKeyValue(key, value);
break;
}
else
value.Append((char)c);
}
if (c == -1)
{
AddRawKeyValue(key, value);
return;
}
}
else if (c == '&')
AddRawKeyValue(key, value);
else
key.Append((char)c);
}
if (c == -1)
AddRawKeyValue(key, value);
EndSubStream(input);
}
}
}
void AddRawKeyValue(StringBuilder key, StringBuilder value)
{
string decodedKey = HttpUtility.UrlDecode(key.ToString(), ContentEncoding);
form.Add(decodedKey,
HttpUtility.UrlDecode(value.ToString(), ContentEncoding));
key.Length = 0;
value.Length = 0;
}
WebROCollection form;
HttpFileCollection files;
public sealed class HttpFileCollection : NameObjectCollectionBase
{
internal HttpFileCollection()
{
}
internal void AddFile(string name, HttpPostedFile file)
{
BaseAdd(name, file);
}
public void CopyTo(Array dest, int index)
{
/* XXX this is kind of gross and inefficient
* since it makes a copy of the superclass's
* list */
object[] values = BaseGetAllValues();
values.CopyTo(dest, index);
}
public string GetKey(int index)
{
return BaseGetKey(index);
}
public HttpPostedFile Get(int index)
{
return (HttpPostedFile)BaseGet(index);
}
public HttpPostedFile Get(string key)
{
return (HttpPostedFile)BaseGet(key);
}
public HttpPostedFile this[string key]
{
get
{
return Get(key);
}
}
public HttpPostedFile this[int index]
{
get
{
return Get(index);
}
}
public string[] AllKeys
{
get
{
return BaseGetAllKeys();
}
}
}
class WebROCollection : NameValueCollection
{
bool got_id;
int id;
public bool GotID
{
get { return got_id; }
}
public int ID
{
get { return id; }
set
{
got_id = true;
id = value;
}
}
public void Protect()
{
IsReadOnly = true;
}
public void Unprotect()
{
IsReadOnly = false;
}
public override string ToString()
{
StringBuilder result = new StringBuilder();
foreach (string key in AllKeys)
{
if (result.Length > 0)
result.Append('&');
if (key != null && key.Length > 0)
{
result.Append(key);
result.Append('=');
}
result.Append(Get(key));
}
return result.ToString();
}
}
public sealed class HttpPostedFile
{
string name;
string content_type;
Stream stream;
class ReadSubStream : Stream
{
Stream s;
long offset;
long end;
long position;
public ReadSubStream(Stream s, long offset, long length)
{
this.s = s;
this.offset = offset;
this.end = offset + length;
position = offset;
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int dest_offset, int count)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (dest_offset < 0)
throw new ArgumentOutOfRangeException("dest_offset", "< 0");
if (count < 0)
throw new ArgumentOutOfRangeException("count", "< 0");
int len = buffer.Length;
if (dest_offset > len)
throw new ArgumentException("destination offset is beyond array size");
// reordered to avoid possible integer overflow
if (dest_offset > len - count)
throw new ArgumentException("Reading would overrun buffer");
if (count > end - position)
count = (int)(end - position);
if (count <= 0)
return 0;
s.Position = position;
int result = s.Read(buffer, dest_offset, count);
if (result > 0)
position += result;
else
position = end;
return result;
}
public override int ReadByte()
{
if (position >= end)
return -1;
s.Position = position;
int result = s.ReadByte();
if (result < 0)
position = end;
else
position++;
return result;
}
public override long Seek(long d, SeekOrigin origin)
{
long real;
switch (origin)
{
case SeekOrigin.Begin:
real = offset + d;
break;
case SeekOrigin.End:
real = end + d;
break;
case SeekOrigin.Current:
real = position + d;
break;
default:
throw new ArgumentException();
}
long virt = real - offset;
if (virt < 0 || virt > Length)
throw new ArgumentException();
position = s.Seek(real, SeekOrigin.Begin);
return position;
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return false; }
}
public override long Length
{
get { return end - offset; }
}
public override long Position
{
get
{
return position - offset;
}
set
{
if (value > Length)
throw new ArgumentOutOfRangeException();
position = Seek(value, SeekOrigin.Begin);
}
}
}
internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
{
this.name = name;
this.content_type = content_type;
this.stream = new ReadSubStream(base_stream, offset, length);
}
public string ContentType
{
get
{
return (content_type);
}
}
public int ContentLength
{
get
{
return (int)stream.Length;
}
}
public string FileName
{
get
{
return (name);
}
}
public Stream InputStream
{
get
{
return (stream);
}
}
public void SaveAs(string filename)
{
byte[] buffer = new byte[16 * 1024];
long old_post = stream.Position;
try
{
File.Delete(filename);
using (FileStream fs = File.Create(filename))
{
stream.Position = 0;
int n;
while ((n = stream.Read(buffer, 0, 16 * 1024)) != 0)
{
fs.Write(buffer, 0, n);
}
}
}
finally
{
stream.Position = old_post;
}
}
}
class Helpers
{
public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
}
internal sealed class StrUtils
{
StrUtils() { }
public static bool StartsWith(string str1, string str2)
{
return StartsWith(str1, str2, false);
}
public static bool StartsWith(string str1, string str2, bool ignore_case)
{
int l2 = str2.Length;
if (l2 == 0)
return true;
int l1 = str1.Length;
if (l2 > l1)
return false;
return (0 == String.Compare(str1, 0, str2, 0, l2, ignore_case, Helpers.InvariantCulture));
}
public static bool EndsWith(string str1, string str2)
{
return EndsWith(str1, str2, false);
}
public static bool EndsWith(string str1, string str2, bool ignore_case)
{
int l2 = str2.Length;
if (l2 == 0)
return true;
int l1 = str1.Length;
if (l2 > l1)
return false;
return (0 == String.Compare(str1, l1 - l2, str2, 0, l2, ignore_case, Helpers.InvariantCulture));
}
}
class HttpMultipart
{
public class Element
{
public string ContentType;
public string Name;
public string Filename;
public Encoding Encoding;
public long Start;
public long Length;
public override string ToString()
{
return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
Start.ToString() + ", Length " + Length.ToString();
}
}
Stream data;
string boundary;
byte[] boundary_bytes;
byte[] buffer;
bool at_eof;
Encoding encoding;
StringBuilder sb;
const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
// See RFC 2046
// In the case of multipart entities, in which one or more different
// sets of data are combined in a single body, a "multipart" media type
// field must appear in the entity's header. The body must then contain
// one or more body parts, each preceded by a boundary delimiter line,
// and the last one followed by a closing boundary delimiter line.
// After its boundary delimiter line, each body part then consists of a
// header area, a blank line, and a body area. Thus a body part is
// similar to an RFC 822 message in syntax, but different in meaning.
public HttpMultipart(Stream data, string b, Encoding encoding)
{
this.data = data;
//DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
//var ms = new MemoryStream(32 * 1024);
//data.CopyTo(ms);
//this.data = ms;
boundary = b;
boundary_bytes = encoding.GetBytes(b);
buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
this.encoding = encoding;
sb = new StringBuilder();
}
string ReadLine()
{
// CRLF or LF are ok as line endings.
bool got_cr = false;
int b = 0;
sb.Length = 0;
while (true)
{
b = data.ReadByte();
if (b == -1)
{
return null;
}
if (b == LF)
{
break;
}
got_cr = (b == CR);
sb.Append((char)b);
}
if (got_cr)
sb.Length--;
return sb.ToString();
}
static string GetContentDispositionAttribute(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
return null;
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
return null;
if (begin == end)
return "";
return l.Substring(begin, end - begin);
}
string GetContentDispositionAttributeWithEncoding(string l, string name)
{
int idx = l.IndexOf(name + "=\"");
if (idx < 0)
return null;
int begin = idx + name.Length + "=\"".Length;
int end = l.IndexOf('"', begin);
if (end < 0)
return null;
if (begin == end)
return "";
string temp = l.Substring(begin, end - begin);
byte[] source = new byte[temp.Length];
for (int i = temp.Length - 1; i >= 0; i--)
source[i] = (byte)temp[i];
return encoding.GetString(source);
}
bool ReadBoundary()
{
try
{
string line = ReadLine();
while (line == "")
line = ReadLine();
if (line[0] != '-' || line[1] != '-')
return false;
if (!StrUtils.EndsWith(line, boundary, false))
return true;
}
catch
{
}
return false;
}
string ReadHeaders()
{
string s = ReadLine();
if (s == "")
return null;
return s;
}
bool CompareBytes(byte[] orig, byte[] other)
{
for (int i = orig.Length - 1; i >= 0; i--)
if (orig[i] != other[i])
return false;
return true;
}
long MoveToNextBoundary()
{
long retval = 0;
bool got_cr = false;
int state = 0;
int c = data.ReadByte();
while (true)
{
if (c == -1)
return -1;
if (state == 0 && c == LF)
{
retval = data.Position - 1;
if (got_cr)
retval--;
state = 1;
c = data.ReadByte();
}
else if (state == 0)
{
got_cr = (c == CR);
c = data.ReadByte();
}
else if (state == 1 && c == '-')
{
c = data.ReadByte();
if (c == -1)
return -1;
if (c != '-')
{
state = 0;
got_cr = false;
continue; // no ReadByte() here
}
int nread = data.Read(buffer, 0, buffer.Length);
int bl = buffer.Length;
if (nread != bl)
return -1;
if (!CompareBytes(boundary_bytes, buffer))
{
state = 0;
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
{
at_eof = true;
}
else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
{
state = 0;
data.Position = retval + 2;
if (got_cr)
{
data.Position++;
got_cr = false;
}
c = data.ReadByte();
continue;
}
data.Position = retval + 2;
if (got_cr)
data.Position++;
break;
}
else
{
// state == 1
state = 0; // no ReadByte() here
}
}
return retval;
}
public Element ReadNextElement()
{
if (at_eof || ReadBoundary())
return null;
Element elem = new Element();
string header;
while ((header = ReadHeaders()) != null)
{
if (StrUtils.StartsWith(header, "Content-Disposition:", true))
{
elem.Name = GetContentDispositionAttribute(header, "name");
elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
}
else if (StrUtils.StartsWith(header, "Content-Type:", true))
{
elem.ContentType = header.Substring("Content-Type:".Length).Trim();
elem.Encoding = GetEncoding(elem.ContentType);
}
}
long start = 0;
start = data.Position;
elem.Start = start;
long pos = MoveToNextBoundary();
if (pos == -1)
return null;
elem.Length = pos - start;
return elem;
}
static string StripPath(string path)
{
if (path == null || path.Length == 0)
return path;
if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
return path;
return path.Substring(path.LastIndexOf('\\') + 1);
}
}
}
}

View file

@ -0,0 +1,157 @@
using System.Text;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using WebSocketMessageType = MediaBrowser.Model.Net.WebSocketMessageType;
using WebSocketState = MediaBrowser.Model.Net.WebSocketState;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public class SharpWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
public event EventHandler<EventArgs> Closed;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private WebSocketSharp.WebSocket WebSocket { get; set; }
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// Initializes a new instance of the <see cref="NativeWebSocket" /> class.
/// </summary>
/// <param name="socket">The socket.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">socket</exception>
public SharpWebSocket(WebSocketSharp.WebSocket socket, ILogger logger)
{
if (socket == null)
{
throw new ArgumentNullException("socket");
}
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
WebSocket = socket;
socket.OnMessage += socket_OnMessage;
socket.OnClose += socket_OnClose;
socket.OnError += socket_OnError;
WebSocket.ConnectAsServer();
}
void socket_OnError(object sender, WebSocketSharp.ErrorEventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
void socket_OnClose(object sender, WebSocketSharp.CloseEventArgs e)
{
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
void socket_OnMessage(object sender, WebSocketSharp.MessageEventArgs e)
{
if (OnReceive != null)
{
OnReceiveBytes(e.RawData);
}
}
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get
{
WebSocketState commonState;
if (!Enum.TryParse(WebSocket.ReadyState.ToString(), true, out commonState))
{
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.ReadyState.ToString());
}
return commonState;
}
}
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
System.Net.WebSockets.WebSocketMessageType nativeType;
if (!Enum.TryParse(type.ToString(), true, out nativeType))
{
_logger.Warn("Unrecognized WebSocketMessageType: {0}", type.ToString());
}
var completionSource = new TaskCompletionSource<bool>();
WebSocket.SendAsync(Encoding.UTF8.GetString(bytes), res => completionSource.TrySetResult(true));
return completionSource.Task;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
WebSocket.OnMessage -= socket_OnMessage;
WebSocket.OnClose -= socket_OnClose;
WebSocket.OnError -= socket_OnError;
_cancellationTokenSource.Cancel();
WebSocket.Close();
}
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
}
}

View file

@ -0,0 +1,192 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Amib.Threading;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Web;
using WebSocketSharp.Net;
using WebSocketSharp.Server;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public class WebSocketSharpListener : IHttpListener
{
private readonly ConcurrentDictionary<string, string> _localEndPoints = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private WebSocketSharp.Server.HttpServer _httpsv;
private readonly ILogger _logger;
private readonly SmartThreadPool _threadPoolManager;
public WebSocketSharpListener(ILogger logger, SmartThreadPool threadPoolManager)
{
_logger = logger;
_threadPoolManager = threadPoolManager;
}
public IEnumerable<string> LocalEndPoints
{
get { return _localEndPoints.Keys.ToList(); }
}
public System.Action<Exception, IRequest> ErrorHandler { get; set; }
public System.Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
public Action<WebSocketConnectEventArgs> WebSocketHandler { get; set; }
public void Start(IEnumerable<string> urlPrefixes)
{
_httpsv = new WebSocketSharp.Server.HttpServer(8096, false, urlPrefixes.First());
_httpsv.OnRequest += _httpsv_OnRequest;
_httpsv.Start();
}
void _httpsv_OnRequest(object sender, HttpRequestEventArgs e)
{
_threadPoolManager.QueueWorkItem(() => InitTask(e.Context));
}
private void InitTask(HttpListenerContext context)
{
try
{
var task = this.ProcessRequestAsync(context);
task.ContinueWith(x => HandleError(x.Exception, context), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.AttachedToParent);
if (task.Status == TaskStatus.Created)
{
task.RunSynchronously();
}
}
catch (Exception ex)
{
HandleError(ex, context);
}
}
private Task ProcessRequestAsync(HttpListenerContext context)
{
var request = context.Request;
LogHttpRequest(request);
if (request.IsWebSocketRequest)
{
ProcessWebSocketRequest(context);
return Task.FromResult(true);
}
if (string.IsNullOrEmpty(context.Request.RawUrl))
return ((object)null).AsTaskResult();
var httpReq = GetRequest(context);
return RequestHandler(httpReq, request.Url);
}
/// <summary>
/// Logs the HTTP request.
/// </summary>
/// <param name="request">The request.</param>
private void LogHttpRequest(HttpListenerRequest request)
{
var endpoint = request.LocalEndPoint;
if (endpoint != null)
{
var address = endpoint.ToString();
_localEndPoints.GetOrAdd(address, address);
}
LogRequest(_logger, request);
}
private void ProcessWebSocketRequest(HttpListenerContext ctx)
{
try
{
var webSocketContext = ctx.AcceptWebSocket(null, null);
if (WebSocketHandler != null)
{
WebSocketHandler(new WebSocketConnectEventArgs
{
WebSocket = new SharpWebSocket(webSocketContext.WebSocket, _logger),
Endpoint = ctx.Request.RemoteEndPoint.ToString()
});
}
}
catch (Exception ex)
{
_logger.ErrorException("AcceptWebSocketAsync error", ex);
ctx.Response.StatusCode = 500;
ctx.Response.Close();
}
}
private IHttpRequest GetRequest(HttpListenerContext httpContext)
{
var operationName = httpContext.Request.GetOperationName();
var req = new WebSocketSharpRequest(httpContext, operationName, RequestAttributes.None, _logger);
req.RequestAttributes = req.GetAttributes();
return req;
}
/// <summary>
/// Logs the request.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="request">The request.</param>
private static void LogRequest(ILogger logger, HttpListenerRequest request)
{
var log = new StringBuilder();
//var headers = string.Join(",", request.Headers.AllKeys.Where(i => !string.Equals(i, "cookie", StringComparison.OrdinalIgnoreCase) && !string.Equals(i, "Referer", StringComparison.OrdinalIgnoreCase)).Select(k => k + "=" + request.Headers[k]));
//log.AppendLine("Ip: " + request.RemoteEndPoint + ". Headers: " + headers);
var type = request.IsWebSocketRequest ? "Web Socket" : "HTTP " + request.HttpMethod;
logger.LogMultiline(type + " " + request.Url, LogSeverity.Debug, log);
}
private void HandleError(Exception ex, HttpListenerContext context)
{
var httpReq = GetRequest(context);
if (ErrorHandler != null)
{
ErrorHandler(ex, httpReq);
}
}
public void Stop()
{
_httpsv.Stop();
}
private readonly object _disposeLock = new object();
public void Dispose()
{
lock (_disposeLock)
{
if (_httpsv != null)
{
_httpsv.OnRequest -= _httpsv_OnRequest;
_httpsv.Stop();
_httpsv = null;
}
}
}
}
}

View file

@ -0,0 +1,402 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Funq;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
using ServiceStack.Web;
using WebSocketSharp.Net;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public partial class WebSocketSharpRequest : IHttpRequest
{
public Container Container { get; set; }
private readonly HttpListenerRequest request;
private readonly IHttpResponse response;
public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, RequestAttributes requestAttributes, ILogger logger)
{
this.OperationName = operationName;
this.RequestAttributes = requestAttributes;
this.request = httpContext.Request;
this.response = new WebSocketSharpResponse(logger, httpContext.Response);
this.RequestPreferences = new RequestPreferences(this);
}
public HttpListenerRequest HttpRequest
{
get { return request; }
}
public object OriginalRequest
{
get { return request; }
}
public IResponse Response
{
get { return response; }
}
public IHttpResponse HttpResponse
{
get { return response; }
}
public RequestAttributes RequestAttributes { get; set; }
public IRequestPreferences RequestPreferences { get; private set; }
public T TryResolve<T>()
{
if (typeof(T) == typeof(IHttpRequest))
throw new Exception("You don't need to use IHttpRequest.TryResolve<IHttpRequest> to resolve itself");
if (typeof(T) == typeof(IHttpResponse))
throw new Exception("Resolve IHttpResponse with 'Response' property instead of IHttpRequest.TryResolve<IHttpResponse>");
return Container == null
? HostContext.TryResolve<T>()
: Container.TryResolve<T>();
}
public string OperationName { get; set; }
public object Dto { get; set; }
public string GetRawBody()
{
if (bufferedStream != null)
{
return bufferedStream.ToArray().FromUtf8Bytes();
}
using (var reader = new StreamReader(InputStream))
{
return reader.ReadToEnd();
}
}
public string RawUrl
{
get { return request.RawUrl; }
}
public string AbsoluteUri
{
get { return request.Url.AbsoluteUri.TrimEnd('/'); }
}
public string UserHostAddress
{
get { return request.UserHostAddress; }
}
public string XForwardedFor
{
get
{
return String.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedFor]) ? null : request.Headers[HttpHeaders.XForwardedFor];
}
}
public int? XForwardedPort
{
get
{
return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedPort]) ? (int?)null : int.Parse(request.Headers[HttpHeaders.XForwardedPort]);
}
}
public string XForwardedProtocol
{
get
{
return string.IsNullOrEmpty(request.Headers[HttpHeaders.XForwardedProtocol]) ? null : request.Headers[HttpHeaders.XForwardedProtocol];
}
}
public string XRealIp
{
get
{
return String.IsNullOrEmpty(request.Headers[HttpHeaders.XRealIp]) ? null : request.Headers[HttpHeaders.XRealIp];
}
}
private string remoteIp;
public string RemoteIp
{
get
{
return remoteIp ??
(remoteIp = XForwardedFor ??
(XRealIp ??
((request.RemoteEndPoint != null) ? request.RemoteEndPoint.Address.ToString() : null)));
}
}
public bool IsSecureConnection
{
get { return request.IsSecureConnection || XForwardedProtocol == "https"; }
}
public string[] AcceptTypes
{
get { return request.AcceptTypes; }
}
private Dictionary<string, object> items;
public Dictionary<string, object> Items
{
get { return items ?? (items = new Dictionary<string, object>()); }
}
private string responseContentType;
public string ResponseContentType
{
get
{
return responseContentType
?? (responseContentType = this.GetResponseContentType());
}
set
{
this.responseContentType = value;
HasExplicitResponseContentType = true;
}
}
public bool HasExplicitResponseContentType { get; private set; }
private string pathInfo;
public string PathInfo
{
get
{
if (this.pathInfo == null)
{
var mode = HostContext.Config.HandlerFactoryPath;
var pos = request.RawUrl.IndexOf("?");
if (pos != -1)
{
var path = request.RawUrl.Substring(0, pos);
this.pathInfo = HttpRequestExtensions.GetPathInfo(
path,
mode,
mode ?? "");
}
else
{
this.pathInfo = request.RawUrl;
}
this.pathInfo = this.pathInfo.UrlDecode();
this.pathInfo = NormalizePathInfo(pathInfo, mode);
}
return this.pathInfo;
}
}
private Dictionary<string, System.Net.Cookie> cookies;
public IDictionary<string, System.Net.Cookie> Cookies
{
get
{
if (cookies == null)
{
cookies = new Dictionary<string, System.Net.Cookie>();
for (var i = 0; i < this.request.Cookies.Count; i++)
{
var httpCookie = this.request.Cookies[i];
cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain);
}
}
return cookies;
}
}
public string UserAgent
{
get { return request.UserAgent; }
}
private NameValueCollectionWrapper headers;
public INameValueCollection Headers
{
get { return headers ?? (headers = new NameValueCollectionWrapper(request.Headers)); }
}
private NameValueCollectionWrapper queryString;
public INameValueCollection QueryString
{
get { return queryString ?? (queryString = new NameValueCollectionWrapper(HttpUtility.ParseQueryString(request.Url.Query))); }
}
private NameValueCollectionWrapper formData;
public INameValueCollection FormData
{
get { return formData ?? (formData = new NameValueCollectionWrapper(this.Form)); }
}
public bool IsLocal
{
get { return request.IsLocal; }
}
private string httpMethod;
public string HttpMethod
{
get
{
return httpMethod
?? (httpMethod = Param(HttpHeaders.XHttpMethodOverride)
?? request.HttpMethod);
}
}
public string Verb
{
get { return HttpMethod; }
}
public string Param(string name)
{
return Headers[name]
?? QueryString[name]
?? FormData[name];
}
public string ContentType
{
get { return request.ContentType; }
}
public Encoding contentEncoding;
public Encoding ContentEncoding
{
get { return contentEncoding ?? request.ContentEncoding; }
set { contentEncoding = value; }
}
public Uri UrlReferrer
{
get { return request.UrlReferrer; }
}
public static Encoding GetEncoding(string contentTypeHeader)
{
var param = GetParameter(contentTypeHeader, "charset=");
if (param == null) return null;
try
{
return Encoding.GetEncoding(param);
}
catch (ArgumentException)
{
return null;
}
}
public bool UseBufferedStream
{
get { return bufferedStream != null; }
set
{
bufferedStream = value
? bufferedStream ?? new MemoryStream(request.InputStream.ReadFully())
: null;
}
}
private MemoryStream bufferedStream;
public Stream InputStream
{
get { return bufferedStream ?? request.InputStream; }
}
public long ContentLength
{
get { return request.ContentLength64; }
}
private IHttpFile[] httpFiles;
public IHttpFile[] Files
{
get
{
if (httpFiles == null)
{
if (files == null)
return httpFiles = new IHttpFile[0];
httpFiles = new IHttpFile[files.Count];
for (var i = 0; i < files.Count; i++)
{
var reqFile = files[i];
httpFiles[i] = new HttpFile
{
ContentType = reqFile.ContentType,
ContentLength = reqFile.ContentLength,
FileName = reqFile.FileName,
InputStream = reqFile.InputStream,
};
}
}
return httpFiles;
}
}
static Stream GetSubStream(Stream stream)
{
if (stream is MemoryStream)
{
var other = (MemoryStream)stream;
try
{
return new MemoryStream(other.GetBuffer(), 0, (int)other.Length, false, true);
}
catch (UnauthorizedAccessException)
{
return new MemoryStream(other.ToArray(), 0, (int)other.Length, false, true);
}
}
return stream;
}
static void EndSubStream(Stream stream)
{
}
public static string GetHandlerPathIfAny(string listenerUrl)
{
if (listenerUrl == null) return null;
var pos = listenerUrl.IndexOf("://", StringComparison.InvariantCultureIgnoreCase);
if (pos == -1) return null;
var startHostUrl = listenerUrl.Substring(pos + "://".Length);
var endPos = startHostUrl.IndexOf('/');
if (endPos == -1) return null;
var endHostUrl = startHostUrl.Substring(endPos + 1);
return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/');
}
public static string NormalizePathInfo(string pathInfo, string handlerPath)
{
if (handlerPath != null && pathInfo.TrimStart('/').StartsWith(
handlerPath, StringComparison.InvariantCultureIgnoreCase))
{
return pathInfo.TrimStart('/').Substring(handlerPath.Length);
}
return pathInfo;
}
}
}

View file

@ -0,0 +1,144 @@
using System;
using System.IO;
using System.Net;
using MediaBrowser.Model.Logging;
using ServiceStack;
using ServiceStack.Host;
using ServiceStack.Web;
using HttpListenerResponse = WebSocketSharp.Net.HttpListenerResponse;
namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
{
public class WebSocketSharpResponse : IHttpResponse
{
private readonly ILogger _logger;
private readonly HttpListenerResponse response;
public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response)
{
_logger = logger;
this.response = response;
}
public bool UseBufferedStream { get; set; }
public object OriginalResponse
{
get { return response; }
}
public int StatusCode
{
get { return this.response.StatusCode; }
set { this.response.StatusCode = value; }
}
public string StatusDescription
{
get { return this.response.StatusDescription; }
set { this.response.StatusDescription = value; }
}
public string ContentType
{
get { return response.ContentType; }
set { response.ContentType = value; }
}
public ICookies Cookies { get; set; }
public void AddHeader(string name, string value)
{
if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
{
ContentType = value;
return;
}
response.AddHeader(name, value);
}
public void Redirect(string url)
{
response.Redirect(url);
}
public Stream OutputStream
{
get { return response.OutputStream; }
}
public object Dto { get; set; }
public void Write(string text)
{
try
{
var bOutput = System.Text.Encoding.UTF8.GetBytes(text);
response.ContentLength64 = bOutput.Length;
var outputStream = response.OutputStream;
outputStream.Write(bOutput, 0, bOutput.Length);
Close();
}
catch (Exception ex)
{
_logger.ErrorException("Could not WriteTextToResponse: " + ex.Message, ex);
throw;
}
}
public void Close()
{
if (!this.IsClosed)
{
this.IsClosed = true;
try
{
this.response.CloseOutputStream(_logger);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing HttpListener output stream", ex);
}
}
}
public void End()
{
Close();
}
public void Flush()
{
response.OutputStream.Flush();
}
public bool IsClosed
{
get;
private set;
}
public void SetContentLength(long contentLength)
{
//you can happily set the Content-Length header in Asp.Net
//but HttpListener will complain if you do - you have to set ContentLength64 on the response.
//workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header
response.ContentLength64 = contentLength;
}
public void SetCookie(Cookie cookie)
{
var cookieStr = cookie.AsHeaderValue();
response.Headers.Add(HttpHeaders.SetCookie, cookieStr);
}
public bool SendChunked
{
get { return response.SendChunked; }
set { response.SendChunked = value; }
}
}
}

View file

@ -45,9 +45,6 @@
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Alchemy">
<HintPath>..\packages\Alchemy.2.2.1\lib\net40\Alchemy.dll</HintPath>
</Reference>
<Reference Include="Mono.Nat, Version=1.2.20.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Mono.Nat.1.2.20.0\lib\net40\Mono.Nat.dll</HintPath>
@ -154,9 +151,15 @@
<Compile Include="HttpServer\ServerLogFactory.cs" />
<Compile Include="HttpServer\ServerLogger.cs" />
<Compile Include="HttpServer\Security\SessionContext.cs" />
<Compile Include="HttpServer\SocketSharp\SharpWebSocket.cs" />
<Compile Include="HttpServer\StreamWriter.cs" />
<Compile Include="HttpServer\SwaggerService.cs" />
<Compile Include="Drawing\ImageProcessor.cs" />
<Compile Include="HttpServer\SocketSharp\Extensions.cs" />
<Compile Include="HttpServer\SocketSharp\RequestMono.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpListener.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpRequest.cs" />
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
<Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" />
@ -270,8 +273,6 @@
<Compile Include="Themes\AppThemeManager.cs" />
<Compile Include="Udp\UdpMessageReceivedEventArgs.cs" />
<Compile Include="Udp\UdpServer.cs" />
<Compile Include="WebSocket\AlchemyServer.cs" />
<Compile Include="WebSocket\AlchemyWebSocket.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common.Implementations\MediaBrowser.Common.Implementations.csproj">

View file

@ -45,12 +45,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
get { return _webSocketConnections; }
}
/// <summary>
/// Gets or sets the external web socket server.
/// </summary>
/// <value>The external web socket server.</value>
private IWebSocketServer ExternalWebSocketServer { get; set; }
/// <summary>
/// The _logger
/// </summary>
@ -67,22 +61,13 @@ namespace MediaBrowser.Server.Implementations.ServerManager
/// <value>The configuration manager.</value>
private IServerConfigurationManager ConfigurationManager { get; set; }
/// <summary>
/// Gets a value indicating whether [supports web socket].
/// </summary>
/// <value><c>true</c> if [supports web socket]; otherwise, <c>false</c>.</value>
public bool SupportsNativeWebSocket
{
get { return HttpServer != null && HttpServer.SupportsWebSockets; }
}
/// <summary>
/// Gets the web socket port number.
/// </summary>
/// <value>The web socket port number.</value>
public int WebSocketPortNumber
{
get { return SupportsNativeWebSocket ? ConfigurationManager.Configuration.HttpServerPortNumber : ConfigurationManager.Configuration.LegacyWebSocketPortNumber; }
get { return ConfigurationManager.Configuration.HttpServerPortNumber; }
}
/// <summary>
@ -128,27 +113,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
ReloadHttpServer(urlPrefixes);
}
public void StartWebSocketServer()
{
if (!SupportsNativeWebSocket)
{
ReloadExternalWebSocketServer(ConfigurationManager.Configuration.LegacyWebSocketPortNumber);
}
}
/// <summary>
/// Starts the external web socket server.
/// </summary>
private void ReloadExternalWebSocketServer(int portNumber)
{
DisposeExternalWebSocketServer();
ExternalWebSocketServer = _applicationHost.Resolve<IWebSocketServer>();
ExternalWebSocketServer.Start(portNumber);
ExternalWebSocketServer.WebSocketConnected += HttpServer_WebSocketConnected;
}
/// <summary>
/// Restarts the Http Server, or starts it if not currently running
/// </summary>
@ -325,8 +289,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
HttpServer.WebSocketConnected -= HttpServer_WebSocketConnected;
HttpServer.Dispose();
}
DisposeExternalWebSocketServer();
}
/// <summary>
@ -350,18 +312,6 @@ namespace MediaBrowser.Server.Implementations.ServerManager
}
}
/// <summary>
/// Disposes the external web socket server.
/// </summary>
private void DisposeExternalWebSocketServer()
{
if (ExternalWebSocketServer != null)
{
_logger.Info("Disposing {0}", ExternalWebSocketServer.GetType().Name);
ExternalWebSocketServer.Dispose();
}
}
/// <summary>
/// Adds the web socket listeners.
/// </summary>

View file

@ -1,165 +0,0 @@
using Alchemy;
using Alchemy.Classes;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using System;
using System.Net;
#if __MonoCS__
using Mono.Unix.Native;
#endif
namespace MediaBrowser.Server.Implementations.WebSocket
{
/// <summary>
/// Class AlchemyServer
/// </summary>
public class AlchemyServer : IWebSocketServer
{
/// <summary>
/// Occurs when [web socket connected].
/// </summary>
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
/// <summary>
/// Gets or sets the web socket server.
/// </summary>
/// <value>The web socket server.</value>
private WebSocketServer WebSocketServer { get; set; }
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private bool _hasStopped;
/// <summary>
/// Initializes a new instance of the <see cref="AlchemyServer" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">logger</exception>
public AlchemyServer(ILogger logger)
{
if (logger == null)
{
throw new ArgumentNullException("logger");
}
_logger = logger;
}
/// <summary>
/// Gets the port.
/// </summary>
/// <value>The port.</value>
public int Port { get; private set; }
/// <summary>
/// Starts the specified port number.
/// </summary>
/// <param name="portNumber">The port number.</param>
public void Start(int portNumber)
{
_logger.Info("Starting Alchemy web socket server on port {0}", portNumber);
try
{
WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any)
{
OnConnected = OnAlchemyWebSocketClientConnected,
TimeOut = TimeSpan.FromHours(24)
};
#if __MonoCS__
//Linux: port below 1024 require root or cap_net_bind_service
if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
{
if (Syscall.getuid() == 0)
{
WebSocketServer.FlashAccessPolicyEnabled = true;
}
else
{
WebSocketServer.FlashAccessPolicyEnabled = false;
}
}
#endif
WebSocketServer.Start();
}
catch (Exception ex)
{
_logger.ErrorException("The web socket server is unable to start on port {0} due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system.", ex, portNumber);
throw;
}
Port = portNumber;
_logger.Info("Alchemy Web Socket Server started");
}
/// <summary>
/// Called when [alchemy web socket client connected].
/// </summary>
/// <param name="context">The context.</param>
private void OnAlchemyWebSocketClientConnected(UserContext context)
{
if (_hasStopped)
{
return;
}
if (WebSocketConnected != null)
{
var socket = new AlchemyWebSocket(context, _logger);
WebSocketConnected(this, new WebSocketConnectEventArgs
{
WebSocket = socket,
Endpoint = context.ClientAddress.ToString()
});
}
}
/// <summary>
/// Stops this instance.
/// </summary>
public void Stop()
{
if (WebSocketServer != null)
{
WebSocketServer.Stop();
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private readonly object _syncLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
_hasStopped = true;
lock (_syncLock)
{
if (WebSocketServer != null)
{
_logger.Debug("Disposing alchemy server");
WebSocketServer.Dispose();
WebSocketServer = null;
}
}
}
}
}

View file

@ -1,134 +0,0 @@
using Alchemy.Classes;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Net;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.WebSocket
{
/// <summary>
/// Class AlchemyWebSocket
/// </summary>
public class AlchemyWebSocket : IWebSocket
{
/// <summary>
/// The logger
/// </summary>
private readonly ILogger _logger;
public event EventHandler<EventArgs> Closed;
/// <summary>
/// Gets or sets the web socket.
/// </summary>
/// <value>The web socket.</value>
private UserContext UserContext { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AlchemyWebSocket" /> class.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="logger">The logger.</param>
/// <exception cref="System.ArgumentNullException">context</exception>
public AlchemyWebSocket(UserContext context, ILogger logger)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
_logger = logger;
UserContext = context;
context.SetOnDisconnect(OnDisconnected);
context.SetOnReceive(OnReceiveContext);
_logger.Info("Client connected from {0}", context.ClientAddress);
}
/// <summary>
/// The _disconnected
/// </summary>
private bool _disconnected;
/// <summary>
/// Gets or sets the state.
/// </summary>
/// <value>The state.</value>
public WebSocketState State
{
get { return _disconnected ? WebSocketState.Closed : WebSocketState.Open; }
}
/// <summary>
/// Called when [disconnected].
/// </summary>
/// <param name="context">The context.</param>
private void OnDisconnected(UserContext context)
{
_disconnected = true;
EventHelper.FireEventIfNotNull(Closed, this, EventArgs.Empty, _logger);
}
/// <summary>
/// Called when [receive].
/// </summary>
/// <param name="context">The context.</param>
private void OnReceiveContext(UserContext context)
{
if (OnReceive != null)
{
var json = context.DataFrame.ToString();
OnReceive(json);
}
}
private readonly Task _cachedTask = Task.FromResult(true);
/// <summary>
/// Sends the async.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="type">The type.</param>
/// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task SendAsync(byte[] bytes, WebSocketMessageType type, bool endOfMessage, CancellationToken cancellationToken)
{
UserContext.Send(bytes);
return _cachedTask;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
}
/// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
public Action<byte[]> OnReceiveBytes { get; set; }
/// <summary>
/// Gets or sets the on receive.
/// </summary>
/// <value>The on receive.</value>
public Action<string> OnReceive { get; set; }
}
}

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Alchemy" version="2.2.1" targetFramework="net45" />
<package id="Mono.Nat" version="1.2.20.0" targetFramework="net45" />
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
<package id="System.Data.SQLite.Core" version="1.0.91.3" targetFramework="net45" />

View file

@ -72,7 +72,6 @@ using MediaBrowser.Server.Implementations.ServerManager;
using MediaBrowser.Server.Implementations.Session;
using MediaBrowser.Server.Implementations.Sync;
using MediaBrowser.Server.Implementations.Themes;
using MediaBrowser.Server.Implementations.WebSocket;
using MediaBrowser.ServerApplication.EntryPoints;
using MediaBrowser.ServerApplication.FFMpeg;
using MediaBrowser.ServerApplication.IO;
@ -517,8 +516,6 @@ namespace MediaBrowser.ServerApplication
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
RegisterSingleInstance(LocalizationManager);
RegisterSingleInstance<IWebSocketServer>(() => new AlchemyServer(Logger));
RegisterSingleInstance<IBlurayExaminer>(() => new BdInfoExaminer());
UserDataManager = new UserDataManager(LogManager);
@ -868,8 +865,6 @@ namespace MediaBrowser.ServerApplication
throw;
}
}
ServerManager.StartWebSocketServer();
}
/// <summary>
@ -885,11 +880,6 @@ namespace MediaBrowser.ServerApplication
{
NotifyPendingRestart();
}
else if (!ServerManager.SupportsNativeWebSocket && ServerManager.WebSocketPortNumber != ServerConfigurationManager.Configuration.LegacyWebSocketPortNumber)
{
NotifyPendingRestart();
}
}
/// <summary>
@ -1022,7 +1012,7 @@ namespace MediaBrowser.ServerApplication
Version = ApplicationVersion.ToString(),
IsNetworkDeployed = CanSelfUpdate,
WebSocketPortNumber = ServerManager.WebSocketPortNumber,
SupportsNativeWebSocket = ServerManager.SupportsNativeWebSocket,
SupportsNativeWebSocket = true,
FailedPluginAssemblies = FailedAssemblies.ToList(),
InProgressInstallations = InstallationManager.CurrentInstallations.Select(i => i.Item1).ToList(),
CompletedInstallations = InstallationManager.CompletedInstallations.ToList(),