fix: better fix to the backend connectivity problem using the react backend for guessing the API URL
This commit is contained in:
parent
0a759b121c
commit
a73ce8e094
7 changed files with 93 additions and 48 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue