Docker Compose环境下Deno Fresh前端访问FastAPI后端失败排查
问题描述
使用Docker Compose (V2)启动默认配置的Deno Fresh前端和FastAPI后端,Compose已创建包含两个容器的网络,但前端访问API时遇到以下问题:
http://api:5000/和http://localhost:5000/均抛出ERR_CONNECTION_REFUSED错误/api:5000/返回404 Not Found
只有硬编码服务器地址http://[server]:5000/能正常获取数据,但不想硬编码地址(地址可能变更或未知)。
相关配置文件如下:
docker-compose.yaml
services: api: build: context: ./fast-api dockerfile: Dockerfile.fastapi ports: - "5000:5000" frontend: build: context: ./frontend dockerfile: Dockerfile.fresh ports: - "8000:8000"
Fresh组件代码
import { useEffect, useState } from "preact/hooks"; export default function ApiFetch() { const [data, setData] = useState(null); useEffect(() => { fetch("http://api:5000/") .then(response => response.json()) .then(data => setData(data)); }, []); return <div>Hello from {data ? data.Hello : "still loading..."}</div>; };
已知React可通过package.json配置代理,但Fresh无该文件,也未找到相关官方文档。
问题原因与解决方案
核心原因
- 浏览器无法识别Docker内部服务名:前端组件中的
fetch是在浏览器端执行的,浏览器属于宿主机网络,不在Docker Compose创建的内部网络中,因此无法解析api这个Docker服务名,导致http://api:5000/请求失败。 - FastAPI监听地址限制:如果
http://localhost:5000/也无法访问,大概率是FastAPI启动时仅绑定了容器内部的127.0.0.1,而非0.0.0.0,导致宿主机无法访问容器内的5000端口。
解决方案
步骤1:修正FastAPI的监听地址
修改FastAPI的Dockerfile或启动命令,确保服务监听0.0.0.0,允许容器外部(包括宿主机和Docker内部网络)访问:
在Dockerfile.fastapi中,将启动命令改为:
uvicorn main:app --host 0.0.0.0 --port 5000
(默认uvicorn仅监听127.0.0.1,仅容器内部可访问,改为0.0.0.0后才能被外部访问)
步骤2:给Fresh配置API转发(避免硬编码地址)
由于Fresh没有package.json代理配置,可通过服务器端路由转发实现:
- 在Fresh项目的
routes目录下创建api/[...].ts文件,编写转发逻辑:
import { Handlers } from "$fresh/server.ts"; export const handler: Handlers = { async GET(req, ctx) { // 提取请求路径,去掉前缀/api后转发到FastAPI服务 const url = new URL(req.url); const apiPath = url.pathname.replace(/^\/api/, ""); const apiUrl = new URL(`http://api:5000${apiPath}`); // 转发请求到FastAPI const response = await fetch(apiUrl, { method: req.method, headers: req.headers, body: req.body, }); // 返回FastAPI的响应给浏览器 return new Response(response.body, { status: response.status, headers: response.headers, }); }, // 如需支持POST/PUT等其他方法,添加对应处理逻辑 async POST(req, ctx) { const url = new URL(req.url); const apiPath = url.pathname.replace(/^\/api/, ""); const apiUrl = new URL(`http://api:5000${apiPath}`); const response = await fetch(apiUrl, { method: req.method, headers: req.headers, body: req.body, }); return new Response(response.body, { status: response.status, headers: response.headers, }); } };
- 修改Fresh组件中的
fetch路径为相对路径:
import { useEffect, useState } from "preact/hooks"; export default function ApiFetch() { const [data, setData] = useState(null); useEffect(() => { // 请求当前域名下的/api路径,由Fresh服务器转发到FastAPI fetch("/api/") .then(response => response.json()) .then(data => setData(data)); }, []); return <div>Hello from {data ? data.Hello : "still loading..."}</div>; };
这样配置后,浏览器请求/api/会先发送到Fresh服务器,Fresh服务器在Docker内部网络中访问http://api:5000/并转发响应,既避免了硬编码服务器地址,又解决了网络访问问题。
内容的提问来源于stack exchange,提问作者redOctober13
相关产品推荐
相关产品推荐

