httpclient.GetAsync: 底层连接已关闭:在发送过程中发生了意外错误

4
我有一个简单的Web API,它获取一个URL并生成一个短网址作为返回。我创建了一个VS控制台应用程序,在其中通过HTTPClient对象调用Web API。当我第一次运行代码时,它会抛出以下错误: 错误信息: 一个或多个错误发生。 内部异常: System.Net.Http.HttpRequestException: 发送请求时出错。---> System.Net.WebException: 基础连接被关闭: 发送时发生意外错误。---> System.IO.IOException: 无法从传输连接中读取数据: 远程主机强迫关闭了一个现有的连接。---> System.Net.Sockets.SocketException: 远程主机强迫关闭了一个现有的连接在 System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult) at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult) --- 内部异常堆栈跟踪的结尾 --- at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult) at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar) --- 内部异常堆栈跟踪的结尾 --- at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult) at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar) --- 内部异常堆栈跟踪的结尾 ---
这是我用来调用Web API的代码。通过浏览器调用Web API可以正常工作。
static void Main(string[] args)
{
    string url = "https://app1/api/AddUrl?longUrl=http://google.com";
    var result = GetTinyUrl(url).Result;
    Console.WriteLine(result.ShortUrl);     
}

protected static async Task<UrlResponse> GetUrl(string baseUrl)
{
    HttpClientHandler handler = new HttpClientHandler();
    handler.UseDefaultCredentials = true;

    var client = new HttpClient(handler);

    var response = client.GetAsync(baseUrl).Result;
    if (response.IsSuccessStatusCode)
    {
        HttpResponseMessage response1 = response;
        response.EnsureSuccessStatusCode();
        var resp = await response.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<UrlResponse>(resp);
        return result;
    }

    return null;
}
4个回答

12

只是想说,我花了几个小时解决一个类似的问题,这个问题是在连接我正在编写的Azure服务时出现的。

无法理解为什么Postman可以正常工作,但我的代码却彻底失败。同样,我在代码中尝试的其他URL都可以正常工作或者给出了预期的错误信息。

最后,失败的原因是我忘记了将TLS/SSL设置页上的最低TLS版本降低!

我认为旧版本的HttpClient只能处理TLS v1.0。


6
卡在这里了,一看到TLS就全都回来了:System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 胜利! - WeisserHund
优秀的解决方案,Richard和WeisserHund。该问题发生在较旧的.NET框架上。 - DonYorkDon

5
混合使用阻塞调用.Result和async await可能会导致死锁。
如果使用async await,则应该全部转换为异步操作,并避免为每个调用创建新的HttpClient实例,因为这可能会导致套接字耗尽。
static Lazy<HttpClient> http = new Lazy<HttpClient>(() => {
    //Handle TLS protocols
    System.Net.ServicePointManager.SecurityProtocol =
        System.Net.SecurityProtocolType.Tls
        | System.Net.SecurityProtocolType.Tls11
        | System.Net.SecurityProtocolType.Tls12;

    var handler = new HttpClientHandler() {
        UseDefaultCredentials = true
    };

    var client = new HttpClient(handler);
    return client;
});

protected static async Task<UrlResponse> GetUrl(string baseUrl) {
    var client = http.Value;

    var response = await client.GetAsync(baseUrl); //Remove .Result and just await
    if (response.IsSuccessStatusCode) {
        HttpResponseMessage response1 = response;
        response.EnsureSuccessStatusCode();
        var resp = await response.Content.ReadAsStringAsync();
        var result = JsonConvert.DeserializeObject<UrlResponse>(resp);
        return result;
    }
    return null;
}

由于这是一个控制台应用程序且只有一个线程,因此如果调用异步方法,则必须更改在主函数中调用异步方法的方式。

static void Main(string[] args) {
    string url = "https://app1/api/AddUrl?longUrl=http://google.com";
    var result = GetTinyUrl(url).GetAwaiter().GetResult(); //<-- 
    Console.WriteLine(result.ShortUrl);     
}

参考 异步/等待 - 异步编程的最佳实践


我将把这段代码包含在网页部件中。使用控制台应用程序只是为了测试目的。 - Burre Ifort
@BurreIfort,这个例子并不是真正的代表它将被使用的地方,这将影响所提供的答案,并可能导致更多的问题。如果我们不知道实际情况,那么所提供的答案就没有正确的上下文。 - Nkosi

2
在解决方案属性中选择更高的目标框架可以解决我的问题。

0

我通常不建议使用HttpClient进行网络请求,因为它有时会在与各种Web API交互时表现不佳。我强烈鼓励您改用HttpWebRequest。您可以完全控制地构造HTTP请求,并产生更好的整体结果。

string uri = "https://api.foo.com/bar";

var httpWebRequest = (HttpWebRequest)WebRequest.Create(uri);
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "GET"; // PUT, POST, DELETE, etc
//httpWebRequest.ContentLength = 0; // sometimes you need to send zero (depending on API)
HttpWebResponse httpResponse = (HttpWebResponse)await httpWebRequest.GetResponseAsync();

using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
    var response = streamReader.ReadToEnd();
    // this is your code here...
    var result = JsonConvert.DeserializeObject<UrlResponse>(response);
    return result;
}

这就是我听说的HttpClient vs HttWebRequest之间的区别,HttpClient在某种程度上更好。我会考虑你的建议@Svek。 - Burre Ifort
1
在某些情况下,需要更高级别的控制(例如设置“用户代理”以模拟来自iPhone或Android设备或一些通用IoT设备的调用等)。实际上,HttpClient在底层使用HttpWebRequest,对于专门的请求,我倾向于使用HttpWebRequest更容易表达HTTP请求的意图。 - Svek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接