对于 Web 开发而言, 经常会遇到跨域问题。我们先来看一下什么是跨域问题:
跨域问题(Cross-Origin)本质上是浏览器的同源策略(Same-Origin Policy, SOP)在发挥作用:
同源指“协议 + 域名(或 IP)+ 端口”三要素完全一致。只要三者有任何一个不同,就被视为跨域。
为什么浏览器要限制跨域?
- 安全:阻止一个站点随意读取或修改另一个站点的敏感资源(如 Cookie、LocalStorage、DOM),避免 XSS、CSRF 等攻击链被无限放大。
- 隔离:让不同网站在沙盒里各自运行,互不干扰。
同源策略只在浏览器环境生效;后端服务之间(如服务器 A 请求服务器 B)并没有 SOP 的限制。
场景 |
描述 |
是否受限 |
fetch('https://api.foo.com') 从 https://www.bar.com 发出 |
协议、域名不同 |
受限 |
http://example.com:3000 调用 http://example.com:4000 |
端口不同 |
受限 |
⚠️ 用 Nginx/OpenResty 并不会“自动”解决 CORS。
- 你可以把前端请求代理到后端 API,使浏览器认为请求仍在同一域名下,达到“变同源”的效果。
- 或者直接在后端/代理层加 CORS 响应头,两种方式都可以。
懒猫微服的上使用的是 OpenResty,这是一个功能齐全的 Web 应用服务器,它集成了标准的 nginx core、大量第三方 nginx 模块以及它们的大部分外部依赖项。所以和 Nginx 的配置文件是通用的。
以我之前比赛做的项目为例,这个是 Nginx 作为网关,监听 80 端口,然后反向代理到 Next.js 和 Flask。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| services: nginx: build: context: . dockerfile: Dockerfile ports: - "80:80" depends_on: - next-app - backend restart: unless-stopped
next-app: image: smart-shopping-app container_name: next-frontend expose: - "3000" environment: - NODE_ENV=production - NEXT_TELEMETRY_DISABLED=1 restart: unless-stopped
backend: image: shoppingassistant-backend container_name: backend-app expose: - "5005" restart: unless-stopped
|
而 Nginx 的配置文件如写,做七成的转发,把根路径转发到前端,/api 转发到后端。所以前端的 axios 请求等于访问的/api 这个端点,所以可以规避跨域的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| server { listen 80; server_name localhost;
location / { proxy_pass http://next-app:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
location /api { proxy_pass http://backend:5005; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
|
其实对于懒猫微服的 OpenResty 的也是一样的,好处是不用自己再找 base image 了,直接把配置文件写进去就能用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| lzc-sdk-version: "0.1" name: APP Proxy Test package: cloud.lazycat.app.app-proxy-test version: 0.0.1 application: routes: - /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80 subdomain: app-proxy-test services: app-proxy: image: registry.lazycat.cloud/app-proxy:v0.1.0 setup_script: | cat <<'EOF' > /etc/nginx/conf.d/default.conf server { listen 80; server_name _;
location / { root /usr/local/openresty/nginx/html; index index.html index.htm; }
location /api/ { proxy_pass http://flask:5000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } EOF flask: image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c
|
flask 的 image 是我之前做的一个镜像仓库docker run -p 5005:5000 cloudsmithy/flask-demo:latest
然后通过lzc-cli appstore copy-image cloudsmithy/flask-demo
把镜像换成懒猫的镜像,registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c
通过setup_script
传入和 Nginx 类似的配置文件,原理是替换 docker image 本身的 entrypoint/command 参数。
1 2 3 4 5
| lzc-cli project build -o release.lpk
lzc-cli app install release.lpk
|
我们可以看到这个是 OpenResty 的主页,然后访问https://app-proxy-test.micro.heiyu.space/api/
也能返回 Flask 容器“Hello from multi-arch Flask Docker in production mode!”。

如果你想把根路由直接代理到容器,也可以使用这个办法。这个一般是用来做反向代理来访问内网的服务,即使是 http 也没有关系。这个环境变量应该是懒猫魔改的快捷方式。不要和配置文件混用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| lzc-sdk-version: "0.1" name: APP Proxy Test package: cloud.lazycat.app.app-proxy-test version: 0.0.1 application: routes: - /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80 subdomain: app-proxy-test services: app-proxy: image: registry.lazycat.cloud/app-proxy:v0.1.0 environment: - UPSTREAM="http://flask:5000" flask: image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c
|
有时候还会加上BASIC_AUTH_HEADER
这个字段来让 nginx/Openresty 自动填写密码,除了你的容器以外,代理外边服务也行。
其实用echo -n "user:password" | base64
,的数据来填充BASIC_AUTH_HEADER
“Basic “
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| lzc-sdk-version: "0.1" name: APP Proxy Test package: cloud.lazycat.app.app-proxy-test version: 0.0.1 application: routes: - /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80 subdomain: app-proxy-test services: app-proxy: image: registry.lazycat.cloud/app-proxy:v0.1.0 environment: - UPSTREAM="https://xxx:9200/" - BASIC_AUTH_HEADER="Basic YWRt46YzssdsfFlOk=" flask: image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c
|
另外也支持多域名解析,这个在传统的线下机房比较常见,而云上基本上还是 7 层基于路由转发,比如第一种,我也更加熟悉第一种。
这个其实就是加了一个 secondary_domains 的字段,然后把后端单独暴露出来了。这样就子域名就可以访问后端。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| lzc-sdk-version: "0.1" name: APP Proxy Test package: cloud.lazycat.app.app-proxy-test version: 0.0.1 application: routes: - /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80 subdomain: app-proxy-test secondary_domains: - flask services: app-proxy: image: registry.lazycat.cloud/app-proxy:v0.1.0 setup_script: | cat <<'EOF' > /etc/nginx/conf.d/default.conf server { server_name app-proxy-test.*; location / { root /usr/local/openresty/nginx/html; index index.html index.htm; } }
server { server_name flask.*;
location / { proxy_pass http://flask:5000; } } EOF flask: image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c
|