Refactored HttpRequest and HttpRequestBuilder, moving most of the logic to the HttpRequestBuilder.

Added ContentSummary to be able to describe the ContentData in a human readable form. (Useful for JsonRpc and FormData).
This commit is contained in:
Taloth Saldono 2016-02-28 16:41:22 +01:00
parent 7818f0c59b
commit 2ffbbb0e71
41 changed files with 683 additions and 347 deletions

View File

@ -58,18 +58,20 @@ namespace NzbDrone.Common.Test.Http
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Url.Should().Be(request.Url.ToString());
response.Resource.Url.Should().Be(request.Url.AbsoluteUri);
}
[Test]
public void should_execute_simple_post()
{
var message = "{ my: 1 }";
var request = new HttpRequest("http://eu.httpbin.org/post");
request.Body = "{ my: 1 }";
request.SetContent(message);
var response = Subject.Post<HttpBinResource>(request);
response.Resource.Data.Should().Be(request.Body);
response.Resource.Data.Should().Be(message);
}
[TestCase("gzip")]
@ -162,7 +164,7 @@ namespace NzbDrone.Common.Test.Http
public void should_send_cookie()
{
var request = new HttpRequest("http://eu.httpbin.org/get");
request.AddCookie("my", "cookie");
request.Cookies["my"] = "cookie";
var response = Subject.Get<HttpBinResource>(request);
@ -176,7 +178,7 @@ namespace NzbDrone.Common.Test.Http
public void GivenOldCookie()
{
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
oldRequest.AddCookie("my", "cookie");
oldRequest.Cookies["my"] = "cookie";
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
@ -260,7 +262,7 @@ namespace NzbDrone.Common.Test.Http
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
requestSet.AddCookie("my", "oldcookie");
requestSet.Cookies["my"] = "oldcookie";
var responseSet = Subject.Get(requestSet);
@ -322,10 +324,10 @@ namespace NzbDrone.Common.Test.Http
{
// the date is bad in the below - should be 13-Jul-2016
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
System.Uri.EscapeUriString(malformedCookie);
var requestSet = new HttpRequestBuilder("http://eu.httpbin.org/response-headers")
.AddQueryParam("Set-Cookie", malformedCookie)
.Build();
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
@ -376,6 +378,21 @@ namespace NzbDrone.Common.Test.Http
{
}
}
public void should_submit_formparameters_in_body()
{
Assert.Fail();
}
public void should_submit_attachments_as_multipart()
{
Assert.Fail();
}
public void should_submit_formparameters_as_multipart_if_attachments_exist()
{
Assert.Fail();
}
}
public class HttpBinResource

View File

@ -1,4 +1,5 @@
using FluentAssertions;
using System;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
@ -8,14 +9,32 @@ namespace NzbDrone.Common.Test.Http
[TestFixture]
public class HttpRequestBuilderFixture : TestBase
{
[TestCase("http://host/{seg}/some", "http://host/dir/some")]
[TestCase("http://host/some/{seg}", "http://host/some/dir")]
public void should_add_single_segment_url_segments(string url, string result)
{
var requestBuilder = new HttpRequestBuilder(url);
requestBuilder.SetSegment("seg", "dir");
requestBuilder.Build().Url.Should().Be(result);
}
[Test]
public void shouldnt_add_value_for_nonexisting_segment()
{
var requestBuilder = new HttpRequestBuilder("http://host/{seg}/some");
Assert.Throws<InvalidOperationException>(() => requestBuilder.SetSegment("seg2", "dir"));
}
[Test]
public void should_remove_duplicated_slashes()
{
var builder = new HttpRequestBuilder("http://domain/");
var request = builder.Build("/v1/");
var request = builder.Resource("/v1/").Build();
request.Url.ToString().Should().Be("http://domain/v1/");
request.Url.AbsoluteUri.Should().Be("http://domain/v1/");
}
}

View File

@ -8,22 +8,5 @@ namespace NzbDrone.Common.Test.Http
[TestFixture]
public class HttpRequestFixture
{
[TestCase("http://host/{seg}/some", "http://host/dir/some")]
[TestCase("http://host/some/{seg}", "http://host/some/dir")]
public void should_add_single_segment_url_segments(string url, string result)
{
var request = new HttpRequest(url);
request.AddSegment("seg", "dir");
request.Url.Should().Be(result);
}
[Test]
public void shouldnt_add_value_for_nonexisting_segment()
{
var request = new HttpRequest("http://host/{seg}/some");
Assert.Throws<InvalidOperationException>(() => request.AddSegment("seg2", "dir"));
}
}
}

View File

