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
|
### 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)
|
- Multi-container (docker-compose with separate SurrealDB)
|
||||||
- Single-container (all services in one container)
|
- 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:
|
**Examples:**
|
||||||
- You access the frontend at `http://localhost:8502`
|
- Access frontend at `http://localhost:8502` → API at `http://localhost:5055` ✅
|
||||||
- Your browser can directly reach `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
|
### Custom Configuration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,12 +99,15 @@ curl http://your-server-ip:5055/api/config
|
||||||
Expected response:
|
Expected response:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"apiUrl": "http://localhost:5055",
|
|
||||||
"version": "1.0.0",
|
"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
|
### Troubleshooting
|
||||||
|
|
||||||
**Problem:** Frontend shows "Cannot connect to API" error
|
**Problem:** Frontend shows "Cannot connect to API" error
|
||||||
|
|
|
||||||
|
|
@ -118,34 +118,13 @@ async def check_database_health() -> dict:
|
||||||
async def get_config(request: Request):
|
async def get_config(request: Request):
|
||||||
"""
|
"""
|
||||||
Get frontend configuration.
|
Get frontend configuration.
|
||||||
This endpoint provides runtime configuration to the frontend,
|
|
||||||
allowing the same Docker image to work in different environments.
|
|
||||||
|
|
||||||
Auto-detection logic:
|
Returns version information and health status.
|
||||||
1. If API_URL env var is set, use it (explicit override)
|
Note: The frontend determines the API URL via its own runtime-config endpoint,
|
||||||
2. Otherwise, detect from incoming HTTP request (zero-config)
|
so this endpoint no longer returns apiUrl.
|
||||||
|
|
||||||
Also checks for version updates from GitHub (with caching and error handling).
|
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
|
# Get current version
|
||||||
current_version = get_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')}")
|
logger.warning(f"Database offline: {db_health.get('error', 'Unknown error')}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"apiUrl": api_url,
|
|
||||||
"version": current_version,
|
"version": current_version,
|
||||||
"latestVersion": latest_version,
|
"latestVersion": latest_version,
|
||||||
"hasUpdate": has_update,
|
"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
|
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
|
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
|
## Common Scenarios
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime Configuration Endpoint
|
* 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 endpoint provides server-side environment variables to the client at runtime.
|
||||||
* This solves the NEXT_PUBLIC_* limitation where variables are baked into the build.
|
* 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,
|
* Auto-detection logic:
|
||||||
* allowing the same Docker image to work in different deployment scenarios.
|
* 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() {
|
export async function GET(request: NextRequest) {
|
||||||
// Priority:
|
// Priority 1: Check if API_URL is explicitly set
|
||||||
// 1. API_URL from environment (set by user at runtime)
|
const envApiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL
|
||||||
// 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'
|
|
||||||
|
|
||||||
|
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({
|
return NextResponse.json({
|
||||||
apiUrl,
|
apiUrl: 'http://localhost:5055',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
* This allows the same Docker image to work in different environments.
|
* 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
|
// Build timestamp for debugging - set at build time
|
||||||
const BUILD_TIME = new Date().toISOString()
|
const BUILD_TIME = new Date().toISOString()
|
||||||
|
|
@ -116,9 +116,9 @@ async function fetchConfig(): Promise<AppConfig> {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json()
|
const data: BackendConfigResponse = await response.json()
|
||||||
config = {
|
config = {
|
||||||
apiUrl: data.apiUrl || baseUrl,
|
apiUrl: baseUrl, // Use baseUrl from runtime-config (Python no longer returns this)
|
||||||
version: data.version || 'unknown',
|
version: data.version || 'unknown',
|
||||||
buildTime: BUILD_TIME,
|
buildTime: BUILD_TIME,
|
||||||
latestVersion: data.latestVersion || null,
|
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 {
|
export interface AppConfig {
|
||||||
apiUrl: string
|
apiUrl: string
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue