Docker化React调用FastAPI:为何localhost可行而服务名触发CORS?
Docker容器通信与CORS问题排查
问题场景
我有两个处于同一Docker网络的容器:Docker化React应用(使用Vite开发服务器)和Docker化FastAPI后端,docker-compose.yml配置如下:
services: backend: build: context: ./backend ports: - "8000:8000" volumes: - ./backend/app:/code/app networks: - app-network react-frontend: build: context: ./react-frontend dockerfile: Dockerfile restart: always environment: - VITE_DEV=true ports: - "3000:3000" volumes: - ./react-frontend:/app - /app/node_modules command: npm run dev networks: - app-network depends_on: - backend networks: app-network: driver: bridge
FastAPI已配置CORS允许来源:
origins = [ "http://react-frontend:3000", "http://localhost:3000" ] middleware = [ Middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) ] app = FastAPI(lifespan=lifespan, middleware=middleware)
尝试用Docker服务名http://backend请求后端时触发CORS错误:
const URL = "http://backend/" // 触发CORS错误 try { const response = await axios.get(URL) console.log(response.data) } catch (error) { console.error("An error occurred") }
错误信息:
跨源请求被阻止:同源策略禁止读取http://backend/的外部资源。原因:CORS请求失败。状态码:(null)。
但使用http://localhost:8000请求却正常工作:
const URL = "http://localhost:8000/" // 正常工作 try { const response = await axios.get(URL) console.log(response.data) } catch (error) { console.error("An error occurred") }
疑问:按Docker网络逻辑,容器应通过服务名通信,localhost不应生效,为何实际相反?是否需修改vite.config.js来适配服务名调用?
原因分析
- 请求发起方是浏览器而非React容器:Vite开发服务器运行在React容器中,但前端代码最终是在本地浏览器里执行的。浏览器不在Docker网络范围内,无法解析
backend这个服务名,请求根本无法到达后端,进而触发浏览器的同源策略拦截(表现为CORS错误)。 - localhost能生效的核心原因:
docker-compose配置里把后端容器的8000端口映射到了宿主机的8000端口,浏览器访问localhost:8000时,实际是通过宿主机的端口转发机制连接到后端容器。同时这个请求的来源是http://localhost:3000,符合FastAPI的CORS允许列表,所以能正常响应。
解决方法:配置Vite代理
要在开发环境下让前端通过Docker服务名访问后端,需要借助Vite的代理功能,把前端请求转发到Docker网络内的后端服务。修改vite.config.js:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { proxy: { // 匹配所有以/api开头的请求 '/api': { target: 'http://backend:8000', // Docker服务名+后端容器端口 changeOrigin: true, // 如果后端接口没有/api前缀,可通过rewrite去掉请求里的/api // rewrite: (path) => path.replace(/^\/api/, '') } } } })
然后修改前端请求代码,使用相对路径:
// 假设后端接口路径为/api/xxx const response = await axios.get('/api/')
配置后逻辑:
- 浏览器向Vite开发服务器发起请求,二者同源(均为
localhost:3000),不会触发浏览器的同源策略拦截。 - Vite服务器作为代理,将请求转发到Docker网络内的
backend:8000,服务器间通信不受浏览器同源策略限制。
内容的提问来源于stack exchange,提问作者OhDaeSu
相关产品推荐
相关产品推荐