@ -1,19 +0,0 @@
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Cloud
{
public interface IDroneServicesRequestBuilder
{
HttpRequest Build(string path);
}
public class DroneServicesHttpRequestBuilder : HttpRequestBuilder, IDroneServicesRequestBuilder
{
private const string ROOT_URL = "http://services.sonarr.tv/v1/";
public DroneServicesHttpRequestBuilder()
: base(ROOT_URL)
{
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Cloud
{
public interface ISonarrCloudRequestBuilder
{
IHttpRequestBuilderFactory Services { get; }
IHttpRequestBuilderFactory SkyHookTvdb { get; }
}
public class SonarrCloudRequestBuilder : ISonarrCloudRequestBuilder
{
public SonarrCloudRequestBuilder()
{
Services = new HttpRequestBuilder("http://services.sonarr.tv/v1/")
.CreateFactory();
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/{language}/")
.SetSegment("language", "en")
.CreateFactory();
}
public IHttpRequestBuilderFactory Services { get; private set; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
}
}

View File

@ -26,7 +26,7 @@ namespace NzbDrone.Common.Extensions
public static void Add<TKey, TValue>(this ICollection<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
{
collection.Add(key, value);
collection.Add(new KeyValuePair<TKey, TValue>(key, value));
}
}
}

View File

@ -86,6 +86,11 @@ namespace NzbDrone.Common.Http.Dispatchers
curlEasy.UserAgent = UserAgentBuilder.UserAgent;
curlEasy.FollowLocation = request.AllowAutoRedirect;
if (request.RequestTimeout != TimeSpan.Zero)
{
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
}
if (OsInfo.IsWindows)
{
curlEasy.CaInfo = "curl-ca-bundle.crt";
@ -96,11 +101,10 @@ namespace NzbDrone.Common.Http.Dispatchers
curlEasy.Cookie = cookies.GetCookieHeader(request.Url);
}
if (!request.Body.IsNullOrWhiteSpace())
if (request.ContentData != null)
{
// TODO: This might not go well with encoding.
curlEasy.PostFieldSize = request.Body.Length;
curlEasy.SetOpt(CurlOption.CopyPostFields, request.Body);
curlEasy.PostFieldSize = request.ContentData.Length;
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
}
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state

View File

@ -23,19 +23,22 @@ namespace NzbDrone.Common.Http.Dispatchers
webRequest.ContentLength = 0;
webRequest.CookieContainer = cookies;
if (request.RequestTimeout != TimeSpan.Zero)
{
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
}
if (request.Headers != null)
{
AddRequestHeaders(webRequest, request.Headers);
}
if (!request.Body.IsNullOrWhiteSpace())
if (request.ContentData != null)
{
var bytes = request.Headers.GetEncodingFromContentType().GetBytes(request.Body.ToCharArray());
webRequest.ContentLength = bytes.Length;
webRequest.ContentLength = request.ContentData.Length;
using (var writeStream = webRequest.GetRequestStream())
{
writeStream.Write(bytes, 0, bytes.Length);
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
}
}
@ -75,43 +78,43 @@ namespace NzbDrone.Common.Http.Dispatchers
switch (header.Key)
{
case "Accept":
webRequest.Accept = header.Value.ToString();
webRequest.Accept = header.Value;
break;
case "Connection":
webRequest.Connection = header.Value.ToString();
webRequest.Connection = header.Value;
break;
case "Content-Length":
webRequest.ContentLength = Convert.ToInt64(header.Value);
break;
case "Content-Type":
webRequest.ContentType = header.Value.ToString();
webRequest.ContentType = header.Value;
break;
case "Date":
webRequest.Date = (DateTime)header.Value;
webRequest.Date = HttpHeader.ParseDateTime(header.Value);
break;
case "Expect":
webRequest.Expect = header.Value.ToString();
webRequest.Expect = header.Value;
break;
case "Host":
webRequest.Host = header.Value.ToString();
webRequest.Host = header.Value;
break;
case "If-Modified-Since":
webRequest.IfModifiedSince = (DateTime)header.Value;
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
break;
case "Range":
throw new NotImplementedException();
case "Referer":
webRequest.Referer = header.Value.ToString();
webRequest.Referer = header.Value;
break;
case "Transfer-Encoding":
webRequest.TransferEncoding = header.Value.ToString();
webRequest.TransferEncoding = header.Value;
break;
case "User-Agent":
throw new NotSupportedException("User-Agent other than Sonarr not allowed.");
case "Proxy-Connection":
throw new NotImplementedException();
default:
webRequest.Headers.Add(header.Key, header.Value.ToString());
webRequest.Headers.Add(header.Key, header.Value);
break;
}
}

View File

@ -4,9 +4,11 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL;

View File

@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
public HttpResponse Response { get; private set; }
public HttpException(HttpRequest request, HttpResponse response)
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.ToString()))
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.AbsoluteUri))
{
Request = request;
Response = response;

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Http
{
public class HttpFormData
{
public string Name { get; set; }
public string FileName { get; set; }
public byte[] ContentData { get; set; }
public string ContentType { get; set; }
}
}

View File

@ -4,37 +4,92 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using NzbDrone.Common.Extensions;
using System.Collections;
using System.Globalization;
namespace NzbDrone.Common.Http
{
public class HttpHeader : Dictionary<string, object>
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
{
public HttpHeader(NameValueCollection headers) : base(StringComparer.OrdinalIgnoreCase)
public HttpHeader(NameValueCollection headers)
: base(headers)
{
foreach (var key in headers.AllKeys)
}
public HttpHeader()
{
}
public bool ContainsKey(string key)
{
key = key.ToLowerInvariant();
return AllKeys.Any(v => v.ToLowerInvariant() == key);
}
public string GetSingleValue(string key)
{
var values = GetValues(key);
if (values == null || values.Length == 0)
{
this[key] = headers[key];
return null;
}
if (values.Length > 1)
{
throw new ApplicationException(string.Format("Expected {0} to occur only once.", key));
}
return values[0];
}
protected T? GetSingleValue<T>(string key, Func<string, T> converter) where T : struct
{
var value = GetSingleValue(key);
if (value == null)
{
return null;
}
return converter(value);
}
protected void SetSingleValue(string key, string value)
{
if (value == null)
{
Remove(key);
}
else
{
Set(key, value);
}
}
public HttpHeader() : base(StringComparer.OrdinalIgnoreCase)
protected void SetSingleValue<T>(string key, T? value, Func<T, string> converter = null) where T : struct
{
if (!value.HasValue)
{
Remove(key);
}
else if (converter != null)
{
Set(key, converter(value.Value));
}
else
{
Set(key, value.Value.ToString());
}
}
public long? ContentLength
{
get
{
if (!ContainsKey("Content-Length"))
{
return null;
}
return Convert.ToInt64(this["Content-Length"]);
return GetSingleValue("Content-Length", Convert.ToInt64);
}
set
{
this["Content-Length"] = value;
SetSingleValue("Content-Length", value);
}
}
@ -42,15 +97,11 @@ namespace NzbDrone.Common.Http
{
get
{
if (!ContainsKey("Content-Type"))
{
return null;
}
return this["Content-Type"].ToString();
return GetSingleValue("Content-Type");
}
set
{
this["Content-Type"] = value;
SetSingleValue("Content-Type", value);
}
}
@ -58,25 +109,36 @@ namespace NzbDrone.Common.Http
{
get
{
if (!ContainsKey("Accept"))
{
return null;
}
return this["Accept"].ToString();
return GetSingleValue("Accept");
}
set
{
this["Accept"] = value;
SetSingleValue("Accept", value);
}
}
public new IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return AllKeys.SelectMany(GetValues, (k, c) => new KeyValuePair<string, string>(k, c)).ToList().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return base.GetEnumerator();
}
public Encoding GetEncodingFromContentType()
{
return GetEncodingFromContentType(ContentType ?? string.Empty);
}
public static Encoding GetEncodingFromContentType(string contentType)
{
Encoding encoding = null;
if (ContentType.IsNotNullOrWhiteSpace())
if (contentType.IsNotNullOrWhiteSpace())
{
var charset = ContentType.ToLowerInvariant()
var charset = contentType.ToLowerInvariant()
.Split(';', '=', ' ')
.SkipWhile(v => v != "charset")
.Skip(1).FirstOrDefault();
@ -99,5 +161,18 @@ namespace NzbDrone.Common.Http
return encoding;
}
public static DateTime ParseDateTime(string value)
{
return DateTime.ParseExact(value, "R", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal);
}
public static List<KeyValuePair<string, string>> ParseCookies(string cookies)
{
return cookies.Split(';')
.Select(v => v.Trim().Split('='))
.Select(v => new KeyValuePair<string, string>(v[0], v[1]))
.ToList();
}
}
}

View File

@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public class HttpRequest
{
private readonly Dictionary<string, string> _segments;
public HttpRequest(string url, HttpAccept httpAccept = null)
public HttpRequest(string uri, HttpAccept httpAccept = null)
{
UriBuilder = new UriBuilder(url);
UrlBuilder = new UriBuilder(uri);
Headers = new HttpHeader();
_segments = new Dictionary<string, string>();
AllowAutoRedirect = true;
Cookies = new Dictionary<string, string>();
@ -28,73 +28,41 @@ namespace NzbDrone.Common.Http
}
}
public UriBuilder UriBuilder { get; private set; }
public Uri Url
{
get
{
var uri = UriBuilder.Uri.ToString();
foreach (var segment in _segments)
{
uri = uri.Replace(segment.Key, segment.Value);
}
return new Uri(uri);
}
}
public UriBuilder UrlBuilder { get; private set; }
public Uri Url { get { return UrlBuilder.Uri; } }
public HttpMethod Method { get; set; }
public HttpHeader Headers { get; set; }
public string Body { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public bool SuppressHttpError { get; set; }
public bool AllowAutoRedirect { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public bool StoreResponseCookie { get; set; }
public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; }
public override string ToString()
{
if (Body == null)
if (ContentSummary == null)
{
return string.Format("Req: [{0}] {1}", Method, Url);
}
return string.Format("Req: [{0}] {1} {2} {3}", Method, Url, Environment.NewLine, Body);
}
public void AddSegment(string segment, string value)
{
var key = "{" + segment + "}";
if (!UriBuilder.Uri.ToString().Contains(key))
else
{
throw new InvalidOperationException("Segment " + key +" is not defined in Uri");
return string.Format("Req: [{0}] {1}: {2}", Method, Url, ContentSummary);
}
_segments.Add(key, value);
}
public void AddQueryParam(string segment, string value)
public void SetContent(byte[] data)
{
UriBuilder.SetQueryParam(segment, value);
ContentData = data;
}
public void AddCookie(string key, string value)
public void SetContent(string data)
{
Cookies[key] = value;
}
public void AddCookie(string cookies)
{
foreach (var pair in cookies.Split(';'))
{
var split = pair.Split('=');
Cookies[split[0].Trim()] = split[1].Trim();
}
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
}
}
}

