fix: better fix to the backend connectivity problem using the react backend for guessing the API URL

This commit is contained in:
LUIS NOVO 2025-10-19 10:16:58 -03:00
parent 0a759b121c
commit a73ce8e094
7 changed files with 93 additions and 48 deletions

View file

@ -6,17 +6,24 @@ Starting from version 1.0.0-alpha, Open Notebook uses a simplified API connectio
### How It Works
The frontend now automatically discovers the API location at runtime, eliminating the need for complex network configurations. This works for both Docker deployment modes:
The frontend automatically discovers the API location at runtime by analyzing the incoming HTTP request. This eliminates the need for complex network configurations and works for both Docker deployment modes:
- Multi-container (docker-compose with separate SurrealDB)
- Single-container (all services in one container)
### Default Configuration
**Auto-detection logic:**
1. If `API_URL` environment variable is set → use it (explicit override)
2. Otherwise, detect from the HTTP request:
- Uses the same hostname you're accessing the frontend from
- Automatically changes port to 5055 (API port)
- Respects `X-Forwarded-Proto` header for reverse proxy setups
3. Falls back to `http://localhost:5055` if detection fails
By default, the API is accessible at `http://localhost:5055`. This works for most local Docker deployments where:
- You access the frontend at `http://localhost:8502`
- Your browser can directly reach `http://localhost:5055`
**Examples:**
- Access frontend at `http://localhost:8502` → API at `http://localhost:5055`
- Access frontend at `http://10.20.30.20:8502` → API at `http://10.20.30.20:5055`
- Access frontend at `http://my-server:8502` → API at `http://my-server:5055`
**No configuration needed** for standard localhost deployments!
**No configuration needed** for most deployments!
### Custom Configuration

View file

@ -99,12 +99,15 @@ curl http://your-server-ip:5055/api/config
Expected response:
```json
{
"apiUrl": "http://localhost:5055",
"version": "1.0.0",
"dbStatus": "connected"
"latestVersion": "1.0.0",
"hasUpdate": false,
"dbStatus": "online"
}
```
Note: The API URL is now auto-detected by the frontend from the hostname you're accessing, so `/api/config` no longer returns `apiUrl`.
### Troubleshooting
**Problem:** Frontend shows "Cannot connect to API" error

View file

