Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proxy provider implementations #23

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 364 additions & 0 deletions WebReaper/API.md

Large diffs are not rendered by default.

155 changes: 155 additions & 0 deletions WebReaper/API.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions WebReaper/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;

namespace WebReaper.Extensions;

internal static class EnumerableExtensions
{
public static IEnumerable<U> SelectTruthy<T, U>(this IEnumerable<T> enumerable, Func<T, U?> predicate)
where U : class
{
foreach (var item in enumerable)
{
if (predicate(item) is { } result)
{
yield return result;
}
}
}

public static IEnumerable<T> SelectTruthy<T>(this IEnumerable<T?> enumerable)
{
foreach (var item in enumerable)
{
if (item is { } result)
{
yield return result;
}
}
}

public static IEnumerable<U> SelectTruthy<T, U>(this IEnumerable<T> enumerable, Func<T, U?> predicate) where U : struct
{
foreach (var item in enumerable)
{
if (predicate(item) is { } result)
{
yield return result;
}
}
}

public static IEnumerable<T> SelectTruthy<T>(this IEnumerable<T?> enumerable) where T : struct
{
foreach (var item in enumerable)
{
if (item is { } result)
{
yield return result;
}
}
}

public static T ChooseRandom<T>(this IEnumerable<T> enumerable, Random? random = null)
{
random ??= Random.Shared;
if (enumerable.TryGetNonEnumeratedCount(out var count))
{
var index = random.Next(count);
return enumerable.ElementAt(index);
}
else
{
var list = enumerable.ToList();
return list[random.Next(list.Count)];
}
}
}
15 changes: 15 additions & 0 deletions WebReaper/Proxy/Abstract/IProxyProposalProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Net;

namespace WebReaper.Proxy.Abstract;

/// <summary>
/// Supplies a list of unvalidated proxies.
/// </summary>
public interface IProxyProposalProvider
{
/// <summary>
/// Returns a list of potential proxies, which may or may not be valid.
/// </summary>
Task<IEnumerable<WebProxy>> GetProxiesAsync(CancellationToken cancellationToken = default);
}
17 changes: 17 additions & 0 deletions WebReaper/Proxy/Abstract/IProxyProposalValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Net;
using WebReaper.Proxy.Concrete;

namespace WebReaper.Proxy.Abstract;

/// <summary>
/// Validates a proposed proxy.
/// </summary>
public interface IProxyProposalValidator
{
/// <summary>
/// Validates a proposed proxy.
/// </summary>
/// <returns>A <see cref="ProxyProposalValidationResult"/> indicating whether the proxy is valid or invalid, or the validator does not apply to the result.</returns>
Task<ProxyProposalValidationResult> ValidateAsync(WebProxy proxy, CancellationToken cancellationToken = default);
}
6 changes: 6 additions & 0 deletions WebReaper/Proxy/Abstract/IProxyProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

namespace WebReaper.Proxy.Abstract;

/// <summary>
/// Provides a validated proxy.
/// </summary>
public interface IProxyProvider
{
/// <summary>
/// Returns a validated proxy.
/// </summary>
Task<WebProxy> GetProxyAsync();
}
15 changes: 15 additions & 0 deletions WebReaper/Proxy/Abstract/IValidatedProxyListProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Net;

namespace WebReaper.Proxy.Abstract;

/// <summary>
/// Supplies a list of validated, ready to use proxies.
/// </summary>
public interface IValidatedProxyListProvider
{
/// <summary>
/// Returns a list of validated proxies.
/// </summary>
Task<IEnumerable<WebProxy>> GetProxiesAsync(CancellationToken cancellationToken = default);
}
74 changes: 74 additions & 0 deletions WebReaper/Proxy/Concrete/PingTimeoutProxyProposalValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Net;
using Microsoft.Extensions.Options;
using WebReaper.Proxy.Abstract;

namespace WebReaper.Proxy.Concrete;

/// <summary>
/// Options for <see cref="PingTimeoutProxyProposalValidator"/>.
/// </summary>
public sealed class PingTimeoutValidatorOptions : IOptions<PingTimeoutValidatorOptions>
{
/// <summary>
/// The URL to visit to validate the proxy.
/// </summary>
public Uri ProbeUrl { get; set; } = new("https://www.cloudflare.com/");
/// <summary>
/// The maximum time to wait for a response from the probe URL.
/// </summary>
public TimeSpan ProbeTimeout { get; set; } = TimeSpan.FromSeconds(5);
PingTimeoutValidatorOptions IOptions<PingTimeoutValidatorOptions>.Value => this;
}

/// <summary>
/// Validates a proxy by requesting a URL and waiting for a response.
/// </summary>
public sealed class PingTimeoutProxyProposalValidator : IProxyProposalValidator
{
private readonly PingTimeoutValidatorOptions _options;

/// <summary>
/// Initializes a new instance of the <see cref="PingTimeoutProxyProposalValidator"/> class.
/// </summary>
public PingTimeoutProxyProposalValidator(IOptions<PingTimeoutValidatorOptions> options)
{
_options = options.Value;
}
/// <inheritdoc/>

public async Task<ProxyProposalValidationResult> ValidateAsync(WebProxy proxy, CancellationToken cancellationToken = default)
{
using HttpMessageHandler h = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true
};
using var client = new HttpClient(h, false)
{
Timeout = _options.ProbeTimeout
};
try
{
var response = await client.GetAsync(_options.ProbeUrl, cancellationToken);
response.EnsureSuccessStatusCode();
return ProxyProposalValidationResult.Valid();
}
catch (AggregateException ex)
{
if (ex.InnerExceptions.All(ex => ex is OperationCanceledException))
{
return default;
}
return ProxyProposalValidationResult.Invalid(ex);
}
catch (OperationCanceledException)
{
return default;
}
catch (Exception ex)
{
return ProxyProposalValidationResult.Invalid(ex);
}
}
}
Loading