mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-30 19:32:57 +01:00
a little more consolidation
This commit is contained in:
@@ -1,482 +0,0 @@
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Cache;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Networking.HttpManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Class HttpManager
|
||||
/// </summary>
|
||||
public class HttpManager : IHttpClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The _app paths
|
||||
/// </summary>
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appPaths">The kernel.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public HttpManager(IApplicationPaths appPaths, ILogger logger)
|
||||
{
|
||||
if (appPaths == null)
|
||||
{
|
||||
throw new ArgumentNullException("appPaths");
|
||||
}
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds a dictionary of http clients by host. Use GetHttpClient(host) to retrieve or create a client for web requests.
|
||||
/// DON'T dispose it after use.
|
||||
/// </summary>
|
||||
/// <value>The HTTP clients.</value>
|
||||
private readonly ConcurrentDictionary<string, HttpClient> _httpClients = new ConcurrentDictionary<string, HttpClient>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets
|
||||
/// </summary>
|
||||
/// <param name="host">The host.</param>
|
||||
/// <returns>HttpClient.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">host</exception>
|
||||
private HttpClient GetHttpClient(string host)
|
||||
{
|
||||
if (string.IsNullOrEmpty(host))
|
||||
{
|
||||
throw new ArgumentNullException("host");
|
||||
}
|
||||
|
||||
HttpClient client;
|
||||
if (!_httpClients.TryGetValue(host, out client))
|
||||
{
|
||||
var handler = new WebRequestHandler
|
||||
{
|
||||
AutomaticDecompression = DecompressionMethods.Deflate,
|
||||
CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate)
|
||||
};
|
||||
|
||||
client = new HttpClient(handler);
|
||||
client.DefaultRequestHeaders.Add("Accept", "application/json,image/*");
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
_httpClients.TryAdd(host, client);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a GET request and returns the resulting stream
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{Stream}.</returns>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
public async Task<Stream> Get(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateParams(url, resourcePool, cancellationToken);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("HttpManager.Get url: {0}", url);
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var msg = await GetHttpClient(GetHostFromUrl(url)).GetAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
EnsureSuccessStatusCode(msg);
|
||||
|
||||
return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
throw GetCancellationException(url, cancellationToken, ex);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
throw new HttpException(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a POST request
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="postData">Params to add to the POST data.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>stream on success, null on failure</returns>
|
||||
/// <exception cref="System.ArgumentNullException">postData</exception>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
public async Task<Stream> Post(string url, Dictionary<string, string> postData, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateParams(url, resourcePool, cancellationToken);
|
||||
|
||||
if (postData == null)
|
||||
{
|
||||
throw new ArgumentNullException("postData");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var strings = postData.Keys.Select(key => string.Format("{0}={1}", key, postData[key]));
|
||||
var postContent = string.Join("&", strings.ToArray());
|
||||
var content = new StringContent(postContent, Encoding.UTF8, "application/x-www-form-urlencoded");
|
||||
|
||||
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("HttpManager.Post url: {0}", url);
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var msg = await GetHttpClient(GetHostFromUrl(url)).PostAsync(url, content, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
EnsureSuccessStatusCode(msg);
|
||||
|
||||
return await msg.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
throw GetCancellationException(url, cancellationToken, ex);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
throw new HttpException(ex.Message, ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the contents of a given url into a temporary location
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="userAgent">The user agent.</param>
|
||||
/// <returns>Task{System.String}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">progress</exception>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
public async Task<string> GetTempFile(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken, IProgress<double> progress, string userAgent = null)
|
||||
{
|
||||
ValidateParams(url, resourcePool, cancellationToken);
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
throw new ArgumentNullException("progress");
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var tempFile = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid() + ".tmp");
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
if (!string.IsNullOrEmpty(userAgent))
|
||||
{
|
||||
message.Headers.Add("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("HttpManager.GetTempFile url: {0}, temp file: {1}", url, tempFile);
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var response = await GetHttpClient(GetHostFromUrl(url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
EnsureSuccessStatusCode(response);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
IEnumerable<string> lengthValues;
|
||||
|
||||
if (!response.Headers.TryGetValues("content-length", out lengthValues) &&
|
||||
!response.Content.Headers.TryGetValues("content-length", out lengthValues))
|
||||
{
|
||||
// We're not able to track progress
|
||||
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
|
||||
{
|
||||
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var length = long.Parse(string.Join(string.Empty, lengthValues.ToArray()));
|
||||
|
||||
using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), progress.Report, length))
|
||||
{
|
||||
using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
|
||||
{
|
||||
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
// Cleanup
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
throw GetCancellationException(url, cancellationToken, ex);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
// Cleanup
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
throw new HttpException(ex.Message, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
// Cleanup
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the contents of a given url into a MemoryStream
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{MemoryStream}.</returns>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
public async Task<MemoryStream> GetMemoryStream(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateParams(url, resourcePool, cancellationToken);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
|
||||
_logger.Info("HttpManager.GetMemoryStream url: {0}", url);
|
||||
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var response = await GetHttpClient(GetHostFromUrl(url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
EnsureSuccessStatusCode(response);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
|
||||
{
|
||||
await stream.CopyToAsync(ms, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
ms.Dispose();
|
||||
|
||||
throw GetCancellationException(url, cancellationToken, ex);
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
ms.Dispose();
|
||||
|
||||
throw new HttpException(ex.Message, ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting response from " + url, ex);
|
||||
|
||||
ms.Dispose();
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
resourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the params.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="resourcePool">The resource pool.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="System.ArgumentNullException">url</exception>
|
||||
private void ValidateParams(string url, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentNullException("url");
|
||||
}
|
||||
|
||||
if (resourcePool == null)
|
||||
{
|
||||
throw new ArgumentNullException("resourcePool");
|
||||
}
|
||||
|
||||
if (cancellationToken == null)
|
||||
{
|
||||
throw new ArgumentNullException("cancellationToken");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the host from URL.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetHostFromUrl(string url)
|
||||
{
|
||||
var start = url.IndexOf("://", StringComparison.OrdinalIgnoreCase) + 3;
|
||||
var len = url.IndexOf('/', start) - start;
|
||||
return url.Substring(start, len);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
foreach (var client in _httpClients.Values.ToList())
|
||||
{
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
_httpClients.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws the cancellation exception.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="exception">The exception.</param>
|
||||
/// <returns>Exception.</returns>
|
||||
private Exception GetCancellationException(string url, CancellationToken cancellationToken, OperationCanceledException exception)
|
||||
{
|
||||
// If the HttpClient's timeout is reached, it will cancel the Task internally
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var msg = string.Format("Connection to {0} timed out", url);
|
||||
|
||||
_logger.Error(msg);
|
||||
|
||||
// Throw an HttpException so that the caller doesn't think it was cancelled by user code
|
||||
return new HttpException(msg, exception) { IsTimedOut = true };
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the success status code.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
/// <exception cref="MediaBrowser.Model.Net.HttpException"></exception>
|
||||
private void EnsureSuccessStatusCode(HttpResponseMessage response)
|
||||
{
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new HttpException(response.ReasonPhrase) { StatusCode = response.StatusCode };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,453 +0,0 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using ServiceStack.Common;
|
||||
using ServiceStack.Common.Web;
|
||||
using ServiceStack.ServiceHost;
|
||||
using ServiceStack.ServiceInterface;
|
||||
using ServiceStack.WebHost.Endpoints;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using MimeTypes = MediaBrowser.Common.Net.MimeTypes;
|
||||
using StreamWriter = MediaBrowser.Common.Net.StreamWriter;
|
||||
|
||||
namespace MediaBrowser.Networking.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class BaseRestService
|
||||
/// </summary>
|
||||
public class BaseRestService : Service, IRestfulService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the kernel.
|
||||
/// </summary>
|
||||
/// <value>The kernel.</value>
|
||||
public IKernel Kernel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is range request.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
|
||||
protected bool IsRangeRequest
|
||||
{
|
||||
get
|
||||
{
|
||||
return Request.Headers.AllKeys.Contains("Range");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the optimized result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">result</exception>
|
||||
protected object ToOptimizedResult<T>(T result)
|
||||
where T : class
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException("result");
|
||||
}
|
||||
|
||||
Response.AddHeader("Vary", "Accept-Encoding");
|
||||
|
||||
return RequestContext.ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the optimized result using cache.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToOptimizedResultUsingCache<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn)
|
||||
where T : class
|
||||
{
|
||||
if (cacheKey == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("cacheKey");
|
||||
}
|
||||
if (factoryFn == null)
|
||||
{
|
||||
throw new ArgumentNullException("factoryFn");
|
||||
}
|
||||
|
||||
var key = cacheKey.ToString("N");
|
||||
|
||||
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, string.Empty);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return ToOptimizedResult(factoryFn());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the cached result.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToCachedResult<T>(Guid cacheKey, DateTime lastDateModified, TimeSpan? cacheDuration, Func<T> factoryFn, string contentType)
|
||||
where T : class
|
||||
{
|
||||
if (cacheKey == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("cacheKey");
|
||||
}
|
||||
if (factoryFn == null)
|
||||
{
|
||||
throw new ArgumentNullException("factoryFn");
|
||||
}
|
||||
|
||||
var key = cacheKey.ToString("N");
|
||||
|
||||
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return factoryFn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static file result.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">path</exception>
|
||||
protected object ToStaticFileResult(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
var dateModified = File.GetLastWriteTimeUtc(path);
|
||||
|
||||
var cacheKey = path + dateModified.Ticks;
|
||||
|
||||
return ToStaticResult(cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file stream.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>Stream.</returns>
|
||||
private Stream GetFileStream(string path)
|
||||
{
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static result.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">cacheKey</exception>
|
||||
protected object ToStaticResult(Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType, Func<Task<Stream>> factoryFn)
|
||||
{
|
||||
if (cacheKey == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("cacheKey");
|
||||
}
|
||||
if (factoryFn == null)
|
||||
{
|
||||
throw new ArgumentNullException("factoryFn");
|
||||
}
|
||||
|
||||
var key = cacheKey.ToString("N");
|
||||
|
||||
var result = PreProcessCachedResult(cacheKey, key, lastDateModified, cacheDuration, contentType);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var compress = ShouldCompressResponse(contentType);
|
||||
|
||||
if (compress)
|
||||
{
|
||||
Response.AddHeader("Vary", "Accept-Encoding");
|
||||
}
|
||||
|
||||
return ToStaticResult(contentType, factoryFn, compress).Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shoulds the compress response.
|
||||
/// </summary>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool ShouldCompressResponse(string contentType)
|
||||
{
|
||||
// It will take some work to support compression with byte range requests
|
||||
if (IsRangeRequest)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't compress media
|
||||
if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't compress images
|
||||
if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the static result.
|
||||
/// </summary>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <param name="factoryFn">The factory fn.</param>
|
||||
/// <param name="compress">if set to <c>true</c> [compress].</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
private async Task<object> ToStaticResult(string contentType, Func<Task<Stream>> factoryFn, bool compress)
|
||||
{
|
||||
if (!compress || string.IsNullOrEmpty(RequestContext.CompressionType))
|
||||
{
|
||||
Response.ContentType = contentType;
|
||||
|
||||
var stream = await factoryFn().ConfigureAwait(false);
|
||||
|
||||
return new StreamWriter(stream);
|
||||
}
|
||||
|
||||
string content;
|
||||
|
||||
using (var stream = await factoryFn().ConfigureAwait(false))
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
content = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var contents = content.Compress(RequestContext.CompressionType);
|
||||
|
||||
return new CompressedResult(contents, RequestContext.CompressionType, contentType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pres the process optimized result.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="cacheKeyString">The cache key string.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="contentType">Type of the content.</param>
|
||||
/// <returns>System.Object.</returns>
|
||||
private object PreProcessCachedResult(Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType)
|
||||
{
|
||||
Response.AddHeader("ETag", cacheKeyString);
|
||||
|
||||
if (IsNotModified(cacheKey, lastDateModified, cacheDuration))
|
||||
{
|
||||
AddAgeHeader(lastDateModified);
|
||||
AddExpiresHeader(cacheKeyString, cacheDuration);
|
||||
//ctx.Response.SendChunked = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
Response.ContentType = contentType;
|
||||
}
|
||||
|
||||
return new HttpResult(new byte[] { }, HttpStatusCode.NotModified);
|
||||
}
|
||||
|
||||
SetCachingHeaders(cacheKeyString, lastDateModified, cacheDuration);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified cache key].
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified cache key]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsNotModified(Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
|
||||
{
|
||||
var isNotModified = true;
|
||||
|
||||
if (Request.Headers.AllKeys.Contains("If-Modified-Since"))
|
||||
{
|
||||
DateTime ifModifiedSince;
|
||||
|
||||
if (DateTime.TryParse(Request.Headers["If-Modified-Since"], out ifModifiedSince))
|
||||
{
|
||||
isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate If-None-Match
|
||||
if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(Request.Headers["If-None-Match"])))
|
||||
{
|
||||
Guid ifNoneMatch;
|
||||
|
||||
if (Guid.TryParse(Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch))
|
||||
{
|
||||
if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is not modified] [the specified if modified since].
|
||||
/// </summary>
|
||||
/// <param name="ifModifiedSince">If modified since.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
/// <param name="dateModified">The date modified.</param>
|
||||
/// <returns><c>true</c> if [is not modified] [the specified if modified since]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified)
|
||||
{
|
||||
if (dateModified.HasValue)
|
||||
{
|
||||
var lastModified = NormalizeDateForComparison(dateModified.Value);
|
||||
ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
|
||||
|
||||
return lastModified <= ifModifiedSince;
|
||||
}
|
||||
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value);
|
||||
|
||||
if (DateTime.UtcNow < cacheExpirationDate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
private DateTime NormalizeDateForComparison(DateTime date)
|
||||
{
|
||||
return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the caching headers.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
private void SetCachingHeaders(string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration)
|
||||
{
|
||||
// Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
|
||||
// https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
|
||||
if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue))
|
||||
{
|
||||
AddAgeHeader(lastDateModified);
|
||||
Response.AddHeader("LastModified", lastDateModified.Value.ToString("r"));
|
||||
}
|
||||
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
Response.AddHeader("Cache-Control", "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds));
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
Response.AddHeader("Cache-Control", "public");
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.AddHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
Response.AddHeader("pragma", "no-cache, no-store, must-revalidate");
|
||||
}
|
||||
|
||||
AddExpiresHeader(cacheKey, cacheDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the expires header.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key.</param>
|
||||
/// <param name="cacheDuration">Duration of the cache.</param>
|
||||
private void AddExpiresHeader(string cacheKey, TimeSpan? cacheDuration)
|
||||
{
|
||||
if (cacheDuration.HasValue)
|
||||
{
|
||||
Response.AddHeader("Expires", DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"));
|
||||
}
|
||||
else if (string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
Response.AddHeader("Expires", "-1");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the age header.
|
||||
/// </summary>
|
||||
/// <param name="lastDateModified">The last date modified.</param>
|
||||
private void AddAgeHeader(DateTime? lastDateModified)
|
||||
{
|
||||
if (lastDateModified.HasValue)
|
||||
{
|
||||
Response.AddHeader("Age", Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
using Funq;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using ServiceStack.Api.Swagger;
|
||||
using ServiceStack.Common.Web;
|
||||
using ServiceStack.Configuration;
|
||||
using ServiceStack.Logging.NLogger;
|
||||
using ServiceStack.ServiceHost;
|
||||
using ServiceStack.ServiceInterface.Cors;
|
||||
using ServiceStack.Text;
|
||||
using ServiceStack.WebHost.Endpoints;
|
||||
using ServiceStack.WebHost.Endpoints.Extensions;
|
||||
using ServiceStack.WebHost.Endpoints.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Networking.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class HttpServer
|
||||
/// </summary>
|
||||
public class HttpServer : HttpListenerBase, IHttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL prefix.
|
||||
/// </summary>
|
||||
/// <value>The URL prefix.</value>
|
||||
public string UrlPrefix { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The _rest services
|
||||
/// </summary>
|
||||
private readonly List<IRestfulService> _restServices = new List<IRestfulService>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application host.
|
||||
/// </summary>
|
||||
/// <value>The application host.</value>
|
||||
private IApplicationHost ApplicationHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This subscribes to HttpListener requests and finds the appropriate BaseHandler to process it
|
||||
/// </summary>
|
||||
/// <value>The HTTP listener.</value>
|
||||
private IDisposable HttpListener { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the protobuf serializer.
|
||||
/// </summary>
|
||||
/// <value>The protobuf serializer.</value>
|
||||
private IProtobufSerializer ProtobufSerializer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [web socket connected].
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default redirect path.
|
||||
/// </summary>
|
||||
/// <value>The default redirect path.</value>
|
||||
private string DefaultRedirectPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the server.
|
||||
/// </summary>
|
||||
/// <value>The name of the server.</value>
|
||||
private string ServerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HttpServer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="applicationHost">The application host.</param>
|
||||
/// <param name="protobufSerializer">The protobuf serializer.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="serverName">Name of the server.</param>
|
||||
/// <param name="defaultRedirectpath">The default redirectpath.</param>
|
||||
/// <exception cref="System.ArgumentNullException">urlPrefix</exception>
|
||||
public HttpServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath)
|
||||
: base()
|
||||
{
|
||||
if (protobufSerializer == null)
|
||||
{
|
||||
throw new ArgumentNullException("protobufSerializer");
|
||||
}
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
if (applicationHost == null)
|
||||
{
|
||||
throw new ArgumentNullException("applicationHost");
|
||||
}
|
||||
if (string.IsNullOrEmpty(serverName))
|
||||
{
|
||||
throw new ArgumentNullException("serverName");
|
||||
}
|
||||
if (string.IsNullOrEmpty(defaultRedirectpath))
|
||||
{
|
||||
throw new ArgumentNullException("defaultRedirectpath");
|
||||
}
|
||||
|
||||
ServerName = serverName;
|
||||
DefaultRedirectPath = defaultRedirectpath;
|
||||
ProtobufSerializer = protobufSerializer;
|
||||
_logger = logger;
|
||||
ApplicationHost = applicationHost;
|
||||
|
||||
EndpointHostConfig.Instance.ServiceStackHandlerFactoryPath = null;
|
||||
EndpointHostConfig.Instance.MetadataRedirectPath = "metadata";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the specified container.
|
||||
/// </summary>
|
||||
/// <param name="container">The container.</param>
|
||||
public override void Configure(Container container)
|
||||
{
|
||||
JsConfig.DateHandler = JsonDateHandler.ISO8601;
|
||||
JsConfig.ExcludeTypeInfo = true;
|
||||
JsConfig.IncludeNullValues = false;
|
||||
|
||||
SetConfig(new EndpointHostConfig
|
||||
{
|
||||
DefaultRedirectPath = DefaultRedirectPath,
|
||||
|
||||
// Tell SS to bubble exceptions up to here
|
||||
WriteErrorsToResponse = false,
|
||||
|
||||
DebugMode = true
|
||||
});
|
||||
|
||||
container.Adapter = new ContainerAdapter(ApplicationHost);
|
||||
|
||||
Plugins.Add(new SwaggerFeature());
|
||||
Plugins.Add(new CorsFeature());
|
||||
|
||||
ServiceStack.Logging.LogManager.LogFactory = new NLogFactory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the Web Service
|
||||
/// </summary>
|
||||
/// <param name="urlBase">A Uri that acts as the base that the server is listening on.
|
||||
/// Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/
|
||||
/// Note: the trailing slash is required! For more info see the
|
||||
/// HttpListener.Prefixes property on MSDN.</param>
|
||||
public override void Start(string urlBase)
|
||||
{
|
||||
if (string.IsNullOrEmpty(urlBase))
|
||||
{
|
||||
throw new ArgumentNullException("urlBase");
|
||||
}
|
||||
|
||||
// *** Already running - just leave it in place
|
||||
if (IsStarted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Listener == null)
|
||||
{
|
||||
Listener = new HttpListener();
|
||||
}
|
||||
|
||||
EndpointHost.Config.ServiceStackHandlerFactoryPath = HttpListenerRequestWrapper.GetHandlerPathIfAny(urlBase);
|
||||
|
||||
UrlPrefix = urlBase;
|
||||
|
||||
Listener.Prefixes.Add(urlBase);
|
||||
|
||||
IsStarted = true;
|
||||
Listener.Start();
|
||||
|
||||
HttpListener = CreateObservableStream().Subscribe(ProcessHttpRequestAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the observable stream.
|
||||
/// </summary>
|
||||
/// <returns>IObservable{HttpListenerContext}.</returns>
|
||||
private IObservable<HttpListenerContext> CreateObservableStream()
|
||||
{
|
||||
return Observable.Create<HttpListenerContext>(obs =>
|
||||
Observable.FromAsync(() => Listener.GetContextAsync())
|
||||
.Subscribe(obs))
|
||||
.Repeat()
|
||||
.Retry()
|
||||
.Publish()
|
||||
.RefCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes incoming http requests by routing them to the appropiate handler
|
||||
/// </summary>
|
||||
/// <param name="context">The CTX.</param>
|
||||
private async void ProcessHttpRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
LogHttpRequest(context);
|
||||
|
||||
if (context.Request.IsWebSocketRequest)
|
||||
{
|
||||
await ProcessWebSocketRequest(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
RaiseReceiveWebRequest(context);
|
||||
|
||||
try
|
||||
{
|
||||
ProcessRequest(context);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 422);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (ResourceNotFoundException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 404);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 404);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (DirectoryNotFoundException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 404);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 401);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 400);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleException(context.Response, ex, 500);
|
||||
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the web socket request.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The CTX.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ProcessWebSocketRequest(HttpListenerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false);
|
||||
|
||||
if (WebSocketConnected != null)
|
||||
{
|
||||
WebSocketConnected(this, new WebSocketConnectEventArgs { WebSocket = new NativeWebSocket(webSocketContext.WebSocket, _logger), Endpoint = ctx.Request.RemoteEndPoint.ToString() });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("AcceptWebSocketAsync error", ex);
|
||||
|
||||
ctx.Response.StatusCode = 500;
|
||||
ctx.Response.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The CTX.</param>
|
||||
private void LogHttpRequest(HttpListenerContext ctx)
|
||||
{
|
||||
var log = new StringBuilder();
|
||||
|
||||
log.AppendLine("Url: " + ctx.Request.Url);
|
||||
log.AppendLine("Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
|
||||
|
||||
var type = ctx.Request.IsWebSocketRequest ? "Web Socket" : "HTTP " + ctx.Request.HttpMethod;
|
||||
|
||||
if (EnableHttpRequestLogging)
|
||||
{
|
||||
_logger.LogMultiline(type + " request received from " + ctx.Request.RemoteEndPoint, LogSeverity.Debug, log);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the error message.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
/// <param name="ex">The ex.</param>
|
||||
/// <param name="statusCode">The status code.</param>
|
||||
private void HandleException(HttpListenerResponse response, Exception ex, int statusCode)
|
||||
{
|
||||
_logger.ErrorException("Error processing request", ex);
|
||||
|
||||
response.StatusCode = statusCode;
|
||||
|
||||
response.Headers.Add("Status", statusCode.ToString(new CultureInfo("en-US")));
|
||||
|
||||
response.Headers.Remove("Age");
|
||||
response.Headers.Remove("Expires");
|
||||
response.Headers.Remove("Cache-Control");
|
||||
response.Headers.Remove("Etag");
|
||||
response.Headers.Remove("Last-Modified");
|
||||
|
||||
response.ContentType = "text/plain";
|
||||
|
||||
if (!string.IsNullOrEmpty(ex.Message))
|
||||
{
|
||||
response.AddHeader("X-Application-Error-Code", ex.Message);
|
||||
}
|
||||
|
||||
// This could fail, but try to add the stack trace as the body content
|
||||
try
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("{");
|
||||
sb.AppendLine("\"ResponseStatus\":{");
|
||||
sb.AppendFormat(" \"ErrorCode\":{0},\n", ex.GetType().Name.EncodeJson());
|
||||
sb.AppendFormat(" \"Message\":{0},\n", ex.Message.EncodeJson());
|
||||
sb.AppendFormat(" \"StackTrace\":{0}\n", ex.StackTrace.EncodeJson());
|
||||
sb.AppendLine("}");
|
||||
sb.AppendLine("}");
|
||||
|
||||
response.StatusCode = 500;
|
||||
response.ContentType = ContentType.Json;
|
||||
var sbBytes = sb.ToString().ToUtf8Bytes();
|
||||
response.OutputStream.Write(sbBytes, 0, sbBytes.Length);
|
||||
response.Close();
|
||||
}
|
||||
catch (Exception errorEx)
|
||||
{
|
||||
_logger.ErrorException("Error processing failed request", errorEx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Overridable method that can be used to implement a custom hnandler
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
/// <exception cref="System.NotImplementedException">Cannot execute handler: + handler + at PathInfo: + httpReq.PathInfo</exception>
|
||||
protected override void ProcessRequest(HttpListenerContext context)
|
||||
{
|
||||
if (string.IsNullOrEmpty(context.Request.RawUrl)) return;
|
||||
|
||||
var operationName = context.Request.GetOperationName();
|
||||
|
||||
var httpReq = new HttpListenerRequestWrapper(operationName, context.Request);
|
||||
var httpRes = new HttpListenerResponseWrapper(context.Response);
|
||||
var handler = ServiceStackHttpHandlerFactory.GetHandler(httpReq);
|
||||
|
||||
var serviceStackHandler = handler as IServiceStackHttpHandler;
|
||||
|
||||
if (serviceStackHandler != null)
|
||||
{
|
||||
var restHandler = serviceStackHandler as RestHandler;
|
||||
if (restHandler != null)
|
||||
{
|
||||
httpReq.OperationName = operationName = restHandler.RestPath.RequestType.Name;
|
||||
}
|
||||
serviceStackHandler.ProcessRequest(httpReq, httpRes, operationName);
|
||||
LogResponse(context);
|
||||
httpRes.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Cannot execute handler: " + handler + " at PathInfo: " + httpReq.PathInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the response.
|
||||
/// </summary>
|
||||
/// <param name="ctx">The CTX.</param>
|
||||
private void LogResponse(HttpListenerContext ctx)
|
||||
{
|
||||
var statusode = ctx.Response.StatusCode;
|
||||
|
||||
var log = new StringBuilder();
|
||||
|
||||
log.AppendLine(string.Format("Url: {0}", ctx.Request.Url));
|
||||
|
||||
log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
|
||||
|
||||
var msg = "Http Response Sent (" + statusode + ") to " + ctx.Request.RemoteEndPoint;
|
||||
|
||||
if (EnableHttpRequestLogging)
|
||||
{
|
||||
_logger.LogMultiline(msg, LogSeverity.Debug, log);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the service manager.
|
||||
/// </summary>
|
||||
/// <param name="assembliesWithServices">The assemblies with services.</param>
|
||||
/// <returns>ServiceManager.</returns>
|
||||
protected override ServiceManager CreateServiceManager(params Assembly[] assembliesWithServices)
|
||||
{
|
||||
var types = _restServices.Select(r => r.GetType()).ToArray();
|
||||
|
||||
return new ServiceManager(new Container(), new ServiceController(() => types));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shut down the Web Service
|
||||
/// </summary>
|
||||
public override void Stop()
|
||||
{
|
||||
if (HttpListener != null)
|
||||
{
|
||||
HttpListener.Dispose();
|
||||
HttpListener = null;
|
||||
}
|
||||
|
||||
if (Listener != null)
|
||||
{
|
||||
Listener.Prefixes.Remove(UrlPrefix);
|
||||
}
|
||||
|
||||
base.Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _supports native web socket
|
||||
/// </summary>
|
||||
private bool? _supportsNativeWebSocket;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether [supports web sockets].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [supports web sockets]; otherwise, <c>false</c>.</value>
|
||||
public bool SupportsWebSockets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_supportsNativeWebSocket.HasValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
new ClientWebSocket();
|
||||
|
||||
_supportsNativeWebSocket = true;
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
_supportsNativeWebSocket = false;
|
||||
}
|
||||
}
|
||||
|
||||
return _supportsNativeWebSocket.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [enable HTTP request logging].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [enable HTTP request logging]; otherwise, <c>false</c>.</value>
|
||||
public bool EnableHttpRequestLogging { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds the rest handlers.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
public void Init(IEnumerable<IRestfulService> services)
|
||||
{
|
||||
_restServices.AddRange(services);
|
||||
|
||||
EndpointHost.ConfigureHost(this, ServerName, CreateServiceManager());
|
||||
ContentTypeFilters.Register(ContentType.ProtoBuf, (reqCtx, res, stream) => ProtobufSerializer.SerializeToStream(res, stream), (type, stream) => ProtobufSerializer.DeserializeFromStream(stream, type));
|
||||
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class ContainerAdapter
|
||||
/// </summary>
|
||||
class ContainerAdapter : IContainerAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// The _app host
|
||||
/// </summary>
|
||||
private readonly IApplicationHost _appHost;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContainerAdapter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The app host.</param>
|
||||
public ContainerAdapter(IApplicationHost appHost)
|
||||
{
|
||||
_appHost = appHost;
|
||||
}
|
||||
/// <summary>
|
||||
/// Resolves this instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T Resolve<T>()
|
||||
{
|
||||
return _appHost.Resolve<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the resolve.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns>``0.</returns>
|
||||
public T TryResolve<T>()
|
||||
{
|
||||
return _appHost.TryResolve<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebSocketMessageType = MediaBrowser.Common.Net.WebSocketMessageType;
|
||||
using WebSocketState = MediaBrowser.Common.Net.WebSocketState;
|
||||
|
||||
namespace MediaBrowser.Networking.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class NativeWebSocket
|
||||
/// </summary>
|
||||
public class NativeWebSocket : IWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the web socket.
|
||||
/// </summary>
|
||||
/// <value>The web socket.</value>
|
||||
private System.Net.WebSockets.WebSocket WebSocket { get; set; }
|
||||
|
||||
/// <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 NativeWebSocket(System.Net.WebSockets.WebSocket socket, ILogger logger)
|
||||
{
|
||||
if (socket == null)
|
||||
{
|
||||
throw new ArgumentNullException("socket");
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
|
||||
_logger = logger;
|
||||
WebSocket = socket;
|
||||
|
||||
Receive();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the state.
|
||||
/// </summary>
|
||||
/// <value>The state.</value>
|
||||
public WebSocketState State
|
||||
{
|
||||
get
|
||||
{
|
||||
WebSocketState commonState;
|
||||
|
||||
if (!Enum.TryParse(WebSocket.State.ToString(), true, out commonState))
|
||||
{
|
||||
_logger.Warn("Unrecognized WebSocketState: {0}", WebSocket.State.ToString());
|
||||
}
|
||||
|
||||
return commonState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives this instance.
|
||||
/// </summary>
|
||||
private async void Receive()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
byte[] bytes;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = await ReceiveBytesAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (WebSocketException ex)
|
||||
{
|
||||
_logger.ErrorException("Error reveiving web socket message", ex);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (OnReceiveDelegate != null)
|
||||
{
|
||||
OnReceiveDelegate(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives the async.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{WebSocketMessageInfo}.</returns>
|
||||
/// <exception cref="System.Net.WebSockets.WebSocketException">Connection closed</exception>
|
||||
private async Task<byte[]> ReceiveBytesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var bytes = new byte[4096];
|
||||
var buffer = new ArraySegment<byte>(bytes);
|
||||
|
||||
var result = await WebSocket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (result.CloseStatus.HasValue)
|
||||
{
|
||||
throw new WebSocketException("Connection closed");
|
||||
}
|
||||
|
||||
return buffer.Array;
|
||||
}
|
||||
|
||||
/// <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());
|
||||
}
|
||||
|
||||
return WebSocket.SendAsync(new ArraySegment<byte>(bytes), nativeType, true, cancellationToken);
|
||||
}
|
||||
|
||||
/// <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.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the receive action.
|
||||
/// </summary>
|
||||
/// <value>The receive action.</value>
|
||||
public Action<byte[]> OnReceiveDelegate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using MediaBrowser.Common.Kernel;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
|
||||
namespace MediaBrowser.Networking.HttpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ServerFactory
|
||||
/// </summary>
|
||||
public static class ServerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the server.
|
||||
/// </summary>
|
||||
/// <param name="applicationHost">The application host.</param>
|
||||
/// <param name="protobufSerializer">The protobuf serializer.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="serverName">Name of the server.</param>
|
||||
/// <param name="defaultRedirectpath">The default redirectpath.</param>
|
||||
/// <returns>IHttpServer.</returns>
|
||||
public static IHttpServer CreateServer(IApplicationHost applicationHost, IProtobufSerializer protobufSerializer, ILogger logger, string serverName, string defaultRedirectpath)
|
||||
{
|
||||
return new HttpServer(applicationHost, protobufSerializer, logger, serverName, defaultRedirectpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace MediaBrowser.Networking.Management
|
||||
{
|
||||
/// <summary>
|
||||
/// Class NativeMethods
|
||||
/// </summary>
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
public static class NativeMethods
|
||||
{
|
||||
//declare the Netapi32 : NetServerEnum method import
|
||||
/// <summary>
|
||||
/// Nets the server enum.
|
||||
/// </summary>
|
||||
/// <param name="ServerName">Name of the server.</param>
|
||||
/// <param name="dwLevel">The dw level.</param>
|
||||
/// <param name="pBuf">The p buf.</param>
|
||||
/// <param name="dwPrefMaxLen">The dw pref max len.</param>
|
||||
/// <param name="dwEntriesRead">The dw entries read.</param>
|
||||
/// <param name="dwTotalEntries">The dw total entries.</param>
|
||||
/// <param name="dwServerType">Type of the dw server.</param>
|
||||
/// <param name="domain">The domain.</param>
|
||||
/// <param name="dwResumeHandle">The dw resume handle.</param>
|
||||
/// <returns>System.Int32.</returns>
|
||||
[DllImport("Netapi32", CharSet = CharSet.Auto, SetLastError = true),
|
||||
SuppressUnmanagedCodeSecurityAttribute]
|
||||
|
||||
public static extern int NetServerEnum(
|
||||
string ServerName, // must be null
|
||||
int dwLevel,
|
||||
ref IntPtr pBuf,
|
||||
int dwPrefMaxLen,
|
||||
out int dwEntriesRead,
|
||||
out int dwTotalEntries,
|
||||
int dwServerType,
|
||||
string domain, // null for login domain
|
||||
out int dwResumeHandle
|
||||
);
|
||||
|
||||
//declare the Netapi32 : NetApiBufferFree method import
|
||||
/// <summary>
|
||||
/// Nets the API buffer free.
|
||||
/// </summary>
|
||||
/// <param name="pBuf">The p buf.</param>
|
||||
/// <returns>System.Int32.</returns>
|
||||
[DllImport("Netapi32", SetLastError = true),
|
||||
SuppressUnmanagedCodeSecurityAttribute]
|
||||
|
||||
public static extern int NetApiBufferFree(
|
||||
IntPtr pBuf);
|
||||
}
|
||||
|
||||
//create a _SERVER_INFO_100 STRUCTURE
|
||||
/// <summary>
|
||||
/// Struct _SERVER_INFO_100
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct _SERVER_INFO_100
|
||||
{
|
||||
/// <summary>
|
||||
/// The sv100_platform_id
|
||||
/// </summary>
|
||||
internal int sv100_platform_id;
|
||||
/// <summary>
|
||||
/// The sv100_name
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
internal string sv100_name;
|
||||
}
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MediaBrowser.Networking.Management
|
||||
{
|
||||
/// <summary>
|
||||
/// Class NetUtils
|
||||
/// </summary>
|
||||
public class NetworkManager : INetworkManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the machine's local ip address
|
||||
/// </summary>
|
||||
/// <returns>IPAddress.</returns>
|
||||
public string GetLocalIpAddress()
|
||||
{
|
||||
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
|
||||
var ip = host.AddressList.FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||
|
||||
if (ip == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ip.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a random port number that is currently available
|
||||
/// </summary>
|
||||
/// <returns>System.Int32.</returns>
|
||||
public int GetRandomUnusedPort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Any, 0);
|
||||
listener.Start();
|
||||
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
return port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the netsh URL registration.
|
||||
/// </summary>
|
||||
public void AuthorizeHttpListening(string url)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "netsh",
|
||||
Arguments = string.Format("http add urlacl url={0} user=\"NT AUTHORITY\\Authenticated Users\"", url),
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
Verb = "runas",
|
||||
ErrorDialog = false
|
||||
};
|
||||
|
||||
using (var process = Process.Start(startInfo))
|
||||
{
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the windows firewall rule.
|
||||
/// </summary>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
public void AddSystemFirewallRule(int port, NetworkProtocol protocol)
|
||||
{
|
||||
// First try to remove it so we don't end up creating duplicates
|
||||
RemoveSystemFirewallRule(port, protocol);
|
||||
|
||||
var args = string.Format("advfirewall firewall add rule name=\"Port {0}\" dir=in action=allow protocol={1} localport={0}", port, protocol);
|
||||
|
||||
RunNetsh(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the windows firewall rule.
|
||||
/// </summary>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
public void RemoveSystemFirewallRule(int port, NetworkProtocol protocol)
|
||||
{
|
||||
var args = string.Format("advfirewall firewall delete rule name=\"Port {0}\" protocol={1} localport={0}", port, protocol);
|
||||
|
||||
RunNetsh(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the netsh.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
private void RunNetsh(string args)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "netsh",
|
||||
Arguments = args,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
Verb = "runas",
|
||||
ErrorDialog = false
|
||||
};
|
||||
|
||||
using (var process = new Process { StartInfo = startInfo })
|
||||
{
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns MAC Address from first Network Card in Computer
|
||||
/// </summary>
|
||||
/// <returns>[string] MAC Address</returns>
|
||||
public string GetMacAddress()
|
||||
{
|
||||
var mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
|
||||
var moc = mc.GetInstances();
|
||||
var macAddress = String.Empty;
|
||||
foreach (ManagementObject mo in moc)
|
||||
{
|
||||
if (macAddress == String.Empty) // only return MAC Address from first card
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((bool)mo["IPEnabled"]) macAddress = mo["MacAddress"].ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
mo.Dispose();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
mo.Dispose();
|
||||
}
|
||||
|
||||
return macAddress.Replace(":", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the DllImport : NetServerEnum with all its required parameters
|
||||
/// (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp
|
||||
/// for full details or method signature) to retrieve a list of domain SV_TYPE_WORKSTATION
|
||||
/// and SV_TYPE_SERVER PC's
|
||||
/// </summary>
|
||||
/// <returns>Arraylist that represents all the SV_TYPE_WORKSTATION and SV_TYPE_SERVER
|
||||
/// PC's in the Domain</returns>
|
||||
public IEnumerable<string> GetNetworkDevices()
|
||||
{
|
||||
//local fields
|
||||
const int MAX_PREFERRED_LENGTH = -1;
|
||||
var SV_TYPE_WORKSTATION = 1;
|
||||
var SV_TYPE_SERVER = 2;
|
||||
var buffer = IntPtr.Zero;
|
||||
var tmpBuffer = IntPtr.Zero;
|
||||
var entriesRead = 0;
|
||||
var totalEntries = 0;
|
||||
var resHandle = 0;
|
||||
var sizeofINFO = Marshal.SizeOf(typeof(_SERVER_INFO_100));
|
||||
|
||||
try
|
||||
{
|
||||
//call the DllImport : NetServerEnum with all its required parameters
|
||||
//see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netserverenum.asp
|
||||
//for full details of method signature
|
||||
var ret = NativeMethods.NetServerEnum(null, 100, ref buffer, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, SV_TYPE_WORKSTATION | SV_TYPE_SERVER, null, out resHandle);
|
||||
|
||||
//if the returned with a NERR_Success (C++ term), =0 for C#
|
||||
if (ret == 0)
|
||||
{
|
||||
//loop through all SV_TYPE_WORKSTATION and SV_TYPE_SERVER PC's
|
||||
for (var i = 0; i < totalEntries; i++)
|
||||
{
|
||||
//get pointer to, Pointer to the buffer that received the data from
|
||||
//the call to NetServerEnum. Must ensure to use correct size of
|
||||
//STRUCTURE to ensure correct location in memory is pointed to
|
||||
tmpBuffer = new IntPtr((int)buffer + (i * sizeofINFO));
|
||||
//Have now got a pointer to the list of SV_TYPE_WORKSTATION and
|
||||
//SV_TYPE_SERVER PC's, which is unmanaged memory
|
||||
//Needs to Marshal data from an unmanaged block of memory to a
|
||||
//managed object, again using STRUCTURE to ensure the correct data
|
||||
//is marshalled
|
||||
var svrInfo = (_SERVER_INFO_100)Marshal.PtrToStructure(tmpBuffer, typeof(_SERVER_INFO_100));
|
||||
|
||||
//add the PC names to the ArrayList
|
||||
if (!string.IsNullOrEmpty(svrInfo.sv100_name))
|
||||
{
|
||||
yield return svrInfo.sv100_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
//The NetApiBufferFree function frees
|
||||
//the memory that the NetApiBufferAllocate function allocates
|
||||
NativeMethods.NetApiBufferFree(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network shares.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <returns>IEnumerable{NetworkShare}.</returns>
|
||||
public IEnumerable<NetworkShare> GetNetworkShares(string path)
|
||||
{
|
||||
return new ShareCollection(path).OfType<Share>().Select(ToNetworkShare);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the network share.
|
||||
/// </summary>
|
||||
/// <param name="share">The share.</param>
|
||||
/// <returns>NetworkShare.</returns>
|
||||
private NetworkShare ToNetworkShare(Share share)
|
||||
{
|
||||
return new NetworkShare
|
||||
{
|
||||
Name = share.NetName,
|
||||
Path = share.Path,
|
||||
Remark = share.Remark,
|
||||
Server = share.Server,
|
||||
ShareType = ToNetworkShareType(share.ShareType)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the type of the network share.
|
||||
/// </summary>
|
||||
/// <param name="shareType">Type of the share.</param>
|
||||
/// <returns>NetworkShareType.</returns>
|
||||
/// <exception cref="System.ArgumentException">Unknown share type</exception>
|
||||
private NetworkShareType ToNetworkShareType(ShareType shareType)
|
||||
{
|
||||
switch (shareType)
|
||||
{
|
||||
case ShareType.Device:
|
||||
return NetworkShareType.Device;
|
||||
case ShareType.Disk :
|
||||
return NetworkShareType.Disk;
|
||||
case ShareType.IPC :
|
||||
return NetworkShareType.Ipc;
|
||||
case ShareType.Printer :
|
||||
return NetworkShareType.Printer;
|
||||
case ShareType.Special:
|
||||
return NetworkShareType.Special;
|
||||
default:
|
||||
throw new ArgumentException("Unknown share type");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
/// </summary>
|
||||
/// <param name="endpointstring">The endpointstring.</param>
|
||||
/// <returns>IPEndPoint.</returns>
|
||||
public IPEndPoint Parse(string endpointstring)
|
||||
{
|
||||
return Parse(endpointstring, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified endpointstring.
|
||||
/// </summary>
|
||||
/// <param name="endpointstring">The endpointstring.</param>
|
||||
/// <param name="defaultport">The defaultport.</param>
|
||||
/// <returns>IPEndPoint.</returns>
|
||||
/// <exception cref="System.ArgumentException">Endpoint descriptor may not be empty.</exception>
|
||||
/// <exception cref="System.FormatException"></exception>
|
||||
private static IPEndPoint Parse(string endpointstring, int defaultport)
|
||||
{
|
||||
if (string.IsNullOrEmpty(endpointstring)
|
||||
|| endpointstring.Trim().Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Endpoint descriptor may not be empty.");
|
||||
}
|
||||
|
||||
if (defaultport != -1 &&
|
||||
(defaultport < IPEndPoint.MinPort
|
||||
|| defaultport > IPEndPoint.MaxPort))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
|
||||
}
|
||||
|
||||
string[] values = endpointstring.Split(new char[] { ':' });
|
||||
IPAddress ipaddy;
|
||||
int port = -1;
|
||||
|
||||
//check if we have an IPv6 or ports
|
||||
if (values.Length <= 2) // ipv4 or hostname
|
||||
{
|
||||
if (values.Length == 1)
|
||||
//no port is specified, default
|
||||
port = defaultport;
|
||||
else
|
||||
port = GetPort(values[1]);
|
||||
|
||||
//try to use the address as IPv4, otherwise get hostname
|
||||
if (!IPAddress.TryParse(values[0], out ipaddy))
|
||||
ipaddy = GetIPfromHost(values[0]);
|
||||
}
|
||||
else if (values.Length > 2) //ipv6
|
||||
{
|
||||
//could [a:b:c]:d
|
||||
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
|
||||
{
|
||||
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
|
||||
ipaddy = IPAddress.Parse(ipaddressstring);
|
||||
port = GetPort(values[values.Length - 1]);
|
||||
}
|
||||
else //[a:b:c] or a:b:c
|
||||
{
|
||||
ipaddy = IPAddress.Parse(endpointstring);
|
||||
port = defaultport;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
|
||||
}
|
||||
|
||||
if (port == -1)
|
||||
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
|
||||
|
||||
return new IPEndPoint(ipaddy, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port.
|
||||
/// </summary>
|
||||
/// <param name="p">The p.</param>
|
||||
/// <returns>System.Int32.</returns>
|
||||
/// <exception cref="System.FormatException"></exception>
|
||||
private static int GetPort(string p)
|
||||
{
|
||||
int port;
|
||||
|
||||
if (!int.TryParse(p, out port)
|
||||
|| port < IPEndPoint.MinPort
|
||||
|| port > IPEndPoint.MaxPort)
|
||||
{
|
||||
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the I pfrom host.
|
||||
/// </summary>
|
||||
/// <param name="p">The p.</param>
|
||||
/// <returns>IPAddress.</returns>
|
||||
/// <exception cref="System.ArgumentException"></exception>
|
||||
private static IPAddress GetIPfromHost(string p)
|
||||
{
|
||||
var hosts = Dns.GetHostAddresses(p);
|
||||
|
||||
if (hosts == null || hosts.Length == 0)
|
||||
throw new ArgumentException(string.Format("Host not found: {0}", p));
|
||||
|
||||
return hosts[0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,638 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MediaBrowser.Networking.Management
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of share
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ShareType
|
||||
{
|
||||
/// <summary>Disk share</summary>
|
||||
Disk = 0,
|
||||
/// <summary>Printer share</summary>
|
||||
Printer = 1,
|
||||
/// <summary>Device share</summary>
|
||||
Device = 2,
|
||||
/// <summary>IPC share</summary>
|
||||
IPC = 3,
|
||||
/// <summary>Special share</summary>
|
||||
Special = -2147483648, // 0x80000000,
|
||||
}
|
||||
|
||||
#region Share
|
||||
|
||||
/// <summary>
|
||||
/// Information about a local share
|
||||
/// </summary>
|
||||
public class Share
|
||||
{
|
||||
#region Private data
|
||||
|
||||
private string _server;
|
||||
private string _netName;
|
||||
private string _path;
|
||||
private ShareType _shareType;
|
||||
private string _remark;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="Server"></param>
|
||||
/// <param name="shi"></param>
|
||||
public Share(string server, string netName, string path, ShareType shareType, string remark)
|
||||
{
|
||||
if (ShareType.Special == shareType && "IPC$" == netName)
|
||||
{
|
||||
shareType |= ShareType.IPC;
|
||||
}
|
||||
|
||||
_server = server;
|
||||
_netName = netName;
|
||||
_path = path;
|
||||
_shareType = shareType;
|
||||
_remark = remark;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the computer that this share belongs to
|
||||
/// </summary>
|
||||
public string Server
|
||||
{
|
||||
get { return _server; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Share name
|
||||
/// </summary>
|
||||
public string NetName
|
||||
{
|
||||
get { return _netName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Local path
|
||||
/// </summary>
|
||||
public string Path
|
||||
{
|
||||
get { return _path; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Share type
|
||||
/// </summary>
|
||||
public ShareType ShareType
|
||||
{
|
||||
get { return _shareType; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comment
|
||||
/// </summary>
|
||||
public string Remark
|
||||
{
|
||||
get { return _remark; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is a file system share
|
||||
/// </summary>
|
||||
public bool IsFileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
// Shared device
|
||||
if (0 != (_shareType & ShareType.Device)) return false;
|
||||
// IPC share
|
||||
if (0 != (_shareType & ShareType.IPC)) return false;
|
||||
// Shared printer
|
||||
if (0 != (_shareType & ShareType.Printer)) return false;
|
||||
|
||||
// Standard disk share
|
||||
if (0 == (_shareType & ShareType.Special)) return true;
|
||||
|
||||
// Special disk share (e.g. C$)
|
||||
return ShareType.Special == _shareType && !string.IsNullOrEmpty(_netName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the root of a disk-based share
|
||||
/// </summary>
|
||||
public DirectoryInfo Root
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsFileSystem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_server))
|
||||
if (string.IsNullOrEmpty(_path))
|
||||
return new DirectoryInfo(ToString());
|
||||
else
|
||||
return new DirectoryInfo(_path);
|
||||
return new DirectoryInfo(ToString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns the path to this share
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_server))
|
||||
{
|
||||
return string.Format(@"\\{0}\{1}", Environment.MachineName, _netName);
|
||||
}
|
||||
return string.Format(@"\\{0}\{1}", _server, _netName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this share matches the local path
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public bool MatchesPath(string path)
|
||||
{
|
||||
if (!IsFileSystem) return false;
|
||||
if (string.IsNullOrEmpty(path)) return true;
|
||||
|
||||
return path.ToLower().StartsWith(_path.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// A collection of shares
|
||||
/// </summary>
|
||||
public class ShareCollection : ReadOnlyCollectionBase
|
||||
{
|
||||
#region Platform
|
||||
|
||||
/// <summary>
|
||||
/// Is this an NT platform?
|
||||
/// </summary>
|
||||
protected static bool IsNT
|
||||
{
|
||||
get { return (PlatformID.Win32NT == Environment.OSVersion.Platform); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is Windows 2000 or higher
|
||||
/// </summary>
|
||||
protected static bool IsW2KUp
|
||||
{
|
||||
get
|
||||
{
|
||||
OperatingSystem os = Environment.OSVersion;
|
||||
if (PlatformID.Win32NT == os.Platform && os.Version.Major >= 5)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interop
|
||||
|
||||
#region Constants
|
||||
|
||||
/// <summary>Maximum path length</summary>
|
||||
protected const int MAX_PATH = 260;
|
||||
/// <summary>No error</summary>
|
||||
protected const int NO_ERROR = 0;
|
||||
/// <summary>Access denied</summary>
|
||||
protected const int ERROR_ACCESS_DENIED = 5;
|
||||
/// <summary>Access denied</summary>
|
||||
protected const int ERROR_WRONG_LEVEL = 124;
|
||||
/// <summary>More data available</summary>
|
||||
protected const int ERROR_MORE_DATA = 234;
|
||||
/// <summary>Not connected</summary>
|
||||
protected const int ERROR_NOT_CONNECTED = 2250;
|
||||
/// <summary>Level 1</summary>
|
||||
protected const int UNIVERSAL_NAME_INFO_LEVEL = 1;
|
||||
/// <summary>Max extries (9x)</summary>
|
||||
protected const int MAX_SI50_ENTRIES = 20;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structures
|
||||
|
||||
/// <summary>Unc name</summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
protected struct UNIVERSAL_NAME_INFO
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string lpUniversalName;
|
||||
}
|
||||
|
||||
/// <summary>Share information, NT, level 2</summary>
|
||||
/// <remarks>
|
||||
/// Requires admin rights to work.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
protected struct SHARE_INFO_2
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string NetName;
|
||||
public ShareType ShareType;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Remark;
|
||||
public int Permissions;
|
||||
public int MaxUsers;
|
||||
public int CurrentUsers;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Path;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Password;
|
||||
}
|
||||
|
||||
/// <summary>Share information, NT, level 1</summary>
|
||||
/// <remarks>
|
||||
/// Fallback when no admin rights.
|
||||
/// </remarks>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
protected struct SHARE_INFO_1
|
||||
{
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string NetName;
|
||||
public ShareType ShareType;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string Remark;
|
||||
}
|
||||
|
||||
/// <summary>Share information, Win9x</summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
|
||||
protected struct SHARE_INFO_50
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
|
||||
public string NetName;
|
||||
|
||||
public byte bShareType;
|
||||
public ushort Flags;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string Remark;
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string Path;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
|
||||
public string PasswordRW;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 9)]
|
||||
public string PasswordRO;
|
||||
|
||||
public ShareType ShareType
|
||||
{
|
||||
get { return (ShareType)((int)bShareType & 0x7F); }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Share information level 1, Win9x</summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
|
||||
protected struct SHARE_INFO_1_9x
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 13)]
|
||||
public string NetName;
|
||||
public byte Padding;
|
||||
|
||||
public ushort bShareType;
|
||||
|
||||
[MarshalAs(UnmanagedType.LPTStr)]
|
||||
public string Remark;
|
||||
|
||||
public ShareType ShareType
|
||||
{
|
||||
get { return (ShareType)((int)bShareType & 0x7FFF); }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>Get a UNC name</summary>
|
||||
[DllImport("mpr", CharSet = CharSet.Auto)]
|
||||
protected static extern int WNetGetUniversalName(string lpLocalPath,
|
||||
int dwInfoLevel, ref UNIVERSAL_NAME_INFO lpBuffer, ref int lpBufferSize);
|
||||
|
||||
/// <summary>Get a UNC name</summary>
|
||||
[DllImport("mpr", CharSet = CharSet.Auto)]
|
||||
protected static extern int WNetGetUniversalName(string lpLocalPath,
|
||||
int dwInfoLevel, IntPtr lpBuffer, ref int lpBufferSize);
|
||||
|
||||
/// <summary>Enumerate shares (NT)</summary>
|
||||
[DllImport("netapi32", CharSet = CharSet.Unicode)]
|
||||
protected static extern int NetShareEnum(string lpServerName, int dwLevel,
|
||||
out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead,
|
||||
out int totalEntries, ref int hResume);
|
||||
|
||||
/// <summary>Enumerate shares (9x)</summary>
|
||||
[DllImport("svrapi", CharSet = CharSet.Ansi)]
|
||||
protected static extern int NetShareEnum(
|
||||
[MarshalAs(UnmanagedType.LPTStr)] string lpServerName, int dwLevel,
|
||||
IntPtr lpBuffer, ushort cbBuffer, out ushort entriesRead,
|
||||
out ushort totalEntries);
|
||||
|
||||
/// <summary>Free the buffer (NT)</summary>
|
||||
[DllImport("netapi32")]
|
||||
protected static extern int NetApiBufferFree(IntPtr lpBuffer);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enumerate shares
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the shares on Windows NT
|
||||
/// </summary>
|
||||
/// <param name="server">The server name</param>
|
||||
/// <param name="shares">The ShareCollection</param>
|
||||
protected static void EnumerateSharesNT(string server, ShareCollection shares)
|
||||
{
|
||||
int level = 2;
|
||||
int entriesRead, totalEntries, nRet, hResume = 0;
|
||||
IntPtr pBuffer = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
nRet = NetShareEnum(server, level, out pBuffer, -1,
|
||||
out entriesRead, out totalEntries, ref hResume);
|
||||
|
||||
if (ERROR_ACCESS_DENIED == nRet)
|
||||
{
|
||||
//Need admin for level 2, drop to level 1
|
||||
level = 1;
|
||||
nRet = NetShareEnum(server, level, out pBuffer, -1,
|
||||
out entriesRead, out totalEntries, ref hResume);
|
||||
}
|
||||
|
||||
if (NO_ERROR == nRet && entriesRead > 0)
|
||||
{
|
||||
Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1);
|
||||
int offset = Marshal.SizeOf(t);
|
||||
|
||||
for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset)
|
||||
{
|
||||
IntPtr pItem = new IntPtr(lpItem);
|
||||
if (1 == level)
|
||||
{
|
||||
SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t);
|
||||
shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark);
|
||||
}
|
||||
else
|
||||
{
|
||||
SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t);
|
||||
shares.Add(si.NetName, si.Path, si.ShareType, si.Remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up buffer allocated by system
|
||||
if (IntPtr.Zero != pBuffer)
|
||||
NetApiBufferFree(pBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the shares on Windows 9x
|
||||
/// </summary>
|
||||
/// <param name="server">The server name</param>
|
||||
/// <param name="shares">The ShareCollection</param>
|
||||
protected static void EnumerateShares9x(string server, ShareCollection shares)
|
||||
{
|
||||
int level = 50;
|
||||
int nRet = 0;
|
||||
ushort entriesRead, totalEntries;
|
||||
|
||||
Type t = typeof(SHARE_INFO_50);
|
||||
int size = Marshal.SizeOf(t);
|
||||
ushort cbBuffer = (ushort)(MAX_SI50_ENTRIES * size);
|
||||
//On Win9x, must allocate buffer before calling API
|
||||
IntPtr pBuffer = Marshal.AllocHGlobal(cbBuffer);
|
||||
|
||||
try
|
||||
{
|
||||
nRet = NetShareEnum(server, level, pBuffer, cbBuffer,
|
||||
out entriesRead, out totalEntries);
|
||||
|
||||
if (ERROR_WRONG_LEVEL == nRet)
|
||||
{
|
||||
level = 1;
|
||||
t = typeof(SHARE_INFO_1_9x);
|
||||
size = Marshal.SizeOf(t);
|
||||
|
||||
nRet = NetShareEnum(server, level, pBuffer, cbBuffer,
|
||||
out entriesRead, out totalEntries);
|
||||
}
|
||||
|
||||
if (NO_ERROR == nRet || ERROR_MORE_DATA == nRet)
|
||||
{
|
||||
for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += size)
|
||||
{
|
||||
IntPtr pItem = new IntPtr(lpItem);
|
||||
|
||||
if (1 == level)
|
||||
{
|
||||
SHARE_INFO_1_9x si = (SHARE_INFO_1_9x)Marshal.PtrToStructure(pItem, t);
|
||||
shares.Add(si.NetName, string.Empty, si.ShareType, si.Remark);
|
||||
}
|
||||
else
|
||||
{
|
||||
SHARE_INFO_50 si = (SHARE_INFO_50)Marshal.PtrToStructure(pItem, t);
|
||||
shares.Add(si.NetName, si.Path, si.ShareType, si.Remark);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
Console.WriteLine(nRet);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Clean up buffer
|
||||
Marshal.FreeHGlobal(pBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the shares
|
||||
/// </summary>
|
||||
/// <param name="server">The server name</param>
|
||||
/// <param name="shares">The ShareCollection</param>
|
||||
protected static void EnumerateShares(string server, ShareCollection shares)
|
||||
{
|
||||
if (null != server && 0 != server.Length && !IsW2KUp)
|
||||
{
|
||||
server = server.ToUpper();
|
||||
|
||||
// On NT4, 9x and Me, server has to start with "\\"
|
||||
if (!('\\' == server[0] && '\\' == server[1]))
|
||||
server = @"\\" + server;
|
||||
}
|
||||
|
||||
if (IsNT)
|
||||
EnumerateSharesNT(server, shares);
|
||||
else
|
||||
EnumerateShares9x(server, shares);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if fileName is a valid local file-name of the form:
|
||||
/// X:\, where X is a drive letter from A-Z
|
||||
/// </summary>
|
||||
/// <param name="fileName">The filename to check</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidFilePath(string fileName)
|
||||
{
|
||||
if (null == fileName || 0 == fileName.Length) return false;
|
||||
|
||||
char drive = char.ToUpper(fileName[0]);
|
||||
if ('A' > drive || drive > 'Z')
|
||||
return false;
|
||||
|
||||
else if (Path.VolumeSeparatorChar != fileName[1])
|
||||
return false;
|
||||
else if (Path.DirectorySeparatorChar != fileName[2])
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>The name of the server this collection represents</summary>
|
||||
private string _server;
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor - local machine
|
||||
/// </summary>
|
||||
public ShareCollection()
|
||||
{
|
||||
_server = string.Empty;
|
||||
EnumerateShares(_server, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="Server"></param>
|
||||
public ShareCollection(string server)
|
||||
{
|
||||
_server = server;
|
||||
EnumerateShares(_server, this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Add
|
||||
|
||||
protected void Add(Share share)
|
||||
{
|
||||
InnerList.Add(share);
|
||||
}
|
||||
|
||||
protected void Add(string netName, string path, ShareType shareType, string remark)
|
||||
{
|
||||
InnerList.Add(new Share(_server, netName, path, shareType, remark));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the server this collection represents
|
||||
/// </summary>
|
||||
public string Server
|
||||
{
|
||||
get { return _server; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="Share"/> at the specified index.
|
||||
/// </summary>
|
||||
public Share this[int index]
|
||||
{
|
||||
get { return (Share)InnerList[index]; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="Share"/> which matches a given local path
|
||||
/// </summary>
|
||||
/// <param name="path">The path to match</param>
|
||||
public Share this[string path]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == path || 0 == path.Length) return null;
|
||||
|
||||
path = Path.GetFullPath(path);
|
||||
if (!IsValidFilePath(path)) return null;
|
||||
|
||||
Share match = null;
|
||||
|
||||
for (int i = 0; i < InnerList.Count; i++)
|
||||
{
|
||||
Share s = (Share)InnerList[i];
|
||||
|
||||
if (s.IsFileSystem && s.MatchesPath(path))
|
||||
{
|
||||
//Store first match
|
||||
if (null == match)
|
||||
match = s;
|
||||
|
||||
// If this has a longer path,
|
||||
// and this is a disk share or match is a special share,
|
||||
// then this is a better match
|
||||
else if (match.Path.Length < s.Path.Length)
|
||||
{
|
||||
if (ShareType.Disk == s.ShareType || ShareType.Disk != match.ShareType)
|
||||
match = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Copy this collection to an array
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="index"></param>
|
||||
public void CopyTo(Share[] array, int index)
|
||||
{
|
||||
InnerList.CopyTo(array, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,18 +106,7 @@
|
||||
<Compile Include="..\SharedVersion.cs">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="HttpManager\HttpManager.cs" />
|
||||
<Compile Include="HttpServer\BaseRestService.cs" />
|
||||
<Compile Include="Udp\UdpServer.cs" />
|
||||
<Compile Include="WebSocket\AlchemyServer.cs" />
|
||||
<Compile Include="WebSocket\AlchemyWebSocket.cs" />
|
||||
<Compile Include="HttpServer\HttpServer.cs" />
|
||||
<Compile Include="Management\NativeMethods.cs" />
|
||||
<Compile Include="Management\NetworkManager.cs" />
|
||||
<Compile Include="Management\NetworkShares.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="HttpServer\ServerFactory.cs" />
|
||||
<Compile Include="HttpServer\NativeWebSocket.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Networking.Management;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Networking.Udp
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a Udp Server
|
||||
/// </summary>
|
||||
public class UdpServer : IUdpServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when [message received].
|
||||
/// </summary>
|
||||
public event EventHandler<UdpMessageReceivedEventArgs> MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logger.
|
||||
/// </summary>
|
||||
/// <value>The logger.</value>
|
||||
private ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UdpServer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public UdpServer(ILogger logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="E:MessageReceived" /> event.
|
||||
/// </summary>
|
||||
/// <param name="e">The <see cref="UdpMessageReceivedEventArgs" /> instance containing the event data.</param>
|
||||
protected virtual void OnMessageReceived(UdpMessageReceivedEventArgs e)
|
||||
{
|
||||
EventHandler<UdpMessageReceivedEventArgs> handler = MessageReceived;
|
||||
if (handler != null) handler(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _udp client
|
||||
/// </summary>
|
||||
private UdpClient _udpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the specified port.
|
||||
/// </summary>
|
||||
/// <param name="port">The port.</param>
|
||||
public void Start(int port)
|
||||
{
|
||||
_udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port));
|
||||
|
||||
_udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
|
||||
CreateObservable().Subscribe(OnMessageReceived);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the observable.
|
||||
/// </summary>
|
||||
/// <returns>IObservable{UdpReceiveResult}.</returns>
|
||||
private IObservable<UdpReceiveResult> CreateObservable()
|
||||
{
|
||||
return Observable.Create<UdpReceiveResult>(obs =>
|
||||
Observable.FromAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return _udpClient.ReceiveAsync();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return Task.FromResult(new UdpReceiveResult(new byte[]{}, new IPEndPoint(IPAddress.Any, 0)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.ErrorException("Error receiving udp message", ex);
|
||||
return Task.FromResult(new UdpReceiveResult(new byte[] { }, new IPEndPoint(IPAddress.Any, 0)));
|
||||
}
|
||||
})
|
||||
|
||||
.Subscribe(obs))
|
||||
.Repeat()
|
||||
.Retry()
|
||||
.Publish()
|
||||
.RefCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [message received].
|
||||
/// </summary>
|
||||
/// <param name="message">The message.</param>
|
||||
private void OnMessageReceived(UdpReceiveResult message)
|
||||
{
|
||||
if (message.RemoteEndPoint.Port == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var bytes = message.Buffer;
|
||||
|
||||
OnMessageReceived(new UdpMessageReceivedEventArgs
|
||||
{
|
||||
Bytes = bytes,
|
||||
RemoteEndPoint = message.RemoteEndPoint.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_udpClient.Close();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the async.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="ipAddress">The ip address.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>Task{System.Int32}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">data</exception>
|
||||
public Task SendAsync(string data, string ipAddress, int port)
|
||||
{
|
||||
return SendAsync(Encoding.UTF8.GetBytes(data), ipAddress, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the async.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <param name="ipAddress">The ip address.</param>
|
||||
/// <param name="port">The port.</param>
|
||||
/// <returns>Task{System.Int32}.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">bytes</exception>
|
||||
public Task SendAsync(byte[] bytes, string ipAddress, int port)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new ArgumentNullException("bytes");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress))
|
||||
{
|
||||
throw new ArgumentNullException("ipAddress");
|
||||
}
|
||||
|
||||
return _udpClient.SendAsync(bytes, bytes.Length, ipAddress, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the async.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <param name="remoteEndPoint">The remote end point.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task SendAsync(byte[] bytes, string remoteEndPoint)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new ArgumentNullException("bytes");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(remoteEndPoint))
|
||||
{
|
||||
throw new ArgumentNullException("remoteEndPoint");
|
||||
}
|
||||
|
||||
return _udpClient.SendAsync(bytes, bytes.Length, new NetworkManager().Parse(remoteEndPoint));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using Alchemy;
|
||||
using Alchemy.Classes;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace MediaBrowser.Networking.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;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
WebSocketServer = new WebSocketServer(portNumber, IPAddress.Any)
|
||||
{
|
||||
OnConnected = OnAlchemyWebSocketClientConnected,
|
||||
TimeOut = TimeSpan.FromMinutes(60)
|
||||
};
|
||||
|
||||
WebSocketServer.Start();
|
||||
|
||||
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 (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()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
using Alchemy.Classes;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Networking.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// Class AlchemyWebSocket
|
||||
/// </summary>
|
||||
public class AlchemyWebSocket : IWebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <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(OnReceive);
|
||||
|
||||
_logger.Info("Client connected from {0}", context.ClientAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _disconnected
|
||||
/// </summary>
|
||||
private bool _disconnected = false;
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when [receive].
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
private void OnReceive(UserContext context)
|
||||
{
|
||||
if (OnReceiveDelegate != null)
|
||||
{
|
||||
var json = context.DataFrame.ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
OnReceiveDelegate(bytes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error processing web socket message", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return Task.Run(() => UserContext.Send(bytes));
|
||||
}
|
||||
|
||||
/// <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[]> OnReceiveDelegate { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user