关于在httpclient中使用代理的问题

我的项目是.net 7.0,使用httpclient访问互联网上的rest api服务,但是这个服务需要通过代理服务器才能访问,于是我设置了代理,代码如下

var webProxy = new WebProxy("address", false);
var proxyHttpClientHandler = new HttpClientHandler
                {
                    Proxy = webProxy,
                    UseProxy = true
                };
                
ICredentials credential = new NetworkCredential("user001", "psw");
webProxy.Credentials = credential;
httpClient = new HttpClient(proxyHttpClientHandler);

通过get方法抓取测试网页可以正常返回html内容,说明代理工作正常。
通过post方法,调用rest api则经常失败(不是每次都失败,这点也不太理解),提示“连接被断开”错误。
检查代理服务器日志发现,每次post,实际产生两个请求,一个是所谓的preflight request,对于这个请求,代理服务器日志显示407错误,并且无访问者的username,说明没有携带Credential,对于第二个请求,日志则显示正常转发,并且有访问者的username,说明携带了Credential信息。

我的代理服务器是ubuntu+squid。两条日志信息如下
TCP_DENIED/407 3954 CONNECT api.xxx.com:443 - HIER_NONE/- text/html
TCP_TUNNEL/200 2653 CONNECT api.xxx.com:443 user001 HIER_DIRECT/100.18.6.192

补充一下,具体的错误如下:

InnerException    {"Unable to read data from the transport connection: 远程主机强迫关闭了一个现有的连接。."}    System.Exception {System.IO.IOException}
SocketErrorCode    ConnectionReset    System.Net.Sockets.SocketError

先谢谢各位的回答,我在stackoverflow上找到了部分答案,那就是preflight request请求返回407是http代理协议的正常步骤之一!所以高度怀疑是gfw导致的问题,于是我将程序部署到国外,一切正常了。那么问题变为:如何绕开gfw?因为Get请求一直正常,但Post就会被断开连接。所以代理服务器的ip是没有被彻底屏蔽的。

以下回答由chatGPT-4 plus与答主共同编写:
看起来你的问题可能与CORS(跨源资源共享)有关。这是一种由网页浏览器实施的安全机制,用于限制从一个源的网页如何和另一个源的资源交互。

在某些情况下,例如当你发出一个跨源的POST请求时,浏览器会首先发送一个"预检"请求(即所谓的"preflight"请求)。这是一个OPTIONS请求,用于检查实际请求是否安全发送。如果服务器回应这个预检请求表示它允许这个操作,那么浏览器才会发送实际的POST请求。否则,浏览器将阻止请求,并向用户显示一个错误。

看起来在你的情况下,这个预检请求没有携带凭证,可能是因为你在你的HttpClientHandler对象上设置了UseDefaultCredentials属性为false。你可以试试以下的修改:

var webProxy = new WebProxy("address", false);
var proxyHttpClientHandler = new HttpClientHandler
{
    Proxy = webProxy,
    UseProxy = true,
    UseDefaultCredentials = false, // 设置为false
    Credentials = new NetworkCredential("user001", "psw"), // 直接在这里设置凭证
};
httpClient = new HttpClient(proxyHttpClientHandler);

这样设置后,你的HttpClient应该会在所有请求中都使用你提供的凭证,包括预检请求。

如果这个改变还是没有解决问题,你可能需要在你的代理服务器上调整配置,使其在收到预检请求时不需要凭证,或者允许匿名预检请求。这可能需要修改Squid的配置文件。

请注意,虽然这样的修改可能会解决你的问题,但它也可能会使你的代理服务器对匿名预检请求更加开放,这可能有潜在的安全风险。你应该根据你的具体需求和环境考虑这个问题。

这个问题可能是由于跨域请求引起的。RESTful API使用OPTIONS方法来预检查跨域请求,以确保客户端请求是安全的。

在.NET 7.0 中,可以通过设置 HttpClient 的 DefaultRequestHeaders 属性,在请求头中加入一个名为 "Origin" 的标头,以模拟跨域请求。同时,需要添加名为 "Access-Control-Request-Method" 和 "Access-Control-Request-Headers" 的标头,分别传递请求方法和请求头信息。

以下是修改后的代码:

var webProxy = new WebProxy("address", false);
var proxyHttpClientHandler = new HttpClientHandler
{
    Proxy = webProxy,
    UseProxy = true,
    PreAuthenticate = true // 预先验证凭据
};

ICredentials credential = new NetworkCredential("user001", "psw");
webProxy.Credentials = credential;

using var httpClient = new HttpClient(proxyHttpClientHandler);
httpClient.DefaultRequestHeaders.Add("Origin", "http://localhost");

var requestMessage = new HttpRequestMessage(HttpMethod.Post, url);

requestMessage.Headers.Add("Access-Control-Request-Method", "POST");
requestMessage.Headers.Add("Access-Control-Request-Headers", "Content-Type");

var httpResponseMessage = await httpClient.SendAsync(requestMessage);

var responseString = await httpResponseMessage.Content.ReadAsStringAsync();

在创建 HttpClient 实例时,可设置 PreAuthenticate 属性以允许预先验证凭据。这是因为有时候某些代理服务器可能会在尝试连接之前强制要求 HTTP 身份验证。预先身份验证将证明客户端凭据,这样代理服务器就不会再次弹出身份验证对话框。

将 "Origin" 标头添加到 DefaultRequestHeaders 中,并添加 "Access-Control-Request-Method" 和 "Access-Control-Request-Headers" 标头到请求头中。然后,使用 SendAsync 方法发出请求。

这样做能够模拟跨域请求,解决“preflight request”过程中出现的 HTTP 407 错误问题。

可以借鉴下
c#httpclient 代理_C#-HttpClient和使用代理-不断得到407
https://blog.csdn.net/weixin_33489161/article/details/112017431

直接用http那个是不行的,你服务器ip还会被封
用现在常用的vmess试试。
装了客户端估计都不需要代码设置了
如果不行客户端就开启本地代理
也就是两重代理了