Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AddDockerfile: Fix Connectivity Issue: Cannot Assign Requested Address for #7210

Closed
1 task done
aitrailblazer opened this issue Jan 22, 2025 · 14 comments
Closed
1 task done

Comments

@aitrailblazer
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Problem Description

Error in Connectivity

  • The application attempts to connect to localhost:8000 but fails with the error:
    Cannot assign requested address

  • This issue stems from the inability of the SECEdgarWSAppService to reach the expected container's HTTP endpoint.

Retry Attempts

  • The system retries connecting 5 times, each with a fixed delay and jitter (as configured), but ultimately fails.

Logs

  • Application logs indicate:
  • It successfully connects to the container webfrontend.
  • However, no CIK (Central Index Key) is found for the ticker TSLA.

Components Overview

Docker Setup

A Docker container (secedgarwsapp) runs a Python app to handle SEC filings. Key features:

  • ASGI Server: Uses Uvicorn to expose the app on port 8000.
  • Networking: The container is configured to use localhost as the default BASE_ADDRESS.

Dockerfile Highlights

  • The app exposes port 8000.
  • It runs as a non-root user for better security.
  • Uses Uvicorn to serve main:app.

Service Configuration in my.Web

HttpClient

  • Configured to communicate with the SEC Edgar Web Service at http://localhost:8000.
  • Implements resilience policies:
  • Retries
  • Timeouts
  • Circuit breaking

Program.cs in my.AppHost

The secedgarwsapp service:

  • Exposes port 8000 both internally and externally.
  • Uses OpenTelemetry for observability.
  • Is marked as a dependency for the webfrontend project.

GetCIKAsync Method

This method:

  • Sends a GET request to fetch the CIK for a given ticker.
  • Implements basic validation for tickers.
  • Gracefully handles potential HTTP or parsing errors.

Connecting...
2025-01-21T17:57:13.07790  Connecting to the container 'webfrontend'...
2025-01-21T17:57:13.16873  Successfully Connected to container: 'webfrontend' [Revision: 'webfrontend--0000049-6c5c7c8df5-kn7lw', Replica: 'webfrontend--0000049']
      Execution attempt. Source: 'SECEdgarWSAppService-standard//Standard-Retry', Operation Key: '', Result: 'Cannot assign requested address (localhost:8000)', Handled: 'True', Attempt: '5', Execution Time: 0.987ms
      System.Net.Http.HttpRequestException: Cannot assign requested address (localhost:8000)
       ---> System.Net.Sockets.SocketException (99): Cannot assign requested address
         at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
         at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
         at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
         --- End of inner exception stack trace ---
         at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.InjectNewHttp11ConnectionAsync(QueueItem queueItem)
         at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
         at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
         at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
         at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendCoreAsync>g__Core|4_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
         at Microsoft.Extensions.Http.Resilience.ResilienceHandler.<>c.<<SendAsync>b__3_0>d.MoveNext()
HTTP request error: Cannot assign requested address (localhost:8000)
info: aitrailblazer.Web.Components.Pages.AIFilingScout[0]
      No CIK found for ticker TSLA. Skipping further processing.

my.AppHost/Program.cs

var secedgarwsapp = builder.AddDockerfile("secedgarwsapp", "./sec-edgar-ws")
    //.WithHttpEndpoint(targetPort: 8000, env: "PORT") // Expose HTTP endpoint based on PORT environment variable
    .WithHttpEndpoint(targetPort: 8000, port: 8000, name: "secedgarwsapp-http") // Bind host port 8000 to container port 8000
    .WithExternalHttpEndpoints()  // Make it accessible externally
    .WithOtlpExporter();          // Enable OpenTelemetry for observability

builder.AddProject<Projects.aitrailblazer_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
    .WaitFor(secedgarwsapp);      /
//
builder.Build().Run();

my.AppHost/Dockerfile

# Use an official Python runtime as the base image
FROM python:3.11-slim

# Set environment variables to avoid interactive prompts during apt-get
ENV DEBIAN_FRONTEND=noninteractive

# Set the working directory inside the container
WORKDIR /app

# Install system dependencies required by WeasyPrint
RUN ...

# Copy the requirements file to the container
COPY requirements.txt requirements.txt

# Install Python dependencies
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt

# Copy the application code to the container
COPY . .

# Expose the port that the app runs on
EXPOSE 8000

# Set environment variables for Uvicorn
ENV PORT=8000
ENV HOST=0.0.0.0

# Use non-root user for better security
RUN addgroup --system appgroup && adduser --system --group appuser
USER appuser

# Command to run the app using Uvicorn ASGI server
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--log-level", "info"]

my.Web
Program.cs

builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
{
    client.BaseAddress = new Uri("http://localhost:8000"); // or 0.0.0.0

    client.Timeout = TimeSpan.FromMinutes(5); // Total timeout for all retries combined
})
.AddStandardResilienceHandler(options =>
{
    // Retry policy configuration
    options.Retry.MaxRetryAttempts = 5; // Retry up to 5 times
    options.Retry.Delay = TimeSpan.FromSeconds(2); // Fixed delay between retries
    options.Retry.UseJitter = true; // Add jitter to avoid retry storms

    // Timeout policy configuration
    options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(60); // Timeout for individual attempts

    // Total request timeout (all retries combined)
    options.TotalRequestTimeout.Timeout = TimeSpan.FromMinutes(5);

    // Circuit breaker configuration
    options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(120); // At least double the attempt timeout
});

SECEdgarWSAppService.cs

 public async Task<string?> GetCIKAsync(string ticker)
    {
        try
        {
            // Validate ticker format before making the request
            if (string.IsNullOrWhiteSpace(ticker) || !System.Text.RegularExpressions.Regex.IsMatch(ticker, @"^[A-Za-z0-9]+$"))
            {
                Console.WriteLine("Invalid ticker format. Only alphanumeric characters are allowed.");
                return string.Empty; // Return empty string for unexpected errors
            }

            // Construct the endpoint URL for fetching the CIK
            string endpoint = $"/cik/{ticker}";

            // Send a GET request to the endpoint
            var response = await _httpClient.GetAsync(endpoint);

            // If the response status is 404 (Not Found), return null
            if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                Console.WriteLine($"Ticker not found: {ticker}");
                return string.Empty; // Return empty string for unexpected errors
            }

            // Ensure the request was successful
            response.EnsureSuccessStatusCode();

            // Parse the response as plain text
            var cik = await response.Content.ReadAsStringAsync();

            // Validate the CIK
            if (string.IsNullOrWhiteSpace(cik) || !long.TryParse(cik, out _))
            {
                Console.WriteLine("Invalid or empty CIK returned by the server.");
                return string.Empty; // Return empty string for unexpected errors
            }

            // Log the retrieved CIK
            Console.WriteLine($"Fetched CIK for {ticker}: {cik}");

            return cik;
        }
        catch (HttpRequestException httpEx)
        {
            // Log the HTTP request error
            Console.WriteLine($"HTTP request error: {httpEx.Message}");
            return string.Empty; // Return empty string for unexpected errors
        }
        catch (Exception ex)
        {
            // Log any unexpected error
            Console.WriteLine($"Unexpected error: {ex.Message}");
            return string.Empty; // Return empty string for unexpected errors
        }
    }

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version info

No response

Anything else?

No response

@davidfowl
Copy link
Member

Your web front end is trying to call your python container, but it's using localhost to talk to the python container? Why are you using localhost instead of http://secedgarwsapp?

@aitrailblazer
Copy link
Author

I tried but couldn't be found. How you will do it?

@davidfowl
Copy link
Member

Change this code:

var secedgarwsapp = builder.AddDockerfile("secedgarwsapp", "./sec-edgar-ws")
    //.WithHttpEndpoint(targetPort: 8000, env: "PORT") // Expose HTTP endpoint based on PORT environment variable
    .WithHttpEndpoint(targetPort: 8000, port: 8000, name: "secedgarwsapp-http") // Bind host port 8000 to container port 8000
    .WithExternalHttpEndpoints()  // Make it accessible externally
    .WithOtlpExporter();          // Enable OpenTelemetry for observability

builder.AddProject<Projects.aitrailblazer_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
   .WithReference(secedgarwsapp.GetEndpoint("secedgarwsapp-http"))
    .WaitFor(secedgarwsapp);    

Then it should work.

PS: The documentation explains the pattern https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/app-host-overview?tabs=docker#connection-string-and-endpoint-references

@aitrailblazer
Copy link
Author

aitrailblazer commented Jan 22, 2025

in my.Web:

builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
{
    client.BaseAddress = new("http://secedgarwsapp-http:8000");

    client.Timeout = TimeSpan.FromMinutes(5); // Total timeout for all retries combined
})

calling it:

2025-01-22T10:14:33       Execution attempt. Source: 'SECEdgarWSAppService-standard//Standard-Retry', Operation Key: '', Result: 'nodename nor servname provided, or not known (secedgarwsapp-http:8000)', Handled: 'True', Attempt: '0', Execution Time: 12.9737ms
2025-01-22T10:14:33       System.Net.Http.HttpRequestException: nodename nor servname provided, or not known (secedgarwsapp-http:8000)

@davidfowl
Copy link
Member

client.BaseAddress = new("http://secedgarwsapp-http);

Remove the port

@aitrailblazer
Copy link
Author

aitrailblazer commented Jan 22, 2025

builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
{
    client.BaseAddress = new("http://secedgarwsapp-http");

    client.Timeout = TimeSpan.FromMinutes(5); // Total timeout for all retries combined
})

