Skip to main content

ASP.NET controller action to test TCP connectivity from the provider URI.

/// <summary>
/// Tests TCP connectivity to a specified service URI with retry logic (max of 3 attempts, with 5 second timeouts).
/// </summary>
/// <param name="uri">Service URI including protocol, host, and port (e.g., snpp://9999999999@snpp.example.net:444, wctp://9999999999@wctp.example.net:80), https://google.com</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Connectivity test results including success status, attempt details, and timing information.</returns>
/// <response code="200">Connectivity test completed successfully. Check the 'connected' field in response for actual connectivity status.</response>
/// <response code="400">Invalid URI format or missing required parameters.</response>
/// <example>
/// GET /api/health/connectivity?uri=snpp://9999999999@snpp.example.net:444
/// </example>
[HttpGet("connectivity")]
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(object), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> TestConnectivity(
    [FromQuery] [System.ComponentModel.DataAnnotations.Required] string uri,
    CancellationToken cancellationToken = default
)
{
    if (string.IsNullOrWhiteSpace(uri))
    {
        return BadRequest(new { error = "URI parameter is required" });
    }

    try
    {
        var parsedUri = new Uri(uri);

        var host = parsedUri.Host;
        var port = parsedUri.Port;

        if (port == -1)
        {
            return BadRequest(new { error = "Port must be specified in URI" });
        }

        const int maxRetries = 3;
        const int timeoutMs = 5000;

        var startTime = DateTime.UtcNow;
        var attempts = new List<object>();
        var connected = false;

        for (var attempt = 1; attempt <= maxRetries; attempt++)
        {
            var attemptStart = DateTime.UtcNow;

            try
            {
                using var tcpClient = new TcpClient();
                using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                timeoutCts.CancelAfter(timeoutMs);

                await tcpClient.ConnectAsync(host, port, timeoutCts.Token);

                var attemptDuration = DateTime.UtcNow - attemptStart;

                attempts.Add(new
                {
                    attempt,
                    success = true,
                    duration = attemptDuration.TotalMilliseconds,
                    error = null as string,
                    exceptionType = null as string
                });

                connected = true;
                break;
            }
            catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested)
            {
                var attemptDuration = DateTime.UtcNow - attemptStart;

                attempts.Add(new
                {
                    attempt,
                    success = false,
                    duration = attemptDuration.TotalMilliseconds,
                    error = "Operation was cancelled by user",
                    exceptionType = ex.GetType().Name
                });
            }
            catch (OperationCanceledException ex)
            {
                var attemptDuration = DateTime.UtcNow - attemptStart;

                attempts.Add(new
                {
                    attempt,
                    success = false,
                    duration = attemptDuration.TotalMilliseconds,
                    error = $"Connection timeout after {timeoutMs}ms: {ex.Message}",
                    exceptionType = ex.GetType().Name
                });
            }
            catch (Exception ex)
            {
                var attemptDuration = DateTime.UtcNow - attemptStart;

                attempts.Add(new
                {
                    attempt,
                    success = false,
                    duration = attemptDuration.TotalMilliseconds,
                    error = ex.InnerException?.Message ?? ex.Message,
                    exceptionType = ex.GetType().Name
                });
            }
        }

        var totalDuration = DateTime.UtcNow - startTime;

        var response = new
        {
            uri,
            host,
            port,
            connected,
            totalDuration = totalDuration.TotalMilliseconds,
            attempts,
            timestamp = DateTime.UtcNow
        };

        return Ok(response);
    }
    catch (UriFormatException)
    {
        return BadRequest(new { error = "Invalid URI format" });
    }
    catch (Exception ex)
    {
        return BadRequest(new { error = $"Error parsing URI: {ex.Message}" });
    }
}