@ -118,34 +118,13 @@ async def check_database_health() -> dict:
async def get_config(request: Request):
"""
Get frontend configuration.
This endpoint provides runtime configuration to the frontend,
allowing the same Docker image to work in different environments.
Auto-detection logic:
1. If API_URL env var is set, use it (explicit override)
2. Otherwise, detect from incoming HTTP request (zero-config)
Returns version information and health status.
Note: The frontend determines the API URL via its own runtime-config endpoint,
so this endpoint no longer returns apiUrl.
Also checks for version updates from GitHub (with caching and error handling).
"""
# Check if API_URL is explicitly set
env_api_url = os.getenv("API_URL")
if env_api_url:
logger.debug(f"Using API_URL from environment: {env_api_url}")
api_url = env_api_url
else:
# Auto-detect from request
# Get the protocol (http or https)
# Check X-Forwarded-Proto first (for reverse proxies), then fallback to request scheme
proto = request.headers.get("x-forwarded-proto", request.url.scheme)
# Get the host (includes port if non-standard)
host = request.headers.get("host", f"{request.client.host}:5055")
# Construct the API URL
api_url = f"{proto}://{host}"
logger.info(f"Auto-detected API URL from request: {api_url} (proto={proto}, host={host})")
# Get current version
current_version = get_version()
@ -168,7 +147,6 @@ async def get_config(request: Request):
logger.warning(f"Database offline: {db_health.get('error', 'Unknown error')}")
return {
"apiUrl": api_url,
"version": current_version,
"latestVersion": latest_version,
"hasUpdate": has_update,

View file

@ -12,7 +12,14 @@ The frontend uses a three-tier priority system to determine the API URL:
1. **Runtime Configuration** (Highest Priority): `API_URL` environment variable set at container runtime
2. **Build-time Configuration**: `NEXT_PUBLIC_API_URL` baked into the Docker image
3. **Auto-detection** (Fallback): Infers from the incoming HTTP request
3. **Auto-detection** (Fallback): Infers from the incoming HTTP request headers
**Auto-detection details:**
- The Next.js frontend analyzes the incoming HTTP request
- Extracts the hostname from the `host` header
- Respects the `X-Forwarded-Proto` header (for HTTPS behind reverse proxies)
- Constructs the API URL as `{protocol}://{hostname}:5055`
- Example: Request to `http://10.20.30.20:8502` → API URL becomes `http://10.20.30.20:5055`
## Common Scenarios

View file

@ -1,4 +1,4 @@
import { NextResponse } from 'next/server'
import { NextRequest, NextResponse } from 'next/server'
/**
* Runtime Configuration Endpoint
@ -6,17 +6,54 @@ import { NextResponse } from 'next/server'
* This endpoint provides server-side environment variables to the client at runtime.
* This solves the NEXT_PUBLIC_* limitation where variables are baked into the build.
*
* Users can now set API_URL in their docker.env and it will be picked up at runtime,
* allowing the same Docker image to work in different deployment scenarios.
* Auto-detection logic:
* 1. If API_URL env var is set, use it (explicit override)
* 2. Otherwise, detect from incoming HTTP request headers (zero-config)
* 3. Fallback to localhost:5055 if detection fails
*
* This allows the same Docker image to work in different deployment scenarios.
*/
export async function GET() {
// Priority:
// 1. API_URL from environment (set by user at runtime)
// 2. NEXT_PUBLIC_API_URL from build time (fallback)
// 3. Default to localhost:5055
const apiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5055'
export async function GET(request: NextRequest) {
// Priority 1: Check if API_URL is explicitly set
const envApiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL
if (envApiUrl) {
return NextResponse.json({
apiUrl: envApiUrl,
})
}
// Priority 2: Auto-detect from request headers
try {
// Get the protocol (http or https)
// Check X-Forwarded-Proto first (for reverse proxies), then fallback to request scheme
const proto = request.headers.get('x-forwarded-proto') ||
request.nextUrl.protocol.replace(':', '') ||
'http'
// Get the host header (includes port if non-standard)
const hostHeader = request.headers.get('host')
if (hostHeader) {
// Extract just the hostname (remove port if present)
const hostname = hostHeader.split(':')[0]
// Construct the API URL with port 5055
const apiUrl = `${proto}://${hostname}:5055`
console.log(`[runtime-config] Auto-detected API URL: ${apiUrl} (proto=${proto}, host=${hostHeader})`)
return NextResponse.json({
apiUrl,
})
}
} catch (error) {
console.error('[runtime-config] Auto-detection failed:', error)
}
// Priority 3: Fallback to localhost
console.log('[runtime-config] Using fallback: http://localhost:5055')
return NextResponse.json({
apiUrl,
apiUrl: 'http://localhost:5055',
})
}

View file

@ -3,7 +3,7 @@
* This allows the same Docker image to work in different environments.
*/
import { AppConfig } from '@/lib/types/config'
import { AppConfig, BackendConfigResponse } from '@/lib/types/config'
// Build timestamp for debugging - set at build time
const BUILD_TIME = new Date().toISOString()
@ -116,9 +116,9 @@ async function fetchConfig(): Promise<AppConfig> {
})
if (response.ok) {
const data = await response.json()
const data: BackendConfigResponse = await response.json()
config = {
apiUrl: data.apiUrl || baseUrl,
apiUrl: baseUrl, // Use baseUrl from runtime-config (Python no longer returns this)
version: data.version || 'unknown',
buildTime: BUILD_TIME,
latestVersion: data.latestVersion || null,

View file

@ -1,5 +1,18 @@
/**
* Application configuration received from backend /api/config
* Backend configuration response from Python API /api/config endpoint.
* Note: apiUrl is determined by the Next.js runtime-config endpoint,
* not returned by the Python backend.
*/
export interface BackendConfigResponse {
version: string
latestVersion?: string | null
hasUpdate?: boolean
dbStatus?: "online" | "offline"
}
/**
* Complete application configuration used by the frontend.
* This is constructed from the backend response + runtime-config.
*/
export interface AppConfig {
apiUrl: string