View File

@ -1,33 +1,123 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public class HttpRequestBuilder
{
public Uri BaseUri { get; private set; }
public bool SupressHttpError { get; set; }
public HttpMethod Method { get; set; }
public HttpAccept HttpAccept { get; set; }
public Uri BaseUrl { get; private set; }
public string ResourceUrl { get; set; }
public List<KeyValuePair<string, string>> QueryParams { get; private set; }
public List<KeyValuePair<string, string>> SuffixQueryParams { get; private set; }
public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; }
public bool AllowAutoRedirect { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public Action<HttpRequest> PostProcess { get; set; }
public HttpRequestBuilder(string baseUri)
public HttpRequestBuilder(string baseUrl)
{
BaseUri = new Uri(baseUri);
BaseUrl = new Uri(baseUrl);
ResourceUrl = string.Empty;
Method = HttpMethod.GET;
QueryParams = new List<KeyValuePair<string, string>>();
SuffixQueryParams = new List<KeyValuePair<string, string>>();
Segments = new Dictionary<string, string>();
Headers = new HttpHeader();
Cookies = new Dictionary<string, string>();
}
public virtual HttpRequest Build(string path)
public HttpRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
: this(BuildBaseUrl(useHttps, host, port, urlBase))
{
if (BaseUri.ToString().EndsWith("/"))
}
public static string BuildBaseUrl(bool useHttps, string host, int port, string urlBase = null)
{
var protocol = useHttps ? "https" : "http";
if (urlBase.IsNotNullOrWhiteSpace() && !urlBase.StartsWith("/"))
{
path = path.TrimStart('/');
urlBase = "/" + urlBase;
}
var request = new HttpRequest(BaseUri + path)
return string.Format("{0}://{1}:{2}{3}", protocol, host, port, urlBase);
}
public virtual HttpRequestBuilder Clone()
{
var clone = MemberwiseClone() as HttpRequestBuilder;
clone.QueryParams = new List<KeyValuePair<string, string>>(clone.QueryParams);
clone.SuffixQueryParams = new List<KeyValuePair<string, string>>(clone.SuffixQueryParams);
clone.Segments = new Dictionary<string, string>(clone.Segments);
clone.Headers = new HttpHeader(clone.Headers);
clone.Cookies = new Dictionary<string, string>(clone.Cookies);
return clone;
}
protected virtual Uri CreateUri()
{
var builder = new UriBuilder(new Uri(BaseUrl, ResourceUrl));
foreach (var queryParam in QueryParams.Concat(SuffixQueryParams))
{
SuppressHttpError = SupressHttpError,
NetworkCredential = NetworkCredential
};
builder.SetQueryParam(queryParam.Key, queryParam.Value);
}
if (Segments.Any())
{
var url = builder.Uri.ToString();
foreach (var segment in Segments)
{
url = url.Replace(segment.Key, segment.Value);
}
builder = new UriBuilder(url);
}
return builder.Uri;
}
protected virtual HttpRequest CreateRequest()
{
return new HttpRequest(CreateUri().ToString(), HttpAccept);
}
protected virtual void Apply(HttpRequest request)
{
request.Method = Method;
request.SuppressHttpError = SuppressHttpError;
request.AllowAutoRedirect = AllowAutoRedirect;
request.NetworkCredential = NetworkCredential;
foreach (var header in Headers)
{
request.Headers.Set(header.Key, header.Value);
}
foreach (var cookie in Cookies)
{
request.Cookies[cookie.Key] = cookie.Value;
}
}
public virtual HttpRequest Build()
{
var request = CreateRequest();
Apply(request);
if (PostProcess != null)
{
@ -36,5 +126,102 @@ namespace NzbDrone.Common.Http
return request;
}
public IHttpRequestBuilderFactory CreateFactory()
{
return new HttpRequestBuilderFactory(this);
}
public virtual HttpRequestBuilder Resource(string resourceUrl)
{
if (!ResourceUrl.IsNotNullOrWhiteSpace() || resourceUrl.StartsWith("/"))
{
ResourceUrl = resourceUrl.TrimStart('/');
}
else
{
ResourceUrl = string.Format("{0}/{1}", ResourceUrl.TrimEnd('/'), resourceUrl);
}
return this;
}
public virtual HttpRequestBuilder Post()
{
Method = HttpMethod.POST;
return this;
}
public virtual HttpRequestBuilder Accept(HttpAccept accept)
{
HttpAccept = accept;
return this;
}
public virtual HttpRequestBuilder SetHeader(string name, string value)
{
Headers.Set(name, value);
return this;
}
public virtual HttpRequestBuilder AddQueryParam(string key, object value, bool replace = false)
{
if (replace)
{
QueryParams.RemoveAll(v => v.Key == key);
SuffixQueryParams.RemoveAll(v => v.Key == key);
}
QueryParams.Add(key, value.ToString());
return this;
}
public virtual HttpRequestBuilder AddSuffixQueryParam(string key, object value, bool replace = false)
{
if (replace)
{
QueryParams.RemoveAll(v => v.Key == key);
SuffixQueryParams.RemoveAll(v => v.Key == key);
}
SuffixQueryParams.Add(new KeyValuePair<string, string>(key, value.ToString()));
return this;
}
public virtual HttpRequestBuilder SetSegment(string segment, string value, bool dontCheck = false)
{
var key = string.Concat("{", segment, "}");
if (!dontCheck && !CreateUri().ToString().Contains(key))
{
throw new InvalidOperationException(string.Format("Segment {0} is not defined in Uri", segment));
}
Segments[key] = value;
return this;
}
public virtual HttpRequestBuilder SetCookies(IEnumerable<KeyValuePair<string, string>> cookies)
{
foreach (var cookie in cookies)
{
Cookies[cookie.Key] = cookie.Value;
}
return this;
}
public virtual HttpRequestBuilder SetCookie(string key, string value)
{
Cookies[key] = value;
return this;
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Http
{
public interface IHttpRequestBuilderFactory
{
HttpRequestBuilder Create();
}
public class HttpRequestBuilderFactory : IHttpRequestBuilderFactory
{
private HttpRequestBuilder _rootBuilder;
public HttpRequestBuilderFactory(HttpRequestBuilder rootBuilder)
{
SetRootBuilder(rootBuilder);
}
protected HttpRequestBuilderFactory()
{
}
protected void SetRootBuilder(HttpRequestBuilder rootBuilder)
{
_rootBuilder = rootBuilder.Clone();
}
public HttpRequestBuilder Create()
{
return _rootBuilder.Clone();
}
}
}

View File

@ -1,11 +1,16 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Common.Http
{
public class HttpResponse
{
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Request = request;
@ -52,11 +57,27 @@ namespace NzbDrone.Common.Http
}
}
public Dictionary<string, string> GetCookies()
{
var result = new Dictionary<string, string>();
foreach (var cookie in Headers.GetValues("Set-Cookie"))
{
var match = RegexSetCookie.Match(cookie);
if (match.Success)
{
result[match.Groups[1].Value] = match.Groups[2].Value;
}
}
return result;
}
public override string ToString()
{
var result = string.Format("Res: [{0}] {1} : {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
if (HasHttpError && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
{
result += Environment.NewLine + Content;
}

View File

@ -7,32 +7,87 @@ namespace NzbDrone.Common.Http
{
public class JsonRpcRequestBuilder : HttpRequestBuilder
{
public string Method { get; private set; }
public List<object> Parameters { get; private set; }
public static HttpAccept JsonRpcHttpAccept = new HttpAccept("application/json-rpc, application/json");
public static string JsonRpcContentType = "application/json-rpc";
public JsonRpcRequestBuilder(string baseUri, string method, IEnumerable<object> parameters)
: base (baseUri)
public string JsonMethod { get; private set; }
public List<object> JsonParameters { get; private set; }
public JsonRpcRequestBuilder(string baseUrl)
: base(baseUrl)
{
Method = method;
Parameters = parameters.ToList();
Method = HttpMethod.POST;
JsonParameters = new List<object>();
}
public override HttpRequest Build(string path)
public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters)
: base (baseUrl)
{
var request = base.Build(path);
request.Method = HttpMethod.POST;
request.Headers.Accept = "application/json-rpc, application/json";
request.Headers.ContentType = "application/json-rpc";
Method = HttpMethod.POST;
JsonMethod = method;
JsonParameters = parameters.ToList();
}
public override HttpRequestBuilder Clone()
{
var clone = base.Clone() as JsonRpcRequestBuilder;
clone.JsonParameters = new List<object>(JsonParameters);
return clone;
}
public JsonRpcRequestBuilder Call(string method, params object[] parameters)
{
var clone = Clone() as JsonRpcRequestBuilder;
clone.JsonMethod = method;
clone.JsonParameters = parameters.ToList();
return clone;
}
protected override void Apply(HttpRequest request)
{
base.Apply(request);
request.Headers.ContentType = JsonRpcContentType;
var parameterData = new object[JsonParameters.Count];
var parameterSummary = new string[JsonParameters.Count];
for (var i = 0; i < JsonParameters.Count; i++)
{
ConvertParameter(JsonParameters[i], out parameterData[i], out parameterSummary[i]);
}
var message = new Dictionary<string, object>();
message["jsonrpc"] = "2.0";
message["method"] = Method;
message["params"] = Parameters;
message["method"] = JsonMethod;
message["params"] = parameterData;
message["id"] = CreateNextId();
request.Body = message.ToJson();
request.SetContent(message.ToJson());
return request;
if (request.ContentSummary == null)
{
request.ContentSummary = string.Format("{0}({1})", JsonMethod, string.Join(", ", parameterSummary));
}
}
private void ConvertParameter(object value, out object data, out string summary)
{
if (value is byte[])
{
data = Convert.ToBase64String(value as byte[]);
summary = string.Format("[blob {0} bytes]", (value as byte[]).Length);
}
else if (value is Array && ((Array)value).Length > 0)
{
data = value;
summary = "[...]";
}
else
{
data = value;
summary = data.ToJson();
}
}
public string CreateNextId()

View File

@ -67,7 +67,7 @@
<Compile Include="Cache\CachedDictionary.cs" />
<Compile Include="Cache\ICached.cs" />
<Compile Include="Cache\ICachedDictionary.cs" />
<Compile Include="Cloud\CloudClient.cs" />
<Compile Include="Cloud\SonarrCloudRequestBuilder.cs" />
<Compile Include="Composition\Container.cs" />
<Compile Include="Composition\ContainerBuilderBase.cs" />
<Compile Include="Composition\IContainer.cs" />
@ -156,6 +156,7 @@
<Compile Include="Http\HttpAccept.cs" />
<Compile Include="Http\HttpClient.cs" />
<Compile Include="Http\HttpException.cs" />
<Compile Include="Http\HttpFormData.cs" />
<Compile Include="Http\HttpHeader.cs" />
<Compile Include="Http\HttpMethod.cs" />
<Compile Include="Http\HttpProvider.cs" />
@ -168,6 +169,7 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="Http\HttpRequestBuilder.cs" />
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
<Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Http\UriExtensions.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />

View File

@ -112,7 +112,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
}
@ -128,7 +128,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
}

View File

@ -93,7 +93,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(_filePath), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
}
@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
Subject.Download(remoteEpisode);
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.ToString() == _downloadUrl)), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.Get(It.Is<HttpRequest>(v => v.Url.AbsoluteUri == _downloadUrl)), Times.Once());
Mocker.GetMock<IDiskProvider>().Verify(c => c.OpenWriteStream(expectedFilename), Times.Once());
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
}