2025-01-22T11:14:52       Execution attempt. Source: 'SECEdgarWSAppService-standard//Standard-Retry', Operation Key: '', Result: 'nodename nor servname provided, or not known (secedgarwsapp-http:80)', Handled: 'True', Attempt: '0', Execution Time: 126.1177ms
2025-01-22T11:14:52       System.Net.Http.HttpRequestException: nodename nor servname provided, or not known (secedgarwsapp-http:80)

@davidfowl
Copy link
Member

Sorry, I meant:

var secedgarwsapp = builder.AddDockerfile("secedgarwsapp", "./sec-edgar-ws")
    .WithHttpEndpoint(targetPort: 8000, port: 8000) // Bind host port 8000 to container port 8000
    .WithExternalHttpEndpoints()  // Make it accessible externally
    .WithOtlpExporter();   
builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
{
    client.BaseAddress = new("http://secedgarwsapp");

    client.Timeout = TimeSpan.FromMinutes(5); // Total timeout for all retries combined
})

Change both of the following.

@aitrailblazer
Copy link
Author

var secedgarwsapp = builder.AddDockerfile("secedgarwsapp", "./sec-edgar-ws")
    .WithHttpEndpoint(targetPort: 8000, port: 8000) // Bind host port 8000 to container port 8000
    .WithExternalHttpEndpoints()  // Make it accessible externally
    .WithOtlpExporter();

builder.AddProject<Projects.aitrailblazer_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
    .WaitFor(secedgarwsapp);

builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
{
    client.BaseAddress = new("http://secedgarwsapp");

    client.Timeout = TimeSpan.FromMinutes(5); // Total timeout for all retries combined
})
2025-01-22T12:18:04       Sending HTTP request GET http://secedgarwsapp/cik/T
2025-01-22T12:18:04 warn: Polly[3]
2025-01-22T12:18:04       Execution attempt. Source: 'SECEdgarWSAppService-standard//Standard-Retry', Operation Key: '', Result: 'nodename nor servname provided, or not known (secedgarwsapp:80)', Handled: 'True', Attempt: '0', Execution Time: 185.1847ms
2025-01-22T12:18:04       System.Net.Http.HttpRequestException: nodename nor servname provided, or not known (secedgarwsapp:80)
2025-01-22T12:18:04        ---> System.Net.Sockets.SocketException (0xFFFDFFFF): nodename nor servname provided, or not known

@davidfowl
Copy link
Member

Is your application using service discovery? Are service defaults added to the project?

@aitrailblazer
Copy link
Author

yes:


myWeb.csproj

    <ProjectReference Include="..\my.ServiceDefaults\my.ServiceDefaults.csproj" />

my.ServiceDefaults is not changed. it was generated when I created the project.

@davidfowl
Copy link
Member

There should be a call in your application to AddServiceDefaults, that's the logic that wires up service discovery.

@aitrailblazer
Copy link
Author


myWeb.
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
   Start processing HTTP request GET http://secedgarwsapp/cik/TSLA
2025-01-22T13:47:35 info: System.Net.Http.HttpClient.SECEdgarWSAppService.ClientHandler[100]
2025-01-22T13:47:35       Sending HTTP request GET http://secedgarwsapp/cik/TSLA
2025-01-22T13:47:35 warn: Polly[3]
2025-01-22T13:47:35       Execution attempt. Source: 'SECEdgarWSAppService-standard//Standard-Retry', Operation Key: '', Result: 'nodename nor servname provided, or not known (secedgarwsapp:80)', Handled: 'True', Attempt: '0', Execution Time: 6.4582ms

@aitrailblazer
Copy link
Author

This solution is working:

my.AppHost
Program.cs

var secedgarwsapp = builder.AddDockerfile(
    "secedgarwsapp", 
    "./sec-edgar-ws", // The directory where your backend project and Dockerfile are located
    "Dockerfile"
).WithHttpEndpoint(8000, 8000); // Map container's port 80 to host's port 5000


builder.AddProject<Projects.aitrailblazer_Web>("webfrontend")
    .WithExternalHttpEndpoints()
    .WithReference(apiService)
    .WithReference(secedgarwsapp.GetEndpoint("http"));

my.Web
Program.cs

builder.Services.AddHttpClient<SECEdgarWSAppService>(client =>
    {
        // This URL uses "https+http://" to indicate HTTPS is preferred over HTTP.
        // Learn more about service discovery scheme resolution at https://aka.ms/dotnet/sdschemes.
        client.BaseAddress = new("https+http://secedgarwsapp");
    });

@davidfowl
Copy link
Member

Excellent!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants