C# ASP.NET Core Web Api Ошибка: Удаленная сторона закрыла соединение WebSocket, не завершив рукопожатие

Есть 3 разных проекта: клиент(консольное приложение), веб сервис(web api) и Azure функция. Клиент отправляет данные(конфиг своего пк) на web api через WebSocket, и возвращает значение в консоль. Также Web Api полученные данные должен отправить в Azure функцию. Все отлично работает, но если Azure функцию не запустить, то в консоле клиента возвращается ошибка: WebSocket, не завершив рукопожатие. Как исправить так, чтоб web api возвращал значение, даже если Azure функция не запущена?

Ошибка возникает в функции SendAzure, если HttpWebRequest не сработал(Azure функция не запущена).

WebApi:

 [ApiController]
[Route("[controller]")]
public class WebSocketsController : ControllerBase
{
    private readonly ILogger<WebSocketsController> _logger;
    PCInfo info = new PCInfo();
    public WebSocketsController(ILogger<WebSocketsController> logger)
    {
        _logger = logger;
    }

    [HttpGet("/ws")]
    public async Task Get()
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
      {
            using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
            _logger.Log(LogLevel.Information, "WebSocket connection established");
             await Echo(webSocket);
      }
      else
      {
          HttpContext.Response.StatusCode = 400;
      }
    }
    
    private async Task Echo(WebSocket webSocket)
    {
        var buffer = new byte[1024 * 4];
        var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
        string jsonStr = Encoding.UTF8.GetString(buffer);
        info = JsonConvert.DeserializeObject<PCInfo>(jsonStr);

        await SendAzure(jsonStr); //send Azure function

        _logger.Log(LogLevel.Information, "Message received from Client");
     
        while (!result.CloseStatus.HasValue)
        {
            var serverMsg = Encoding.UTF8.GetBytes($"Server: Hi! Client PC parameters: \n{info.pcName}\n{info.osName}\n{info.coreVersion}\n{info.timeZone}");
            await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), result.MessageType, result.EndOfMessage, CancellationToken.None);
            _logger.Log(LogLevel.Information, "Message sent to Client");
          
            result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            _logger.Log(LogLevel.Information, "Message received from Client");
           
        }
        await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        _logger.Log(LogLevel.Information, "WebSocket connection closed");
        await SendAzure(jsonStr); //send Azure function
    }

    private async Task SendAzure(string str)
    {
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(" http://localhost:7071/api/Function1");
        req.Method = "POST";
        req.ContentType = "application/json";
        Stream stream = await req.GetRequestStreamAsync();
        string json = str;
        byte[] buffer = Encoding.UTF8.GetBytes(json);
        await stream.WriteAsync(buffer, 0, buffer.Length);
        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
    }
}

Ответы (1 шт):

Автор решения: aepot

HttpWebRequest устарел, давайте перепишем метод.

private static readonly HttpClient _localhostClient = new HttpClient() { DefaultRequestVersion = new Version(2, 0) };

private async Task<string> SendAzureAsync(string json)
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7071/api/Function1");
    request.Content = new StringContent(json, Encoding.UTF8, "application/json");
    using var response = await _localhostClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    if (!response.IsSuccessStatusCode)
    {
        // залогируйте $"{(int)response.StatusCode} {response.ReasonPhrase}" = например 404 Not Found
    }
    return await response.Content.ReadAsStringAsync();
}

Теперь во-первых, запрос стал асинхронным и не вешает поток сервера, во-вторых за счет одного клиента на все внутренние запросы и поддержки HTTP/2, оно и работать будет быстрее. Далее настройте поведение и таймауты по вкусу, еще можете использовать фабрику клиентов вместо статичного экземпляра.

Еще можно обернуть вызов в try-catch

try
{
    string azureResponse = await SendAzureAsync(jsonStr);
}
catch (Exception ex)
{
    // залогируйте ex.Message
}

А как правильно работать с вебсокетами, я уже показывал здесь.

→ Ссылка