mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-04-23 21:47:14 -04:00
Rework SsdpHttpClient
This commit is contained in:
parent
03f1eff21a
commit
cafeedcadf
5 changed files with 144 additions and 157 deletions
|
@ -235,7 +235,7 @@ namespace Emby.Dlna.PlayTo
|
|||
_logger.LogDebug("Setting mute");
|
||||
var value = mute ? 1 : 0;
|
||||
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -276,7 +276,7 @@ namespace Emby.Dlna.PlayTo
|
|||
// Remote control will perform better
|
||||
Volume = value;
|
||||
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -303,7 +303,7 @@ namespace Emby.Dlna.PlayTo
|
|||
throw new InvalidOperationException("Unable to find service");
|
||||
}
|
||||
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -343,7 +343,7 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
|
||||
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -400,7 +400,8 @@ namespace Emby.Dlna.PlayTo
|
|||
}
|
||||
|
||||
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
||||
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header, cancellationToken)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -428,7 +429,7 @@ namespace Emby.Dlna.PlayTo
|
|||
throw new InvalidOperationException("Unable to find service");
|
||||
}
|
||||
|
||||
return new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -461,7 +462,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -485,7 +486,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
var service = GetAvTransportService();
|
||||
|
||||
await new SsdpHttpClient(_httpClientFactory)
|
||||
await new DlnaHttpClient(_logger, _httpClientFactory)
|
||||
.SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
|
@ -618,7 +619,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -668,7 +669,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -701,7 +702,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return null;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -747,7 +748,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return null;
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -819,7 +820,7 @@ namespace Emby.Dlna.PlayTo
|
|||
return (false, null);
|
||||
}
|
||||
|
||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||
var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
|
||||
Properties.BaseUrl,
|
||||
service,
|
||||
command.Name,
|
||||
|
@ -997,7 +998,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||
|
||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||
var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
|
||||
|
||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
if (document == null)
|
||||
|
@ -1029,7 +1030,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||
|
||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||
var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
|
||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||
if (document == null)
|
||||
|
@ -1064,7 +1065,7 @@ namespace Emby.Dlna.PlayTo
|
|||
|
||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
||||
var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory);
|
||||
|
||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||
if (document == null)
|
||||
|
|
105
Emby.Dlna/PlayTo/DlnaHttpClient.cs
Normal file
105
Emby.Dlna/PlayTo/DlnaHttpClient.cs
Normal file
|
@ -0,0 +1,105 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Emby.Dlna.Common;
|
||||
using MediaBrowser.Common.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class DlnaHttpClient
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||
{
|
||||
// If it's already a complete url, don't stick anything onto the front of it
|
||||
if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
if (!serviceUrl.StartsWith('/'))
|
||||
{
|
||||
serviceUrl = "/" + serviceUrl;
|
||||
}
|
||||
|
||||
return baseUrl + serviceUrl;
|
||||
}
|
||||
|
||||
private async Task<XDocument?> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Dlna).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return await XDocument.LoadAsync(
|
||||
stream,
|
||||
LoadOptions.None,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (XmlException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to parse response");
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug("Malformed response:\n", await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<XDocument?> GetDataAsync(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
return SendRequestAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<XDocument?> SendCommandAsync(
|
||||
string baseUrl,
|
||||
DeviceService service,
|
||||
string command,
|
||||
string postData,
|
||||
string? header = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl))
|
||||
{
|
||||
Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml)
|
||||
};
|
||||
|
||||
request.Headers.TryAddWithoutValidation(
|
||||
"SOAPACTION",
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"\"{0}#{1}\"",
|
||||
service.ServiceType,
|
||||
command));
|
||||
request.Headers.Pragma.ParseAdd("no-cache");
|
||||
|
||||
if (!string.IsNullOrEmpty(header))
|
||||
{
|
||||
request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
|
||||
}
|
||||
|
||||
return SendRequestAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Emby.Dlna.Common;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
||||
namespace Emby.Dlna.PlayTo
|
||||
{
|
||||
public class SsdpHttpClient
|
||||
{
|
||||
private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50";
|
||||
private const string FriendlyName = "Jellyfin";
|
||||
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public SsdpHttpClient(IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public async Task<XDocument> SendCommandAsync(
|
||||
string baseUrl,
|
||||
DeviceService service,
|
||||
string command,
|
||||
string postData,
|
||||
string header = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||
using var response = await PostSoapDataAsync(
|
||||
url,
|
||||
$"\"{service.ServiceType}#{command}\"",
|
||||
postData,
|
||||
header,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
return await XDocument.LoadAsync(
|
||||
stream,
|
||||
LoadOptions.None,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||
{
|
||||
// If it's already a complete url, don't stick anything onto the front of it
|
||||
if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return serviceUrl;
|
||||
}
|
||||
|
||||
if (!serviceUrl.StartsWith('/'))
|
||||
{
|
||||
serviceUrl = "/" + serviceUrl;
|
||||
}
|
||||
|
||||
return baseUrl + serviceUrl;
|
||||
}
|
||||
|
||||
public async Task SubscribeAsync(
|
||||
string url,
|
||||
string ip,
|
||||
int port,
|
||||
string localIp,
|
||||
int eventport,
|
||||
int timeOut = 3600)
|
||||
{
|
||||
using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url);
|
||||
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||
options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture));
|
||||
options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">");
|
||||
options.Headers.TryAddWithoutValidation("NT", "upnp:event");
|
||||
options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead)
|
||||
.ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<XDocument> GetDataAsync(string url, CancellationToken cancellationToken)
|
||||
{
|
||||
using var options = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
response.EnsureSuccessStatusCode();
|
||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return await XDocument.LoadAsync(
|
||||
stream,
|
||||
LoadOptions.None,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> PostSoapDataAsync(
|
||||
string url,
|
||||
string soapAction,
|
||||
string postData,
|
||||
string header,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (soapAction[0] != '\"')
|
||||
{
|
||||
soapAction = $"\"{soapAction}\"";
|
||||
}
|
||||
|
||||
using var options = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
options.Headers.UserAgent.ParseAdd(USERAGENT);
|
||||
options.Headers.TryAddWithoutValidation("SOAPACTION", soapAction);
|
||||
options.Headers.TryAddWithoutValidation("Pragma", "no-cache");
|
||||
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||
|
||||
if (!string.IsNullOrEmpty(header))
|
||||
{
|
||||
options.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
|
||||
}
|
||||
|
||||
options.Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml);
|
||||
|
||||
return await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
@ -103,6 +104,22 @@ namespace Jellyfin.Server
|
|||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
|
||||
|
||||
services.AddHttpClient(NamedClient.Dlna, c =>
|
||||
{
|
||||
c.DefaultRequestHeaders.UserAgent.ParseAdd(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}/{1} UPnP/1.1 {2}/{3}",
|
||||
MediaBrowser.Common.System.OperatingSystem.Name,
|
||||
Environment.OSVersion,
|
||||
_serverApplicationHost.Name,
|
||||
_serverApplicationHost.ApplicationVersionString));
|
||||
|
||||
c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
|
||||
c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from?
|
||||
})
|
||||
.ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddDbContextCheck<JellyfinDb>();
|
||||
|
||||
|
|
|
@ -14,5 +14,10 @@
|
|||
/// Gets the value for the MusicBrainz named http client.
|
||||
/// </summary>
|
||||
public const string MusicBrainz = nameof(MusicBrainz);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value for the DLNA named http client.
|
||||
/// </summary>
|
||||
public const string Dlna = nameof(Dlna);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue