HttpListenerHost.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Net;
  4. using MediaBrowser.Model.Logging;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Globalization;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Reflection;
  11. using System.Security.Cryptography.X509Certificates;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using Emby.Server.Implementations.HttpServer.SocketSharp;
  16. using Emby.Server.Implementations.Services;
  17. using MediaBrowser.Common.Net;
  18. using MediaBrowser.Common.Security;
  19. using MediaBrowser.Controller;
  20. using MediaBrowser.Model.Cryptography;
  21. using MediaBrowser.Model.Extensions;
  22. using MediaBrowser.Model.IO;
  23. using MediaBrowser.Model.Net;
  24. using MediaBrowser.Model.Serialization;
  25. using MediaBrowser.Model.Services;
  26. using MediaBrowser.Model.System;
  27. using MediaBrowser.Model.Text;
  28. namespace Emby.Server.Implementations.HttpServer
  29. {
  30. public class HttpListenerHost : IHttpServer, IDisposable
  31. {
  32. private string DefaultRedirectPath { get; set; }
  33. private readonly ILogger _logger;
  34. public string[] UrlPrefixes { get; private set; }
  35. private readonly List<IService> _restServices = new List<IService>();
  36. private IHttpListener _listener;
  37. public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected;
  38. public event EventHandler<WebSocketConnectingEventArgs> WebSocketConnecting;
  39. private readonly IServerConfigurationManager _config;
  40. private readonly INetworkManager _networkManager;
  41. private readonly IMemoryStreamFactory _memoryStreamProvider;
  42. private readonly IServerApplicationHost _appHost;
  43. private readonly ITextEncoding _textEncoding;
  44. private readonly ISocketFactory _socketFactory;
  45. private readonly ICryptoProvider _cryptoProvider;
  46. private readonly IFileSystem _fileSystem;
  47. private readonly IJsonSerializer _jsonSerializer;
  48. private readonly IXmlSerializer _xmlSerializer;
  49. private readonly X509Certificate _certificate;
  50. private readonly IEnvironmentInfo _environment;
  51. private readonly Func<Type, Func<string, object>> _funcParseFn;
  52. private readonly bool _enableDualModeSockets;
  53. public Action<IRequest, IResponse, object>[] RequestFilters { get; set; }
  54. public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
  55. private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>();
  56. public static HttpListenerHost Instance { get; protected set; }
  57. public HttpListenerHost(IServerApplicationHost applicationHost,
  58. ILogger logger,
  59. IServerConfigurationManager config,
  60. string serviceName,
  61. string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem)
  62. {
  63. Instance = this;
  64. _appHost = applicationHost;
  65. DefaultRedirectPath = defaultRedirectPath;
  66. _networkManager = networkManager;
  67. _memoryStreamProvider = memoryStreamProvider;
  68. _textEncoding = textEncoding;
  69. _socketFactory = socketFactory;
  70. _cryptoProvider = cryptoProvider;
  71. _jsonSerializer = jsonSerializer;
  72. _xmlSerializer = xmlSerializer;
  73. _environment = environment;
  74. _certificate = certificate;
  75. _funcParseFn = funcParseFn;
  76. _enableDualModeSockets = enableDualModeSockets;
  77. _fileSystem = fileSystem;
  78. _config = config;
  79. _logger = logger;
  80. RequestFilters = new Action<IRequest, IResponse, object>[] { };
  81. ResponseFilters = new Action<IRequest, IResponse, object>[] { };
  82. }
  83. public string GlobalResponse { get; set; }
  84. readonly Dictionary<Type, int> _mapExceptionToStatusCode = new Dictionary<Type, int>
  85. {
  86. {typeof (ResourceNotFoundException), 404},
  87. {typeof (RemoteServiceUnavailableException), 502},
  88. {typeof (FileNotFoundException), 404},
  89. //{typeof (DirectoryNotFoundException), 404},
  90. {typeof (SecurityException), 401},
  91. {typeof (PaymentRequiredException), 402},
  92. {typeof (ArgumentException), 400}
  93. };
  94. protected ILogger Logger
  95. {
  96. get
  97. {
  98. return _logger;
  99. }
  100. }
  101. public object CreateInstance(Type type)
  102. {
  103. return _appHost.CreateInstance(type);
  104. }
  105. /// <summary>
  106. /// Applies the request filters. Returns whether or not the request has been handled
  107. /// and no more processing should be done.
  108. /// </summary>
  109. /// <returns></returns>
  110. public void ApplyRequestFilters(IRequest req, IResponse res, object requestDto)
  111. {
  112. //Exec all RequestFilter attributes with Priority < 0
  113. var attributes = GetRequestFilterAttributes(requestDto.GetType());
  114. var i = 0;
  115. var count = attributes.Count;
  116. for (; i < count && attributes[i].Priority < 0; i++)
  117. {
  118. var attribute = attributes[i];
  119. attribute.RequestFilter(req, res, requestDto);
  120. }
  121. //Exec global filters
  122. foreach (var requestFilter in RequestFilters)
  123. {
  124. requestFilter(req, res, requestDto);
  125. }
  126. //Exec remaining RequestFilter attributes with Priority >= 0
  127. for (; i < count && attributes[i].Priority >= 0; i++)
  128. {
  129. var attribute = attributes[i];
  130. attribute.RequestFilter(req, res, requestDto);
  131. }
  132. }
  133. public Type GetServiceTypeByRequest(Type requestType)
  134. {
  135. Type serviceType;
  136. ServiceOperationsMap.TryGetValue(requestType, out serviceType);
  137. return serviceType;
  138. }
  139. public void AddServiceInfo(Type serviceType, Type requestType)
  140. {
  141. ServiceOperationsMap[requestType] = serviceType;
  142. }
  143. private List<IHasRequestFilter> GetRequestFilterAttributes(Type requestDtoType)
  144. {
  145. var attributes = requestDtoType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>().ToList();
  146. var serviceType = GetServiceTypeByRequest(requestDtoType);
  147. if (serviceType != null)
  148. {
  149. attributes.AddRange(serviceType.GetTypeInfo().GetCustomAttributes(true).OfType<IHasRequestFilter>());
  150. }
  151. attributes.Sort((x, y) => x.Priority - y.Priority);
  152. return attributes;
  153. }
  154. private IHttpListener GetListener()
  155. {
  156. //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem);
  157. return new WebSocketSharpListener(_logger,
  158. _certificate,
  159. _memoryStreamProvider,
  160. _textEncoding,
  161. _networkManager,
  162. _socketFactory,
  163. _cryptoProvider,
  164. _enableDualModeSockets,
  165. _fileSystem,
  166. _environment);
  167. }
  168. private void OnWebSocketConnecting(WebSocketConnectingEventArgs args)
  169. {
  170. if (_disposed)
  171. {
  172. return;
  173. }
  174. if (WebSocketConnecting != null)
  175. {
  176. WebSocketConnecting(this, args);
  177. }
  178. }
  179. private void OnWebSocketConnected(WebSocketConnectEventArgs args)
  180. {
  181. if (_disposed)
  182. {
  183. return;
  184. }
  185. if (WebSocketConnected != null)
  186. {
  187. WebSocketConnected(this, args);
  188. }
  189. }
  190. private Exception GetActualException(Exception ex)
  191. {
  192. var agg = ex as AggregateException;
  193. if (agg != null)
  194. {
  195. var inner = agg.InnerException;
  196. if (inner != null)
  197. {
  198. return GetActualException(inner);
  199. }
  200. else
  201. {
  202. var inners = agg.InnerExceptions;
  203. if (inners != null && inners.Count > 0)
  204. {
  205. return GetActualException(inners[0]);
  206. }
  207. }
  208. }
  209. return ex;
  210. }
  211. private int GetStatusCode(Exception ex)
  212. {
  213. if (ex is ArgumentException)
  214. {
  215. return 400;
  216. }
  217. var exceptionType = ex.GetType();
  218. int statusCode;
  219. if (!_mapExceptionToStatusCode.TryGetValue(exceptionType, out statusCode))
  220. {
  221. if (ex is DirectoryNotFoundException)
  222. {
  223. statusCode = 404;
  224. }
  225. else
  226. {
  227. statusCode = 500;
  228. }
  229. }
  230. return statusCode;
  231. }
  232. private void ErrorHandler(Exception ex, IRequest httpReq, bool logException = true)
  233. {
  234. try
  235. {
  236. ex = GetActualException(ex);
  237. if (logException)
  238. {
  239. _logger.ErrorException("Error processing request", ex);
  240. }
  241. var httpRes = httpReq.Response;
  242. if (httpRes.IsClosed)
  243. {
  244. return;
  245. }
  246. var statusCode = GetStatusCode(ex);
  247. httpRes.StatusCode = statusCode;
  248. httpRes.ContentType = "text/html";
  249. Write(httpRes, ex.Message);
  250. }
  251. catch
  252. {
  253. //_logger.ErrorException("Error this.ProcessRequest(context)(Exception while writing error to the response)", errorEx);
  254. }
  255. }
  256. /// <summary>
  257. /// Shut down the Web Service
  258. /// </summary>
  259. public void Stop()
  260. {
  261. if (_listener != null)
  262. {
  263. _logger.Info("Stopping HttpListener...");
  264. var task = _listener.Stop();
  265. Task.WaitAll(task);
  266. _logger.Info("HttpListener stopped");
  267. }
  268. }
  269. private readonly Dictionary<string, int> _skipLogExtensions = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
  270. {
  271. {".js", 0},
  272. {".css", 0},
  273. {".woff", 0},
  274. {".woff2", 0},
  275. {".ttf", 0},
  276. {".html", 0}
  277. };
  278. private bool EnableLogging(string url, string localPath)
  279. {
  280. var extension = GetExtension(url);
  281. if (string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension))
  282. {
  283. if (string.IsNullOrWhiteSpace(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1)
  284. {
  285. return true;
  286. }
  287. }
  288. return false;
  289. }
  290. private string GetExtension(string url)
  291. {
  292. var parts = url.Split(new[] { '?' }, 2);
  293. return Path.GetExtension(parts[0]);
  294. }
  295. public static string RemoveQueryStringByKey(string url, string key)
  296. {
  297. var uri = new Uri(url);
  298. // this gets all the query string key value pairs as a collection
  299. var newQueryString = MyHttpUtility.ParseQueryString(uri.Query);
  300. var originalCount = newQueryString.Count;
  301. if (originalCount == 0)
  302. {
  303. return url;
  304. }
  305. // this removes the key if exists
  306. newQueryString.Remove(key);
  307. if (originalCount == newQueryString.Count)
  308. {
  309. return url;
  310. }
  311. // this gets the page path from root without QueryString
  312. string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0];
  313. return newQueryString.Count > 0
  314. ? String.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
  315. : pagePathWithoutQueryString;
  316. }
  317. private string GetUrlToLog(string url)
  318. {
  319. url = RemoveQueryStringByKey(url, "api_key");
  320. return url;
  321. }
  322. private string NormalizeConfiguredLocalAddress(string address)
  323. {
  324. var index = address.Trim('/').IndexOf('/');
  325. if (index != -1)
  326. {
  327. address = address.Substring(index + 1);
  328. }
  329. return address.Trim('/');
  330. }
  331. private bool ValidateHost(string host)
  332. {
  333. var hosts = _config
  334. .Configuration
  335. .LocalNetworkAddresses
  336. .Select(NormalizeConfiguredLocalAddress)
  337. .ToList();
  338. if (hosts.Count == 0)
  339. {
  340. return true;
  341. }
  342. host = host ?? string.Empty;
  343. _logger.Debug("Validating host {0}", host);
  344. if (_networkManager.IsInPrivateAddressSpace(host))
  345. {
  346. hosts.Add("localhost");
  347. hosts.Add("127.0.0.1");
  348. return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1);
  349. }
  350. return true;
  351. }
  352. private bool ValidateSsl(string remoteIp, string urlString)
  353. {
  354. if (_config.Configuration.RequireHttps && _appHost.EnableHttps)
  355. {
  356. if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1)
  357. {
  358. if (!_networkManager.IsInLocalNetwork(remoteIp))
  359. {
  360. return false;
  361. }
  362. }
  363. }
  364. return true;
  365. }
  366. /// <summary>
  367. /// Overridable method that can be used to implement a custom hnandler
  368. /// </summary>
  369. protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
  370. {
  371. var date = DateTime.Now;
  372. var httpRes = httpReq.Response;
  373. bool enableLog = false;
  374. bool logHeaders = false;
  375. string urlToLog = null;
  376. string remoteIp = null;
  377. try
  378. {
  379. if (_disposed)
  380. {
  381. httpRes.StatusCode = 503;
  382. httpRes.ContentType = "text/plain";
  383. Write(httpRes, "Server shutting down");
  384. return;
  385. }
  386. if (!ValidateHost(host))
  387. {
  388. httpRes.StatusCode = 400;
  389. httpRes.ContentType = "text/plain";
  390. Write(httpRes, "Invalid host");
  391. return;
  392. }
  393. if (!ValidateSsl(httpReq.RemoteIp, urlString))
  394. {
  395. var httpsUrl = urlString
  396. .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
  397. .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
  398. RedirectToUrl(httpRes, httpsUrl);
  399. return;
  400. }
  401. if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
  402. {
  403. httpRes.StatusCode = 200;
  404. httpRes.AddHeader("Access-Control-Allow-Origin", "*");
  405. httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
  406. httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
  407. httpRes.ContentType = "text/plain";
  408. Write(httpRes, string.Empty);
  409. return;
  410. }
  411. var operationName = httpReq.OperationName;
  412. enableLog = EnableLogging(urlString, localPath);
  413. urlToLog = urlString;
  414. logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1;
  415. if (enableLog)
  416. {
  417. urlToLog = GetUrlToLog(urlString);
  418. remoteIp = httpReq.RemoteIp;
  419. LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
  420. }
  421. if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
  422. string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
  423. {
  424. RedirectToUrl(httpRes, DefaultRedirectPath);
  425. return;
  426. }
  427. if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
  428. string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
  429. {
  430. RedirectToUrl(httpRes, "emby/" + DefaultRedirectPath);
  431. return;
  432. }
  433. if (string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase) ||
  434. string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase) ||
  435. localPath.IndexOf("mediabrowser/web", StringComparison.OrdinalIgnoreCase) != -1)
  436. {
  437. httpRes.StatusCode = 200;
  438. httpRes.ContentType = "text/html";
  439. var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase)
  440. .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase);
  441. if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
  442. {
  443. Write(httpRes,
  444. "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
  445. newUrl + "\">" + newUrl + "</a></body></html>");
  446. return;
  447. }
  448. }
  449. if (localPath.IndexOf("dashboard/", StringComparison.OrdinalIgnoreCase) != -1 &&
  450. localPath.IndexOf("web/dashboard", StringComparison.OrdinalIgnoreCase) == -1)
  451. {
  452. httpRes.StatusCode = 200;
  453. httpRes.ContentType = "text/html";
  454. var newUrl = urlString.Replace("mediabrowser", "emby", StringComparison.OrdinalIgnoreCase)
  455. .Replace("/dashboard/", "/web/", StringComparison.OrdinalIgnoreCase);
  456. if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase))
  457. {
  458. Write(httpRes,
  459. "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" +
  460. newUrl + "\">" + newUrl + "</a></body></html>");
  461. return;
  462. }
  463. }
  464. if (string.Equals(localPath, "/web", StringComparison.OrdinalIgnoreCase))
  465. {
  466. RedirectToUrl(httpRes, DefaultRedirectPath);
  467. return;
  468. }
  469. if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
  470. {
  471. RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
  472. return;
  473. }
  474. if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
  475. {
  476. RedirectToUrl(httpRes, DefaultRedirectPath);
  477. return;
  478. }
  479. if (string.IsNullOrEmpty(localPath))
  480. {
  481. RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
  482. return;
  483. }
  484. if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
  485. {
  486. RedirectToUrl(httpRes, "web/pin.html");
  487. return;
  488. }
  489. if (!string.IsNullOrWhiteSpace(GlobalResponse))
  490. {
  491. httpRes.StatusCode = 503;
  492. httpRes.ContentType = "text/html";
  493. Write(httpRes, GlobalResponse);
  494. return;
  495. }
  496. var handler = GetServiceHandler(httpReq);
  497. if (handler != null)
  498. {
  499. await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false);
  500. }
  501. else
  502. {
  503. ErrorHandler(new FileNotFoundException(), httpReq, false);
  504. }
  505. }
  506. catch (OperationCanceledException ex)
  507. {
  508. ErrorHandler(ex, httpReq, false);
  509. }
  510. catch (Exception ex)
  511. {
  512. var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
  513. #if DEBUG
  514. logException = true;
  515. #endif
  516. ErrorHandler(ex, httpReq, logException);
  517. }
  518. finally
  519. {
  520. httpRes.Close();
  521. if (enableLog)
  522. {
  523. var statusCode = httpRes.StatusCode;
  524. var duration = DateTime.Now - date;
  525. LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null);
  526. }
  527. }
  528. }
  529. // Entry point for HttpListener
  530. public ServiceHandler GetServiceHandler(IHttpRequest httpReq)
  531. {
  532. var pathInfo = httpReq.PathInfo;
  533. var pathParts = pathInfo.TrimStart('/').Split('/');
  534. if (pathParts.Length == 0)
  535. {
  536. _logger.Error("Path parts empty for PathInfo: {0}, Url: {1}", pathInfo, httpReq.RawUrl);
  537. return null;
  538. }
  539. string contentType;
  540. var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, _logger, out contentType);
  541. if (restPath != null)
  542. {
  543. return new ServiceHandler
  544. {
  545. RestPath = restPath,
  546. ResponseContentType = contentType
  547. };
  548. }
  549. _logger.Error("Could not find handler for {0}", pathInfo);
  550. return null;
  551. }
  552. private void Write(IResponse response, string text)
  553. {
  554. var bOutput = Encoding.UTF8.GetBytes(text);
  555. response.SetContentLength(bOutput.Length);
  556. var outputStream = response.OutputStream;
  557. outputStream.Write(bOutput, 0, bOutput.Length);
  558. }
  559. public static void RedirectToUrl(IResponse httpRes, string url)
  560. {
  561. httpRes.StatusCode = 302;
  562. httpRes.AddHeader("Location", url);
  563. }
  564. public ServiceController ServiceController { get; private set; }
  565. /// <summary>
  566. /// Adds the rest handlers.
  567. /// </summary>
  568. /// <param name="services">The services.</param>
  569. public void Init(IEnumerable<IService> services)
  570. {
  571. _restServices.AddRange(services);
  572. ServiceController = new ServiceController();
  573. _logger.Info("Calling ServiceStack AppHost.Init");
  574. var types = _restServices.Select(r => r.GetType()).ToArray();
  575. ServiceController.Init(this, types);
  576. var list = new List<Action<IRequest, IResponse, object>>();
  577. foreach (var filter in _appHost.GetExports<IRequestFilter>())
  578. {
  579. list.Add(filter.Filter);
  580. }
  581. RequestFilters = list.ToArray();
  582. ResponseFilters = new Action<IRequest, IResponse, object>[]
  583. {
  584. new ResponseFilter(_logger).FilterResponse
  585. };
  586. }
  587. public RouteAttribute[] GetRouteAttributes(Type requestType)
  588. {
  589. var routes = requestType.GetTypeInfo().GetCustomAttributes<RouteAttribute>(true).ToList();
  590. var clone = routes.ToList();
  591. foreach (var route in clone)
  592. {
  593. routes.Add(new RouteAttribute(NormalizeEmbyRoutePath(route.Path), route.Verbs)
  594. {
  595. Notes = route.Notes,
  596. Priority = route.Priority,
  597. Summary = route.Summary
  598. });
  599. routes.Add(new RouteAttribute(NormalizeMediaBrowserRoutePath(route.Path), route.Verbs)
  600. {
  601. Notes = route.Notes,
  602. Priority = route.Priority,
  603. Summary = route.Summary
  604. });
  605. //routes.Add(new RouteAttribute(DoubleNormalizeEmbyRoutePath(route.Path), route.Verbs)
  606. //{
  607. // Notes = route.Notes,
  608. // Priority = route.Priority,
  609. // Summary = route.Summary
  610. //});
  611. }
  612. return routes.ToArray(routes.Count);
  613. }
  614. public Func<string, object> GetParseFn(Type propertyType)
  615. {
  616. return _funcParseFn(propertyType);
  617. }
  618. public void SerializeToJson(object o, Stream stream)
  619. {
  620. _jsonSerializer.SerializeToStream(o, stream);
  621. }
  622. public void SerializeToXml(object o, Stream stream)
  623. {
  624. _xmlSerializer.SerializeToStream(o, stream);
  625. }
  626. public object DeserializeXml(Type type, Stream stream)
  627. {
  628. return _xmlSerializer.DeserializeFromStream(type, stream);
  629. }
  630. public object DeserializeJson(Type type, Stream stream)
  631. {
  632. //using (var reader = new StreamReader(stream))
  633. //{
  634. // var json = reader.ReadToEnd();
  635. // Logger.Info(json);
  636. // return _jsonSerializer.DeserializeFromString(json, type);
  637. //}
  638. return _jsonSerializer.DeserializeFromStream(stream, type);
  639. }
  640. private string NormalizeEmbyRoutePath(string path)
  641. {
  642. if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
  643. {
  644. return "/emby" + path;
  645. }
  646. return "emby/" + path;
  647. }
  648. private string NormalizeMediaBrowserRoutePath(string path)
  649. {
  650. if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
  651. {
  652. return "/mediabrowser" + path;
  653. }
  654. return "mediabrowser/" + path;
  655. }
  656. private string DoubleNormalizeEmbyRoutePath(string path)
  657. {
  658. if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
  659. {
  660. return "/emby/emby" + path;
  661. }
  662. return "emby/emby/" + path;
  663. }
  664. private bool _disposed;
  665. private readonly object _disposeLock = new object();
  666. protected virtual void Dispose(bool disposing)
  667. {
  668. if (_disposed) return;
  669. lock (_disposeLock)
  670. {
  671. if (_disposed) return;
  672. _disposed = true;
  673. if (disposing)
  674. {
  675. Stop();
  676. }
  677. }
  678. }
  679. public void Dispose()
  680. {
  681. Dispose(true);
  682. GC.SuppressFinalize(this);
  683. }
  684. public void StartServer(string[] urlPrefixes)
  685. {
  686. UrlPrefixes = urlPrefixes;
  687. _listener = GetListener();
  688. _listener.WebSocketConnected = OnWebSocketConnected;
  689. _listener.WebSocketConnecting = OnWebSocketConnecting;
  690. _listener.ErrorHandler = ErrorHandler;
  691. _listener.RequestHandler = RequestHandler;
  692. _listener.Start(UrlPrefixes);
  693. }
  694. }
  695. }