View File

@ -110,7 +110,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.UTorrentTests
httpHeader["Location"] = "http://test.sonarr.tv/not-a-real-torrent.torrent";
Mocker.GetMock<IHttpClient>()
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.AbsoluteUri == _downloadUrl)))
.Setup(s => s.Get(It.Is<HttpRequest>(h => h.Url.ToString() == _downloadUrl)))
.Returns<HttpRequest>(r => new HttpResponse(r, httpHeader, new byte[0], System.Net.HttpStatusCode.Found));
}

View File

@ -84,90 +84,6 @@ namespace NzbDrone.Core.Test
dateTime.ToBestDateString().Should().Be(dateTime.ToShortDateString());
}
[Test]
public void ParentUriString_should_return_self_if_already_parent()
{
var url = "http://www.sonarr.tv";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be(url);
}
[Test]
public void ParentUriString_should_return_parent_url_when_path_is_passed()
{
var url = "http://www.sonarr.tv/test/";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be("http://www.sonarr.tv");
}
[Test]
public void ParentUriString_should_return_parent_url_when_multiple_paths_are_passed()
{
var url = "http://www.sonarr.tv/test/test2";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be("http://www.sonarr.tv");
}
[Test]
public void ParentUriString_should_return_parent_url_when_url_with_query_string_is_passed()
{
var url = "http://www.sonarr.tv/test.aspx?test=10";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be("http://www.sonarr.tv");
}
[Test]
public void ParentUriString_should_return_parent_url_when_url_with_path_and_query_strings_is_passed()
{
var url = "http://www.sonarr.tv/tester/test.aspx?test=10";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be("http://www.sonarr.tv");
}
[Test]
public void ParentUriString_should_return_parent_url_when_url_with_query_strings_is_passed()
{
var url = "http://www.sonarr.tv/test.aspx?test=10&test2=5";
var uri = new Uri(url);
var result = uri.ParentUriString();
//Resolve
result.Should().Be("http://www.sonarr.tv");
}
[Test]
public void MaxOrDefault_should_return_zero_when_collection_is_empty()
{

View File

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.Framework
{
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), TestLogger));
Mocker.SetConstant<IDroneServicesRequestBuilder>(new DroneServicesHttpRequestBuilder());
Mocker.SetConstant<ISonarrCloudRequestBuilder>(new SonarrCloudRequestBuilder());
}
}

