You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于BackgroundService的Windows Service调用HttpClient.Send失败排查

问题描述

用Worker模板创建的Windows服务本身运行正常,但调用HttpClient.Send()发送请求时会出现异常,注释该行代码后服务恢复正常。在Visual Studio中调试运行时,请求能正常发送且可被Fiddler捕获;但将服务发布并注册为Windows服务后,请求无法被Fiddler捕获,事件查看器中抛出SocketException(10060) 连接超时异常。

异常详情

类别:CorewsWorkerService.WindowsBackgroundService
事件ID:0
连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。(SECRET:443)
异常:
System.Net.Http.HttpRequestException: 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。(SECRET:443)
---> System.Net.Sockets.SocketException (10060): 连接尝试失败,因为连接方在一段时间后未正确响应,或已建立的连接失败,因为连接主机未能响应。
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at ......

相关代码

public class TotalArchiveService
{
    public string CallService()
    {
        //SendRequest().GetAwaiter().GetResult();
        SendRequest();
        return "OK - " + DateTime.Now.ToString();
    }

    //static async Task SendRequest()
    private void SendRequest()
    {

        try
        {
            using (var client = new HttpClient {})
            {
                var endpointUrlCustomer = "https://SECRET";

                // Create HTTP request.
                var customerBuilder = new UriBuilder(new Uri($"{endpointUrlCustomer}"));
                var customerRequest = new HttpRequestMessage(HttpMethod.Post, customerBuilder.Uri);
                // set SOAP action
                customerRequest.Headers.Add("SOAPAction", "");
                // set SOAP body content 
                string xmlSOAP = GetXmlSOAP();
                customerRequest.Content = new StringContent(xmlSOAP, Encoding.UTF8, "text/xml");
                // Add signature headers to request object
                SignHttpRequestMessage(customerRequest);

                // Perform request
                var customerResponse = client.Send(customerRequest);
            }
        }
        catch (HttpRequestException e)
        {
            throw e;
        }
    }

    //static async Task SignHttpRequestMessage(HttpRequestMessage request)
    private void SignHttpRequestMessage(HttpRequestMessage request)
    {
        // externalize configuration of these
        string certPath = "";
        string keyStorePassword = "";
        string keyId = "";

        certPath = Path.GetFullPath(@"c:\cert2\cert.pfx"); // path to keystorefile
        keyStorePassword = "SECRET";
        keyId = "SECRET";

        X509Certificate2Collection certs = new X509Certificate2Collection();
        X509Certificate2 cert = new X509Certificate2(certPath, keyStorePassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.Exportable);
        var services = new ServiceCollection()
            .AddHttpMessageSigning()
            .UseKeyId(keyId)
            .UseSignatureAlgorithm(SignatureAlgorithm.CreateForSigning(cert, HashAlgorithmName.SHA256))
            .UseDigestAlgorithm(HashAlgorithmName.SHA256)
            .UseUseDeprecatedAlgorithmParameter()
            .UseNonce(false)
            .UseHeaders()
            .Services;
        using (var serviceProvider = services.BuildServiceProvider())
        {
            using (var signerFactory = serviceProvider.GetRequiredService<IRequestSignerFactory>())
            {
                var requestSigner = signerFactory.CreateFor(keyId);
                //await requestSigner.Sign(request);
                requestSigner.Sign(request);
            }
        }
        request.Headers.Add("Signature", request.Headers.Authorization.ToString());
        request.Headers.Authorization = null;
    }

