# C# HttpClient 代理接入示例

使用 .NET HttpClient、WebProxy、代理账密和请求超时完成 HTTPS 访问。

.NET 应用可以通过 `HttpClientHandler.Proxy` 绑定 `WebProxy`。示例将代理地址和凭据拆开读取，便于凭据轮换和日志脱敏。

## 前置条件

- 调用方项目使用支持 `HttpClient` 的当前 .NET 运行时。
- 通过受控环境注入 `PROXY_HOST`、`PROXY_PORT`、`PROXY_USERNAME` 和 `PROXY_PASSWORD`。
- 长生命周期服务通过依赖注入或客户端工厂管理连接复用。

## 完整示例

```csharp
using System.Net;

var proxyHost = Environment.GetEnvironmentVariable("PROXY_HOST")
    ?? throw new InvalidOperationException("Missing PROXY_HOST");
var proxyPort = Environment.GetEnvironmentVariable("PROXY_PORT")
    ?? throw new InvalidOperationException("Missing PROXY_PORT");
var proxyUsername = Environment.GetEnvironmentVariable("PROXY_USERNAME")
    ?? throw new InvalidOperationException("Missing PROXY_USERNAME");
var proxyPassword = Environment.GetEnvironmentVariable("PROXY_PASSWORD")
    ?? throw new InvalidOperationException("Missing PROXY_PASSWORD");

var proxy = new WebProxy(new Uri($"http://{proxyHost}:{proxyPort}"))
{
    Credentials = new NetworkCredential(proxyUsername, proxyPassword),
};
using var handler = new HttpClientHandler
{
    Proxy = proxy,
    UseProxy = true,
};
using var client = new HttpClient(handler)
{
    Timeout = TimeSpan.FromSeconds(30),
};

using var response = await client.GetAsync("https://<public-test-host>/ip");
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
```

## 预期结果

- HTTPS 请求通过配置的 HTTP 代理建立隧道。
- `EnsureSuccessStatusCode` 在目标返回错误状态时抛出异常。
- 生产服务复用客户端及处理器，不在高频请求中重复创建连接池。

## 错误处理

- 407 检查 `NetworkCredential` 的用户名、密码和当前权益。
- `TaskCanceledException` 需要结合取消令牌判断是主动取消还是请求超时。
- `HttpRequestException` 的状态与内部异常应分层记录，但不得包含凭据。

## 安全提示

不要把 `WebProxy`、环境变量或完整异常请求序列化到日志。凭据更新后应淘汰旧处理器和连接，确保后续请求使用新身份。

html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}