View File

@ -15,13 +15,13 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
public class DailySeriesDataProxy : IDailySeriesDataProxy
{
private readonly IHttpClient _httpClient;
private readonly IDroneServicesRequestBuilder _requestBuilder;
private readonly IHttpRequestBuilderFactory _requestBuilder;
private readonly Logger _logger;
public DailySeriesDataProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder, Logger logger)
public DailySeriesDataProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder;
_requestBuilder = requestBuilder.Services;
_logger = logger;
}
@ -29,7 +29,10 @@ namespace NzbDrone.Core.DataAugmentation.DailySeries
{
try
{
var dailySeriesRequest = _requestBuilder.Build("dailyseries");
var dailySeriesRequest = _requestBuilder.Create()
.Resource("/dailyseries")
.Build();
var response = _httpClient.Get<List<DailySeries>>(dailySeriesRequest);
return response.Resource.Select(c => c.TvdbId);
}

View File

@ -12,17 +12,20 @@ namespace NzbDrone.Core.DataAugmentation.Scene
public class SceneMappingProxy : ISceneMappingProxy
{
private readonly IHttpClient _httpClient;
private readonly IDroneServicesRequestBuilder _requestBuilder;
private readonly IHttpRequestBuilderFactory _requestBuilder;
public SceneMappingProxy(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder)
public SceneMappingProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder;
_requestBuilder = requestBuilder.Services;
}
public List<SceneMapping> Fetch()
{
var request = _requestBuilder.Build("/scenemapping");
var request = _requestBuilder.Create()
.Resource("/scenemapping")
.Build();
return _httpClient.Get<List<SceneMapping>>(request).Resource;
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Http;
using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.DataAugmentation.Xem.Model;
@ -18,32 +19,32 @@ namespace NzbDrone.Core.DataAugmentation.Xem
public class XemProxy : IXemProxy
{
private const string ROOT_URL = "http://thexem.de/map/";
private readonly Logger _logger;
private readonly IHttpClient _httpClient;
private const string XEM_BASE_URL = "http://thexem.de/map/";
private readonly IHttpRequestBuilderFactory _xemRequestBuilder;
private static readonly string[] IgnoredErrors = { "no single connection", "no show with the tvdb_id" };
private HttpRequestBuilder _xemRequestBuilder;
public XemProxy(Logger logger, IHttpClient httpClient)
public XemProxy(IHttpClient httpClient, Logger logger)
{
_logger = logger;
_httpClient = httpClient;
_logger = logger;
_xemRequestBuilder = new HttpRequestBuilder(XEM_BASE_URL)
{
PostProcess = r => r.UriBuilder.SetQueryParam("origin", "tvdb")
};
_xemRequestBuilder = new HttpRequestBuilder(ROOT_URL)
.AddSuffixQueryParam("origin", "tvdb")
.CreateFactory();
}
public List<int> GetXemSeriesIds()
{
_logger.Debug("Fetching Series IDs from");
var request = _xemRequestBuilder.Build("/havemap");
var request = _xemRequestBuilder.Create()
.Resource("/havemap")
.Build();
var response = _httpClient.Get<XemResult<List<string>>>(request).Resource;
CheckForFailureResult(response);
@ -60,9 +61,10 @@ namespace NzbDrone.Core.DataAugmentation.Xem
{
_logger.Debug("Fetching Mappings for: {0}", id);
var request = _xemRequestBuilder.Build("/all");
request.UriBuilder.SetQueryParam("id", id);
var request = _xemRequestBuilder.Create()
.Resource("/all")
.AddQueryParam("id", id)
.Build();
var response = _httpClient.Get<XemResult<List<XemSceneTvdbMapping>>>(request).Resource;
@ -73,8 +75,10 @@ namespace NzbDrone.Core.DataAugmentation.Xem
{
_logger.Debug("Fetching alternate names");
var request = _xemRequestBuilder.Build("/allNames");
request.UriBuilder.SetQueryParam("seasonNumbers", true);
var request = _xemRequestBuilder.Create()
.Resource("/allNames")
.AddQueryParam("seasonNumbers", true)
.Build();
var response = _httpClient.Get<XemResult<Dictionary<int, List<JObject>>>>(request).Resource;

View File

@ -107,7 +107,7 @@ namespace NzbDrone.Core.Download
if (response.StatusCode == HttpStatusCode.SeeOther || response.StatusCode == HttpStatusCode.Found)
{
var locationHeader = (string)response.Headers.GetValueOrDefault("Location", null);
var locationHeader = response.Headers.GetSingleValue("Location");
_logger.Trace("Torrent request is being redirected to: {0}", locationHeader);

View File

@ -63,11 +63,6 @@ namespace NzbDrone.Core
return dateTime.ToShortDateString();
}
public static string ParentUriString(this Uri uri)
{
return uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - string.Join("", uri.Segments).Length - uri.Query.Length);
}
public static int MaxOrDefault(this IEnumerable<int> ints)
{
if (ints == null)

View File

@ -13,9 +13,9 @@ namespace NzbDrone.Core.Http
{
// torcache behaves strangely when it has query params and/or no Referer or browser User-Agent.
// It's a bit vague, and we don't need the query params. So we remove the query params and set a Referer to be safe.
if (request.Url.Host == "torcache.net")
if (request.UrlBuilder.Host == "torcache.net")
{
request.UriBuilder.Query = string.Empty;
request.UrlBuilder.Query = string.Empty;
request.Headers.Add("Referer", request.Url.Scheme + @"://torcache.net/");
}

View File

@ -48,7 +48,10 @@ namespace NzbDrone.Core.Indexers.BitMeTv
{
var request = new IndexerRequest(string.Format("{0}/rss.php?uid={1}&passkey={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.UserId, Settings.RssPasskey), HttpAccept.Html);
request.HttpRequest.AddCookie(Settings.Cookie);
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie))
{
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
}
yield return request;
}

View File

@ -172,14 +172,15 @@ namespace NzbDrone.Core.Indexers.BroadcastheNet
parameters = new BroadcastheNetTorrentQuery();
}
var builder = new JsonRpcRequestBuilder(Settings.BaseUrl, "getTorrents", new object[] { Settings.ApiKey, parameters, PageSize, 0 });
builder.SupressHttpError = true;
var builder = new JsonRpcRequestBuilder(Settings.BaseUrl)
.Call("getTorrents", Settings.ApiKey, parameters, PageSize, 0);
builder.SuppressHttpError = true;
for (var page = 0; page < maxPages;page++)
{
builder.Parameters[3] = page * PageSize;
builder.JsonParameters[3] = page * PageSize;
yield return new IndexerRequest(builder.Build(""));
yield return new IndexerRequest(builder.Build());
}
}
}

View File

@ -114,8 +114,9 @@ namespace NzbDrone.Core.Indexers.HDBits
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
{
var builder = new HttpRequestBuilder(Settings.BaseUrl);
var request = builder.Build("/api/torrents");
var request = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/api/torrents")
.Build();
request.Method = HttpMethod.POST;
const string appJson = "application/json";
@ -125,7 +126,7 @@ namespace NzbDrone.Core.Indexers.HDBits
query.Username = Settings.Username;
query.Passkey = Settings.ApiKey;
request.Body = query.ToJson();
request.SetContent(query.ToJson());
yield return new IndexerRequest(request);
}

View File

@ -137,7 +137,7 @@ namespace NzbDrone.Core.Indexers
foreach (var request in pageableRequest)
{
url = request.Url.ToString();
url = request.Url.AbsoluteUri;
var page = FetchPage(request, parser);

View File

@ -27,7 +27,7 @@ namespace NzbDrone.Core.Indexers.Newznab
throw new ApiKeyException("Invalid API key");
}
if (!indexerResponse.Request.Url.ToString().Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey")))
if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && (errorMessage == "Missing parameter" || errorMessage.Contains("apikey")))
{
throw new ApiKeyException("Indexer requires an API key");
}

View File

@ -75,30 +75,34 @@ namespace NzbDrone.Core.Indexers.Rarbg
private IEnumerable<IndexerRequest> GetPagedRequests(string mode, int? tvdbId, string query, params object[] args)
{
var requestBuilder = new HttpRequestBuilder(Settings.BaseUrl)
.Resource("/pubapi_v2.php")
.Accept(HttpAccept.Json);
var httpRequest = new HttpRequest(Settings.BaseUrl + "/pubapi_v2.php", HttpAccept.Json);
httpRequest.AddQueryParam("mode", mode);
requestBuilder.AddQueryParam("mode", mode);
if (tvdbId.HasValue)
{
httpRequest.AddQueryParam("search_tvdb", tvdbId.Value.ToString());
requestBuilder.AddQueryParam("search_tvdb", tvdbId.Value);
}
if (query.IsNotNullOrWhiteSpace())
{
httpRequest.AddQueryParam("search_string", string.Format(query, args));
requestBuilder.AddQueryParam("search_string", string.Format(query, args));
}
if (!Settings.RankedOnly)
{
httpRequest.AddQueryParam("ranked", "0");
requestBuilder.AddQueryParam("ranked", "0");
}
httpRequest.AddQueryParam("category", "18;41");
httpRequest.AddQueryParam("limit", "100");
httpRequest.AddQueryParam("token", _tokenProvider.GetToken(Settings));
httpRequest.AddQueryParam("format", "json_extended");
httpRequest.AddQueryParam("app_id", "Sonarr");
requestBuilder.AddQueryParam("category", "18;41");
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
requestBuilder.AddQueryParam("format", "json_extended");
requestBuilder.AddQueryParam("app_id", "Sonarr");
yield return new IndexerRequest(httpRequest);
}

View File

@ -266,18 +266,18 @@ namespace NzbDrone.Core.Indexers
try
{
var uri = new Uri(value, UriKind.RelativeOrAbsolute);
var url = new Uri(value, UriKind.RelativeOrAbsolute);
if (!uri.IsAbsoluteUri)
if (!url.IsAbsoluteUri)
{
uri = new Uri(_indexerResponse.HttpRequest.Url, uri);
url = new Uri(_indexerResponse.HttpRequest.Url, url);
}
return uri.AbsoluteUri;
return url.AbsoluteUri;
}
catch (Exception ex)
{
_logger.Debug(ex, string.Format("Failed to parse Uri {0}, ignoring.", value));
_logger.Debug(ex, string.Format("Failed to parse Url {0}, ignoring.", value));
return null;
}
}

View File

@ -50,7 +50,10 @@ namespace NzbDrone.Core.Indexers.TorrentRss
if (Settings.Cookie.IsNotNullOrWhiteSpace())
{
request.HttpRequest.AddCookie(Settings.Cookie);
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie))
{
request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
}
}
yield return request;

View File

@ -23,7 +23,7 @@ namespace NzbDrone.Core.Indexers.Torznab
if (code >= 100 && code <= 199) throw new ApiKeyException("Invalid API key");
if (!indexerResponse.Request.Url.ToString().Contains("apikey=") && errorMessage == "Missing parameter")
if (!indexerResponse.Request.Url.AbsoluteUri.Contains("apikey=") && errorMessage == "Missing parameter")
{
throw new ApiKeyException("Indexer requires an API key");
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Exceptions;
@ -16,20 +17,23 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
private readonly HttpRequestBuilder _requestBuilder;
public SkyHookProxy(IHttpClient httpClient, Logger logger)
private readonly IHttpRequestBuilderFactory _requestBuilder;
public SkyHookProxy(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder, Logger logger)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder.SkyHookTvdb;
_logger = logger;
_requestBuilder = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/en/");
}
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
{
var httpRequest = _requestBuilder.Build(tvdbSeriesId.ToString());
httpRequest.AddSegment("route", "shows");
var httpRequest = _requestBuilder.Create()
.SetSegment("route", "shows")
.Resource(tvdbSeriesId.ToString())
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
@ -81,9 +85,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
var term = System.Web.HttpUtility.UrlEncode((title.ToLower().Trim()));
var httpRequest = _requestBuilder.Build("?term={term}");
httpRequest.AddSegment("route", "search");
httpRequest.AddSegment("term", term);
var httpRequest = _requestBuilder.Create()
.SetSegment("route", "search")
.AddQueryParam("term", title.ToLower().Trim())
.Build();
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);

View File

@ -21,15 +21,14 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
{
var path = "/Notifications/Admin";
var request = BuildRequest(path, settings);
request.Headers.ContentType = "application/json";
request.Body = new
request.SetContent(new
{
Name = title,
Description = message,
ImageUrl = "https://raw.github.com/NzbDrone/NzbDrone/develop/Logo/64.png"
}.ToJson();
request.Headers.ContentType = "application/json";
}.ToJson());
ProcessRequest(request, settings);
}
@ -58,7 +57,7 @@ namespace NzbDrone.Core.Notifications.MediaBrowser
{
var url = string.Format(@"http://{0}/mediabrowser", settings.Address);
return new HttpRequestBuilder(url).Build(path);
return new HttpRequestBuilder(url).Resource(path).Build();
}
private void CheckForError(HttpResponse response)

