En resumen: Para utilizar un proxy con HttpClient en C#, crea unWebProxy, adjúntalo a unHttpClientHandler(oSocketsHttpHandler), y pasa ese controlador alHttpClientconstructor. Para la producción, sustituye los bucles manuales porIHttpClientFactory, añadeNetworkCredentialpara los proxies autenticados y envuelve las llamadas en reintentos con Polly para que las IP inactivas no bloqueen tu worker.
Introducción
Si alguna vez has intentado rastrear un sitio web, te has topado con una API bloqueada por región o has sometido a pruebas de estrés un servicio desde múltiples direcciones IP de salida, ya sabes por qué estamos aquí. Esta guía explica cómo utilizar un proxy con HttpClient en C#, desde una configuración de cinco líneas WebProxy hasta un grupo de rotación que no tiene fugas de sockets.
Un proxy HttpClient es simplemente un HttpClient cuyo controlador está configurado con una WebProxy, de modo que las solicitudes salientes se canalizan a través de una IP intermediaria en lugar de ir directamente al destino. Esa es toda la abstracción. Todo lo demás —autenticación, SOCKS5, validación SSL, rotación, reintentos— es configuración en torno a esa idea central.
Daremos por hecho que estás familiarizado con async/await y la dotnet CLI en una versión reciente de .NET. No daremos por hecho que hayas leído el código fuente de SocketsHttpHandler. Al final tendrás patrones que podrás copiar y pegar para proxies sin autenticación, proxies con autenticación, SOCKS5, rotación con IHttpClientFactory, validación TLS segura cuando hay un proxy en la ruta y una tabla de resolución de problemas para los errores que inevitablemente encontrarás en producción. También hay una matriz de decisión al final para que puedas dejar de mantener tu propio grupo cuando ya no merezca la pena el esfuerzo. Si buscas una visión más amplia del scraping, nuestra guía introductoria sobre cómo crear un scraper web con C# combina muy bien con esta.
Un modelo mental sobre cómo utilizar un proxy con HttpClient en C#
Antes de escribir código, asegúrate de que la estructura de capas es la correcta. HttpClient es una envoltura ligera. El transporte real, incluida la resolución del proxy, reside en su controlador. En el .NET moderno, ese controlador es HttpClientHandler (la fachada compatible con sistemas heredados) o SocketsHttpHandler (el motor subyacente). Ambos exponen una Proxy propiedad de tipo IWebProxy, y la implementación integrada es WebProxy.
El flujo es el siguiente:
HttpClient
|
v
HttpMessageHandler (HttpClientHandler / SocketsHttpHandler)
| Proxy = IWebProxy
v
WebProxy -> proxy server -> upstream targetDe esta estratificación se derivan dos consecuencias. En primer lugar, el proxy está vinculado al controlador, no al cliente. No se puede modificar HttpClient.Proxy, porque no existe tal propiedad. Si quieres un proxy diferente, necesitas un controlador diferente y, por lo tanto, un HttpClient (o, mejor aún, una fábrica que los distribuya por ti).
En segundo lugar, si no asignas un controlador, .NET recurrirá a la resolución de proxy predeterminada del sistema, incluyendo variables de entorno como HTTPS_PROXY. Eso resulta conveniente en el portátil de un desarrollador y sorprendente en un contenedor, así que volveremos más adelante a la opción de excluirse. Lo mismo ocurre si un compañero configura HttpClient.DefaultProxy en algún lugar del código de inicio compartido: todos los clientes creados a partir de entonces lo heredan a menos que se anule el manejador.
A lo largo de este artículo tratamos cualquier proxy operativo como http://host:port, con credenciales opcionales. Siempre que veas a continuación cómo usar un proxy con HttpClient en C#, ese es el patrón en torno al cual estamos configurando.
Configuración de un proyecto C# mínimo para pruebas de proxy
Confirma tu cadena de herramientas y, a continuación, crea una aplicación de consola. Estamos ejecutando todo en un SDK de .NET LTS reciente (los ejemplos se escribieron para .NET 8 y se comportan de la misma manera en versiones posteriores en el momento de escribir este artículo).
dotnet --version # expect 8.x or newer
mkdir httpclient-proxy && cd httpclient-proxy
dotnet new consoleAbre la carpeta en cualquier editor. Vamos a mantener las cosas en Program.cs para mayor claridad. Haz Main asíncrono para que podamos await llamadas HTTP sin .Result problemas:
using System.Net.Http;
static async Task Main()
{
using var client = new HttpClient();
var direct = await client.GetStringAsync("https://api.ipify.org");
Console.WriteLine($"Direct IP: {direct}");
}api.ipify.org es el punto final de eco de IP más económico que existe. Ejecuta dotnet run, anota la IP y guarda esta referencia. Una vez que conectes un proxy, esta misma llamada debería mostrar la IP de salida del proxy en lugar de la tuya. Si no es así, tienes un error de configuración, no un error de red.
Configuración de un WebProxy sin autenticación con HttpClientHandler
Empieza por el caso más sencillo: un proxy gratuito o local que no requiera credenciales. La receta para usar un proxy con HttpClient en C# consiste en tres objetos, en este orden: WebProxy, HttpClientHandler, HttpClient.
using System.Net;
using System.Net.Http;
var proxy = new WebProxy("http://203.0.113.10:8080")
{
BypassProxyOnLocal = true, // skip the proxy for localhost/loopback
UseDefaultCredentials = false // do not silently send Windows creds
};
var handler = new HttpClientHandler
{
Proxy = proxy,
UseProxy = true
};
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(15) };
var ip = await client.GetStringAsync("https://api.ipify.org");
Console.WriteLine($"Proxied IP: {ip}");Algunos detalles que es fácil pasar por alto. El WebProxy constructor acepta un Uri una cadena, y el esquema es importante: http:// para proxies HTTP y HTTPS que utilizan CONNECT, y (como veremos más adelante) socks5:// para SOCKS. Si incluyes credenciales en la URL, WebProxy las ignorará, así que no te molestes. BypassProxyOnLocal = true es un valor predeterminado útil; te evita enviar accidentalmente comprobaciones de estado a través de una IP externa. UseDefaultCredentials = false evita que Windows envíe automáticamente la identidad del usuario actual a un proxy de terceros, que es el tipo de error que solo se detecta cuando una revisión de seguridad analiza tus capturas de paquetes.
Este es el patrón canónico. Todo lo demás en esta guía son variaciones de la misma receta de tres objetos. Si quieres una explicación más detallada sobre qué tipos de proxy tienen sentido para tareas de scraping en concreto, la guía práctica sobre los mejores tipos de proxy para el scraping web es una buena lectura complementaria.
Proxies de autenticación: NetworkCredential, errores 407 y PreAuthenticate
La mayoría de los proxies de pago requieren autenticación. La forma de expresar eso en .NET es NetworkCredential, adjunta al WebProxy , no al controlador:
var proxy = new WebProxy("http://gateway.example.com:8080")
{
Credentials = new NetworkCredential("my-user", "my-pass")
};
var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true };
using var client = new HttpClient(handler);Hay dos errores que hacen tropezar a casi todo el mundo la primera vez.
No pongas las credenciales en la URL. new WebProxy("http://user:pass@host:8080") omitirá silenciosamente la user:pass . El segmento de información del usuario se extrae de la Uri pero nunca se utiliza como credenciales de proxy. Pasa siempre un NetworkCredential.
PreAuthenticate es para el destino, no para el proxy. Cuando el proxy te rechaza, devuelve un HTTP 407 Proxy Authentication Required. HttpClient lo muestra como un HttpRequestException. Activar HttpClientHandler.PreAuthenticate = true no cambia este comportamiento, ya que ese indicador controla si el servidor de destino recibe un encabezado preventivo Authorization en las solicitudes posteriores. No tiene nada que ver con el Proxy-Authorization , que el controlador gestiona por su cuenta una vez que se establece Credentials.
Si sigues recibiendo el error 407 con credenciales que parecen correctas, comprueba tres cosas por orden: si las estás enviando al host correcto (algunos proveedores separan el plano de control de la puerta de enlace), si tu contraseña está codificada en URL en algún punto anterior y si tu cuenta sigue activa. Nuestro artículo sobre errores comunes de estado de proxy y cómo identificarlos profundiza en el tema si necesitas una guía sobre la gama más amplia de errores de proxy.
Elección de un protocolo: proxies HTTP, HTTPS y SOCKS5 en C#
HttpClient no distingue si el destino es HTTP o HTTPS, pero el protocolo del proxy sí importa porque cambia la forma en que se abre la conexión.
- Proxy HTTP (
http://...): para destinos HTTP, el proxy puede leer y reescribir la solicitud. Para destinos HTTPS, el cliente emite unCONNECTy tuneliza TLS de extremo a extremo a través del proxy. - Proxy con terminación HTTPS: un caso especial en el que el proxy presenta su propio certificado TLS a tu cliente y abre una conexión TLS independiente en el sentido ascendente. Así es como funcionan algunas API de scraping comerciales en modo proxy. Tratamos las implicaciones de SSL en la sección dedicada más abajo.
- Proxy SOCKS (
socks5://,socks4://,socks4a://): un túnel de capa de transporte que no entiende HTTP. Todo lo que se pueda enviar a un socket TCP pasa a través de él.
En .NET moderno, SocketsHttpHandler viene con soporte integrado para SOCKS4, SOCKS4a y SOCKS5 (añadido con .NET 6, según el gestor de incidencias del tiempo de ejecución; compruébalo con la documentación de SocketsHttpHandler si estás en una versión preliminar que no sea LTS). La configuración es la misma WebProxy receta, con un esquema diferente:
var socks = new WebProxy("socks5://socks.example.com:1080")
{
Credentials = new NetworkCredential("u", "p")
};
var handler = new SocketsHttpHandler { Proxy = socks, UseProxy = true };
using var client = new HttpClient(handler);Si necesitas utilizar un proxy con HttpClient en C# para un punto final SOCKS, este es el patrón. SOCKS5 con autenticación mediante nombre de usuario y contraseña es el estándar de facto para los proveedores residenciales; SOCKS4 es principalmente heredado.
Rotación limpia de proxies con IHttpClientFactory
La rotación de IP es donde la mayoría de los tutoriales fallan discretamente. El enfoque ingenuo es el siguiente:
// DO NOT DO THIS in a real worker
foreach (var url in proxyUrls)
{
var handler = new HttpClientHandler { Proxy = new WebProxy(url) };
var client = new HttpClient(handler); // never disposed
var html = await client.GetStringAsync(target);
}Ese código tiene fugas de sockets. Cada HttpClient mantiene vivo su controlador, y cada controlador retiene un grupo de conexiones subyacente. Pon en marcha unos cuantos miles de esos en un bucle y agotarás los puertos efímeros, lo que en Linux se manifiesta como SocketException: Address already in use y en Windows como trazas realmente creativas WinHttpException .
La solución es IHttpClientFactory. Gestiona la vida útil de los controladores por ti, los recicla según una programación y te permite registrar un cliente con nombre o tipo por proxy. Una pequeña configuración de DI tiene este aspecto:
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
foreach (var p in proxyPool)
{
services.AddHttpClient(p.Name, c => c.Timeout = TimeSpan.FromSeconds(20))
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
Proxy = new WebProxy(p.Url) { Credentials = p.Creds },
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2)
});
}
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IHttpClientFactory>();Ahora puedes elegir un proxy por solicitud sin filtrar nada:
var rng = new Random();
async Task<string> FetchAsync(string url)
{
var pick = proxyPool[rng.Next(proxyPool.Count)];
var client = factory.CreateClient(pick.Name);
return await client.GetStringAsync(url);
}El round-robin es un cambio de una sola línea: mantén un Interlocked.Increment contador y aplícale el módulo a proxyPool.Count. De cualquier manera, cada solicitud llega a un controlador válido y del grupo, y IHttpClientFactory rota los controladores subyacentes en PooledConnectionLifetime, lo que evita el problema de la caducidad del DNS de larga duración que trataremos en la siguiente sección. Si quieres un repaso más amplio de los patrones de rotación y cuándo usar cada uno, nuestro análisis en profundidad sobre proxies rotativos cubre el aspecto algorítmico en detalle. Ten en cuenta que IHttpClientFactory los detalles de la API, incluidas las duraciones de la inyección de dependencias (DI) y la integración con Polly, pueden variar entre versiones principales; comprueba la página de Microsoft Learn sobre IHttpClientFactory si estás fijando un tiempo de ejecución anterior.
SocketsHttpHandler frente a HttpClientHandler para producción
En el .NET moderno (Core 2.1+ y todos los dotnet new proyecto actual), HttpClientHandler es principalmente un shim de compatibilidad que delega SocketsHttpHandler en segundo plano. Para la mayoría de las demostraciones, ambos son intercambiables. Para trabajadores y rastreadores de larga duración, es preferible SocketsHttpHandler directamente porque expone los controles que importan:
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://proxy:8080"),
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2), // recycle TCP/TLS
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
ConnectTimeout = TimeSpan.FromSeconds(10), // fail fast on dead proxies
AutomaticDecompression = System.Net.DecompressionMethods.All
};Los dos ajustes que vale la pena recordar:
PooledConnectionLifetimecontrola cuánto tiempo puede permanecer activa una conexión en el grupo antes de cerrarse. La implicación en el proxy o la actualización del DNS es la verdadera razón por la que se configura esto. UnHttpClientmantendrá una única conexión TCP para siempre de forma predeterminada, y los cambios de DNS en el servidor de origen (algo muy común en los puntos finales residenciales rotativos) nunca se detectarán. Dos minutos es un valor predeterminado razonable para los rastreadores.ConnectTimeoutes un límite máximo independiente deHttpClient.Timeout. Este último abarca toda la solicitud, mientras que el primero solo cubre el handshake TCP con el proxy. Establecerlo en un valor bajo (de 5 a 10 segundos) es la forma más económica de evitar que los proxies inactivos monopolicen los subprocesos de trabajo.
AutomaticDecompression no tiene relación con los proxies, pero es lo suficientemente útil como para mencionarlo aquí, ya que la mayoría de los puntos finales de scraping comprimen las respuestas con gzip. La semántica de las propiedades en torno a PooledConnectionLifetime y similares varían entre las principales versiones de tiempo de ejecución, así que comprueba la documentación si utilizas .NET 6 o 7.
Gestionar correctamente la validación SSL/TLS cuando hay un proxy en la ruta
Un proxy en la ruta de la solicitud complica el TLS, pero la regla es sencilla: nunca desactives la validación de forma predeterminada. DangerousAcceptAnyServerCertificateValidator existe porque Microsoft quería dejar claro que, si lo configuras, estás aceptando certificados falsificados. En proxies gratuitos o compartidos, eso es literalmente una vulnerabilidad de tipo «man-in-the-middle» a la espera de ser aprovechada por quienquiera que gestione el proxy.
Hay dos casos distintos que hay que diferenciar.
Los túneles CONNECT y SOCKS transportan tus bytes TLS de extremo a extremo. El certificado que ves es el certificado real del sitio de destino. La validación debe permanecer activada, y punto. Si se produce un error de handshake SSL aquí, el proxy está mal configurado o el certificado de origen es realmente incorrecto. No lo pases por alto.
Los proxies con terminación TLS (algunas API de scraping funcionan en este modo) completan intencionadamente el handshake ellos mismos y presentan su propio certificado. En ese caso, aceptar una CA desconocida forma parte del acuerdo, pero solo para ese proxy específico. El patrón seguro es una huella digital o una llamada de retorno con CA fijada:
var expectedThumbprint = "AABBCCDDEEFF00112233445566778899AABBCCDD";
var handler = new SocketsHttpHandler
{
Proxy = new WebProxy("http://tls-terminating-proxy:8080"),
SslOptions = new System.Net.Security.SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
{
if (cert is null) return false;
return string.Equals(cert.GetCertHashString(),
expectedThumbprint,
StringComparison.OrdinalIgnoreCase);
}
}
};Esto sigue siendo una flexibilización, pero limitada: solo se acepta el certificado que coincida con la huella digital fijada, y el resto del mundo sigue teniendo que pasar la validación normal de la cadena. Si estás realizando scraping a gran escala y necesitas usar un proxy con HttpClient en C# frente a una puerta de enlace con terminación TLS, esta es la configuración segura para producción.
Reintentos, tiempos de espera y retroceso exponencial con Polly
Los proxies fallan. Las IP residenciales se desconectan a mitad de sesión, los rangos de los centros de datos se redirigen a nulo, los destinos upstream te limitan la velocidad durante diez minutos y luego vuelven. La respuesta correcta es reintentar con retroceso, no bloquear el worker.
En Polly moderno (v8+), la API es ResiliencePipelineBuilder. Combina un tiempo de espera corto con un presupuesto de reintentos reducido para que un proxy inactivo falle rápidamente y uno inestable tenga una segunda oportunidad:
using Polly;
using Polly.Retry;
using Polly.Timeout;
var pipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
.AddRetry(new RetryStrategyOptions<HttpResponseMessage>
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.Handle<TaskCanceledException>()
.HandleResult(r => (int)r.StatusCode >= 500 || (int)r.StatusCode == 408)
})
.AddTimeout(TimeSpan.FromSeconds(15))
.Build();
var response = await pipeline.ExecuteAsync(
async ct => await client.GetAsync(target, ct));Tres consejos de calibración. Mantén MaxRetryAttempts pequeño (tres es suficiente); un proxy inestable rara vez merece un cuarto intento. UseJitter = true importa cuando se ejecutan cientos de trabajadores en paralelo, de lo contrario todos reintentan al unísono y saturan el mismo backend. Y no incluyas 407 en la lista de reintentos, porque si las credenciales son incorrectas una vez, también lo serán en el siguiente intento, y solo agotarás tu presupuesto más rápido. Verifica la superficie de v8 con la documentación de Polly si estás actualizando desde la v7, ya que varios nombres de clases han cambiado y el estilo de la v7 Policy.HandleAsync no se compila con los nuevos constructores.
Selección de proxy por solicitud y reglas de omisión
Un único proxy estático funciona para proyectos de aficionado. En el momento en que empiezas a mezclar tráfico interno y externo, o a enrutar diferentes dominios a través de diferentes IP de salida, necesitas la selección por solicitud. WebProxy te ofrece dos opciones: BypassList y una IWebProxy .
BypassList acepta patrones de expresiones regulares. Todo lo que coincida omite el proxy por completo, que es la forma de mantener los nombres de host internos y los rangos CIDR privados fuera del salto externo:
var proxy = new WebProxy("http://proxy:8080")
{
BypassProxyOnLocal = true,
BypassList = new[] { @"^.*\.internal\.example\.com$", @"^10\.0\.0\..*$" }
};Para un enrutamiento real por host, implementa IWebProxy tú mismo:
sealed class HostBasedProxy : IWebProxy
{
public ICredentials? Credentials { get; set; }
public Uri? GetProxy(Uri destination) =>
destination.Host.EndsWith("google.com") ? new Uri("http://us-proxy:8080")
: destination.Host.EndsWith("yandex.ru") ? new Uri("http://eu-proxy:8080")
: null;
public bool IsBypassed(Uri host) => GetProxy(host) is null;
}Eso es suficiente para gestionar el enrutamiento geográfico desde un único HttpClient. El controlador llama GetProxy para cada solicitud, por lo que la decisión es dinámica y no necesitas un cliente independiente por región.
Depuración de errores comunes del proxy HttpClient
Cuando algo sale mal, la excepción rara vez se explica por sí misma. La forma más rápida de solucionarlo es partir de los síntomas.
|
Síntoma (lo que ves) |
Causa probable |
Solución en una línea |
|---|---|---|
|
|
Credenciales de proxy incorrectas o ausentes |
Configurar |
|
Estado |
El proxy está activo, el servidor de origen está inactivo o te está limitando la velocidad |
Reintentar con retroceso; cambiar a una IP diferente tras el segundo fallo |
|
|
El proxy ha rechazado |
Confirma |
|
|
Proxy con terminación TLS sin un validador fijado, o un certificado realmente defectuoso |
Fijar una huella digital con |
|
|
La consulta DNS ha fallado dentro del proxy o para el propio proxy |
Verifica el nombre de host; en clientes de larga duración, configura |
|
La solicitud se queda colgada hasta que |
Proxy inactivo, redireccionamiento infinito o bloqueo |
Establece |
|
Esporádico |
Fuga de controladores de los nuevos |
Pasar a |
En caso de duda, registra la URL del proxy junto con la excepción. La mitad de los errores de proxy desaparecen en el momento en que puedes ver qué IP falló realmente, en lugar de tener que adivinar entre un grupo de cincuenta.
Elegir la estrategia de proxy adecuada para tu carga de trabajo
No existe una respuesta universal sobre cómo utilizar un proxy con HttpClient en C# a gran escala. Solo hay que plantearse cuánta ingeniería quieres dedicar a la capa de proxy frente a la capa de datos. Elige la opción más sencilla que aún cumpla con tus requisitos de fiabilidad.
|
Estrategia |
Fiabilidad |
Sobrecarga de mantenimiento |
Segmentación geográfica |
Cuándo elegirlo |
|---|---|---|---|---|
|
Proxies públicos gratuitos |
Muy bajos; muchos son trampas |
Alto; rotación constante |
Ninguna |
Nunca para producción. Solo para experimentos locales. |
|
Proxies estáticos autenticados de centros de datos |
Aceptable para objetivos benignos |
Bajo |
Limitado |
API B2B, herramientas internas, desbloqueo geográfico ligero |
|
Rotación DIY sobre un pool residencial |
Alto si está bien diseñado |
Alto; te encargas de los reintentos, comprobaciones de estado, sesiones persistentes y contabilidad |
Sí, si tu proveedor expone etiquetas de país |
Equipos con ganas de gestionar su propio grupo de servidores y presupuesto para la ingeniería |
|
API de scraping/proxy gestionada |
Alto; el proveedor absorbe los fallos |
Bajo; se llama a un único punto final |
Sí, normalmente por país |
Scraping a gran escala, objetivos anti-bot, equipos pequeños |
Una prueba práctica útil: si el código de proxy en tu repositorio crece más rápido que el código de análisis, estás pagando a ingenieros para que sean una versión peor de un proveedor gestionado. Sube en la pila. Por el contrario, si solo necesitas unas pocas IP estáticas para comunicarte con la API de un socio, no lo compliques en exceso; una sola autenticada WebProxy es suficiente.
Escalabilidad más allá del «hazlo tú mismo»: enrutamiento de HttpClient a través del modo proxy de WebScrapingAPI
Cuando las cuentas del «hazlo tú mismo» ya no cuadran, la salida más limpia es mantener tu HttpClient y simplemente apuntarlo a un punto final de proxy gestionado. WebScrapingAPI expone una puerta de enlace en modo proxy que toma la misma WebProxy + NetworkCredential receta que ya ha escrito, con la rotación, la segmentación geográfica y el manejo antibots absorbidos en el lado del servidor.
var proxy = new WebProxy("http://proxy.webscrapingapi.com:80")
{
Credentials = new NetworkCredential(
"YOUR_API_KEY", // username slot
"render_js=false.country=us" // password slot carries options
)
};
var handler = new SocketsHttpHandler
{
Proxy = proxy,
UseProxy = true,
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
ConnectTimeout = TimeSpan.FromSeconds(15)
};
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(60) };
var html = await client.GetStringAsync("https://example.com/product/42");La estructura es idéntica al patrón de proxy autenticado de antes en el artículo; la clave API va en el campo de nombre de usuario y las opciones de solicitud en el de contraseña. No hay bucle de rotación porque la pasarela elige una nueva IP de salida por cada solicitud, no hay canal de reintentos porque las respuestas fallidas no se facturan, y no hay lógica geográfica en tu código porque la selección de país es un indicador. Aún puedes mantener tu canal de Polly si quieres una defensa en profundidad, pero la superficie que mantienes se reduce drásticamente. Considera esto como una opción más en la matriz anterior, no como un veredicto; es la decisión correcta cuando tu equipo es pequeño y los objetivos son hostiles.
Puntos clave
- Configura el controlador, no el cliente. Un proxy vive en
HttpClientHandleroSocketsHttpHandlera través deWebProxy.HttpClientpor sí mismo no tieneProxypropiedad, por lo que no se puede modificar en tiempo de ejecución. - Pasa siempre las credenciales a través de
NetworkCredential. La inclusiónuser:pass@en la URL del proxy se omite de forma silenciosa y es la causa más común de misteriosos errores 407. - Utilice
IHttpClientFactorypara la rotación. Unforeachbucle que actualice unHttpClientpor proxy provocará fugas de sockets bajo carga. Clientes con nombre por proxy, además dePooledConnectionLifetime, soluciona el problema. - Es preferible
SocketsHttpHandlerdirectamente en producción. OfreceConnectTimeout,PooledConnectionLifetimey la compatibilidad con SOCKS5, todo lo cual acabarás necesitando. - No desactives la validación TLS. Para los proxies con terminación TLS, fija una huella digital. Para los túneles CONNECT o SOCKS, deja la validación activada; los fallos allí son errores reales, no ruido.
Preguntas frecuentes: preguntas sobre el proxy de HttpClient que realmente se hacen los desarrolladores
¿HttpClient detecta automáticamente el proxy del sistema o la variable de entorno HTTPS_PROXY, y cómo puedo desactivarlo?
Sí. Si no hay ningún controlador asignado, HttpClient utiliza el proxy predeterminado del sistema, que en .NET Core 3.1+ también lee HTTP_PROXY, HTTPS_PROXY, y NO_PROXY en Linux y macOS. Para desactivarlo, pasa un controlador explícito con UseProxy = false, o configura HttpClient.DefaultProxy = new WebProxy() al iniciar.
¿Puedo cambiar el proxy en una instancia existente de HttpClient, o necesito una nueva?
Necesitas un nuevo cliente. El proxy se vincula al controlador en el momento de la construcción, y HttpClient no expone un Proxy setter. Utiliza un grupo de clientes preconfigurados proporcionados por IHttpClientFactory, o uno personalizado IWebProxy cuyo GetProxy(Uri) decida dinámicamente mientras el controlador permanece el mismo.
¿Por qué mi solicitud devuelve el código 407 «Proxy Authentication Required» aunque haya establecido las credenciales?
Tres sospechosos habituales: credenciales incrustadas en la URL (eliminadas silenciosamente por WebProxy), una contraseña codificada en la URL dos veces en algún punto anterior, o credenciales asignadas a HttpClientHandler.Credentials en lugar de WebProxy.Credentials. Solo esta última opción alimenta al proxy. PreAuthenticate no sirve de ayuda aquí; esa bandera controla el servidor de destino.
¿Admite HttpClient proxies SOCKS5 en .NET 6 y versiones posteriores?
Sí. SocketsHttpHandler Se ha añadido compatibilidad nativa con SOCKS4, SOCKS4a y SOCKS5 a partir de .NET 6. Utiliza una socks5://host:port URI en tu WebProxy y asigne las credenciales mediante NetworkCredential si el servidor SOCKS requiere autenticación de usuario/contraseña.
¿Cuál es la forma correcta de cancelar una solicitud proxy lenta sin que se pierdan los sockets?
Pasa un CancellationToken desde un CancellationTokenSource con un tiempo de espera razonable, y deja que la solicitud se desarrolle en OperationCanceledException. Empareja el token con SocketsHttpHandler.ConnectTimeout para que el handshake TCP falle rápidamente y el socket vuelva al pool en lugar de quedar colgado.
Conclusión
Eso es prácticamente todo lo que necesitas saber sobre cómo utilizar un proxy con HttpClient en C# sin meterte en un callejón sin salida. La estructura de la solución apenas cambia de una demostración de cinco líneas a un trabajador de producción: un WebProxy, un controlador, un HttpClient. Lo que cambia es todo lo que lo rodea. El código de producción utiliza IHttpClientFactory para que los controladores se reciclen, establece un ConnectTimeout para que los proxies inactivos fallen rápidamente, fija las huellas digitales de TLS en lugar de desactivar la validación y envuelve las solicitudes en un pipeline de Polly para que los fallos transitorios no despierten a nadie a las 3 de la madrugada.
La matriz de decisión que aparece al principio del artículo es la conclusión más importante. Los proxies gratuitos no son gratuitos una vez que se tiene en cuenta el tiempo de ingeniería. Los proxies estáticos de centros de datos son estupendos hasta que tu objetivo implementa una pila antibots seria. La rotación DIY es gratificante de construir y cara de mantener. Las API de proxy gestionadas cambian un presupuesto de créditos por el tiempo que, de otro modo, dedicarías a comprobaciones de estado, reintentos y gestión de abusos.
Si tu equipo ha llegado al punto en el que la capa de proxy consume más tiempo que la capa de análisis, el punto final del proxy de WebScrapingAPI encaja en la misma WebProxy + NetworkCredential receta que ya tienes, y solo pagas por las respuestas exitosas. Sea cual sea el camino que elijas, mantén la abstracción limpia: el controlador en la parte inferior, los reintentos en el medio y tu lógica de negocio en la parte superior. Tu yo del futuro te agradecerá a tu yo del presente por la separación.