    static string GetXmlSOAP()
    {
        string xmlSOAP = "";

        xmlSOAP = @"<?xml version=""1.0"" encoding=""utf-8""?>
        <soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"" 
                          xmlns:wsc=""http://edb.com/ws/WSCommon_v22"" 
                          xmlns:urn=""urn:srv.cus.corews.enterprise.fs.evry.com:ws:customer:v20_1"" 
                          xmlns:urn1=""urn:srv.cus.corews.enterprise.fs.evry.com:domain:customer:v20_1"" 
                          xmlns:urn2=""urn:corews.enterprise.fs.evry.com:domain:common:v8"">
        <soapenv:Header>
      <wsc:AutHeader>
         <wsc:SourceApplication>SECRET</wsc:SourceApplication>
         <wsc:DestinationApplication>SECRET</wsc:DestinationApplication>
         <wsc:Function>SECRET</wsc:Function>
         <wsc:Version>20_1</wsc:Version>
         <wsc:ClientContext>
            <wsc:userid>SECRET</wsc:userid>
            <wsc:credentials/>
            <wsc:channel>BRA</wsc:channel>
            <wsc:orgid>9057</wsc:orgid>
            <!--Optional:-->
            <wsc:orgunit>36000</wsc:orgunit>
            <!--Optional:-->
            <wsc:customerid>SECRET</wsc:customerid>
            <!--Optional:-->
            <wsc:locale>no_NO</wsc:locale>
            <wsc:ip>127.0.0.1</wsc:ip>
         </wsc:ClientContext>
      </wsc:AutHeader>
   </soapenv:Header>
    <soapenv:Body>
        <urn:customerReadRequest>
            <urn:readQualification>
                <urn1:internationalCustomerKey>
                    <urn2:internationalCustomerNumber>SECRET</urn2:internationalCustomerNumber>
                </urn1:internationalCustomerKey>
            </urn:readQualification>
        </urn:customerReadRequest>
    </soapenv:Body>
</soapenv:Envelope>";


        return xmlSOAP;
    }
}

解决方案

1. 调整Windows服务运行权限

Windows服务默认以Local System账户运行,该账户可能没有外部网络访问权限,或无法读取证书文件:

  • 打开服务管理器,找到目标服务,右键选择「属性」→「登录」,切换到具备网络访问权限的账户(如域账户、本地管理员),并确保该账户能读取c:\cert2\cert.pfx文件。

2. 优化HttpClient使用方式

  • 复用HttpClient实例:每次请求创建新HttpClient会导致Socket资源耗尽,建议将HttpClient注册为单例或静态实例,避免频繁创建销毁。
  • 改用异步调用:同步调用易导致线程阻塞,在服务环境下更易触发超时,恢复异步实现并保证调用链全程异步:
    public async Task<string> CallService()
    {
        await SendRequest();
        return "OK - " + DateTime.Now.ToString();
    }
    
    private async Task SendRequest()
    {
        try
        {
            // 使用复用的HttpClient实例
            var customerResponse = await client.SendAsync(customerRequest);
            customerResponse.EnsureSuccessStatusCode(); // 确保请求成功
        }
        catch (HttpRequestException e)
        {
            // 记录日志后再抛出,避免服务直接崩溃
            throw;
        }
    }
    
    private async Task SignHttpRequestMessage(HttpRequestMessage request)
    {
        // ... 其余代码不变
        await requestSigner.Sign(request);
        // ... 其余代码不变
    }
    

3. 配置代理

调试时Fiddler代理生效,但服务运行时不会自动使用代理,若目标服务器需通过代理访问,需在代码中配置:

var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://你的代理地址:端口"),
    UseProxy = true
};
using (var client = new HttpClient(handler))
{
    // ... 请求代码
}

或确保服务运行账户的系统代理设置正确。

4. 修复证书访问权限

  • 右键证书文件c:\cert2\cert.pfx,选择「属性」→「安全」,添加Local System账户并授予「读取」权限。
  • 加载证书时使用X509KeyStorageFlags.MachineKeySet,将证书加载到机器密钥容器,规避用户权限问题:
    X509Certificate2 cert = new X509Certificate2(certPath, keyStorePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
    

5. 增加日志排查

在SendRequest()方法中添加详细日志,记录请求头、证书加载状态、异常完整堆栈等信息,精准定位问题点。


内容的提问来源于stack exchange,提问作者Lars Moe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:55:32