View File

@ -15,20 +15,22 @@ namespace NzbDrone.Core.Update
public class UpdatePackageProvider : IUpdatePackageProvider
{
private readonly IHttpClient _httpClient;
private readonly IDroneServicesRequestBuilder _requestBuilder;
private readonly IHttpRequestBuilderFactory _requestBuilder;
public UpdatePackageProvider(IHttpClient httpClient, IDroneServicesRequestBuilder requestBuilder)
public UpdatePackageProvider(IHttpClient httpClient, ISonarrCloudRequestBuilder requestBuilder)
{
_httpClient = httpClient;
_requestBuilder = requestBuilder;
_requestBuilder = requestBuilder.Services;
}
public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
{
var request = _requestBuilder.Build("/update/{branch}");
request.UriBuilder.SetQueryParam("version", currentVersion);
request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant());
request.AddSegment("branch", branch);
var request = _requestBuilder.Create()
.Resource("/update/{branch}")
.AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.SetSegment("branch", branch)
.Build();
var update = _httpClient.Get<UpdatePackageAvailable>(request).Resource;
@ -39,10 +41,12 @@ namespace NzbDrone.Core.Update
public List<UpdatePackage> GetRecentUpdates(string branch, Version currentVersion)
{
var request = _requestBuilder.Build("/update/{branch}/changes");
request.UriBuilder.SetQueryParam("version", currentVersion);
request.UriBuilder.SetQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant());
request.AddSegment("branch", branch);
var request = _requestBuilder.Create()
.Resource("/update/{branch}/changes")
.AddQueryParam("version", currentVersion)
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
.SetSegment("branch", branch)
.Build();
var updates = _httpClient.Get<List<UpdatePackage>>(request);