1. 为什么H5开发总会遇到跨域问题?

第一次用UniApp开发H5页面时,我对着浏览器控制台鲜红的跨域报错一脸懵。明明在小程序端运行正常的接口请求,到了H5就突然罢工。后来才发现这是浏览器特有的安全机制在作祟——就像小区门禁系统,不允许你拿着A栋的门禁卡刷开B栋的大门。

跨域的本质是浏览器对JavaScript的安全限制,当协议(http/https)、域名或端口任一不同时就会触发。比如你的前端页面在http://localhost:8080,而接口在https://api.example.com,这就构成了典型的跨域场景。有趣的是,这种限制只存在于浏览器环境,这也是为什么同样的代码在App和小程序端能正常运行。

在实际项目中,我遇到过最棘手的两种情况:一是调试时本地开发服务器访问测试环境接口,二是生产环境前端部署在CDN而接口在独立域名。有次紧急上线时,后端同学突然说服务器暂时无法配置CORS,当时急得我差点把咖啡洒在键盘上——幸好proxy代理救场。

2. manifest.json配置代理的完整指南

2.1 基础配置三步走

打开项目根目录的manifest.json文件,切换到"源码视图",找到或添加h5节点。这是我常用的配置模板:

"h5": {
  "devServer": {
    "https": false,
    "port": 8080,
    "disableHostCheck": true,
    "proxy": {
      "/api": {
        "target": "https://your-api-domain.com",
        "changeOrigin": true,
        "secure": false,
        "pathRewrite": {
          "^/api": "/v1"
        }
      }
    }
  }
}

几个关键参数值得注意:

  • disableHostCheck:关闭主机检查,解决Invalid Host header报错
  • changeOrigin:修改请求头中的Origin为目标地址,这个不开启可能导致某些服务器拒绝请求
  • pathRewrite:我习惯用它将通用前缀/api映射到具体的接口版本/v1

2.2 多环境智能配置技巧

实际开发中我们需要区分测试/生产环境。我的方案是通过环境变量动态配置:

"proxy": {
  "/api": {
    "target": process.env.API_BASE || "https://default-api.com",
    "pathRewrite": {
      "^/api": process.env.API_PREFIX || ""
    }
  }
}

然后在package.json中配置脚本命令:

"scripts": {
  "dev:test": "cross-env API_BASE=https://test-api.com API_PREFIX=/test vite",
  "dev:prod": "cross-env API_BASE=https://prod-api.com vite"
}

3. vue.config.js的代理配置方案

3.1 与manifest.json的差异

当项目需要更复杂的代理规则时,vue.config.js会更灵活。有次项目需要同时对接三个不同域名的微服务,我是这样配置的:

module.exports = {
  devServer: {
    proxy: {
      '/user': {
        target: 'https://user-service.com',
        pathRewrite: { '^/user': '' },
        headers: {
          'X-Forwarded-Host': 'localhost:8080'
        }
      },
      '/order': {
        target: 'https://order-service.com',
        auth: 'username:password'
      },
      '/api': {
        target: 'https://main-api.com',
        bypass: (req) => req.url.includes('.json') ? req.url : null
      }
    }
  }
}

注意两个配置文件的优先级规则:

  1. 当两者同时存在时,manifest.json的配置会覆盖vue.config.js
  2. 修改vue.config.js需要重启服务,而manifest.json的修改有时热更新会生效

3.2 高级配置实战

对于需要Cookie的场景,必须配置withCredentials:

proxy: {
  '/auth': {
    target: 'https://auth.example.com',
    secure: false,
    changeOrigin: true,
    onProxyReq: (proxyReq) => {
      proxyReq.setHeader('X-Proxy', 'uniapp-h5')
    },
    cookieDomainRewrite: {
      "*": "localhost"
    }
  }
}

4. 调试技巧与常见坑点

4.1 代理不生效的排查流程

  1. 检查配置加载:在终端运行npx vite inspect查看最终生效的配置
  2. 查看网络请求:Chrome开发者工具中确认请求URL是否被正确重写
  3. 服务端验证:用Postman直接请求代理目标地址,排除服务端问题
  4. 日志输出:添加proxy事件监听
devServer: {
  proxy: {
    '/api': {
      target: 'https://api.example.com',
      onProxyReq: (proxyReq, req, res) => {
        console.log(`Proxying: ${req.url} => ${proxyReq.path}`)
      }
    }
  }
}

4.2 那些年我踩过的坑

  • 路径重写陷阱:有一次配置pathRewrite: {'^/api': '/api/v1'}导致无限循环,最终改成{'^/api': '/v1'}才解决
  • HTTPS证书问题:当目标服务器使用自签名证书时,需要设置secure: false
  • WebSocket代理:需要单独配置ws: true,否则实时通信功能会失败
  • 本地开发跨域:有时需要配合修改hosts文件,将域名指向127.0.0.1

5. 生产环境部署方案

虽然proxy在开发时很好用,但生产环境需要不同的策略。我的经验是:

  1. Nginx反向代理:最稳定的方案
location /api/ {
  proxy_pass https://api-backend.com/;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_redirect off;
}
  1. 云服务商解决方案:
  • AWS的CloudFront + API Gateway
  • 阿里云的CDN + 函数计算
  • 腾讯云的API网关
  1. 纯前端方案:当完全没有后端支持时,可以考虑:
  • 使用云函数做代理层
  • JSONP(仅限GET请求)
  • 现代浏览器支持的CORS配置

6. 接口请求的最佳实践

配置好代理只是第一步,实际项目中还需要考虑:

动态环境切换:

// config.js
export const API_BASE = import.meta.env.PROD 
  ? '/api' 
  : '/proxy-api'

// request.js
uni.request({
  url: `${API_BASE}/user/profile`,
  // ...
})

错误处理增强:

uni.addInterceptor('request', {
  fail: (err) => {
    if(err.errMsg.includes('failed')) {
      uni.showToast({ title: '网络异常', icon: 'none' })
    }
    return Promise.reject(err)
  }
})

性能优化技巧:

  • 对静态数据开启本地缓存
  • 合并高频小请求
  • 使用AbortController取消重复请求

7. 从原理理解代理工作机制

当你在浏览器中输入/api/user时,背后发生了这些事:

  1. 请求首先被开发服务器拦截(如Vite的connect中间件)
  2. 根据匹配规则找到对应的proxy配置
  3. 创建到目标服务器的HTTP连接
  4. 重写请求头(Host、Origin等)
  5. 转发请求并接收响应
  6. 将响应返回给浏览器

这个过程就像有个翻译官在中间协调两方的沟通。理解这个机制后,调试代理问题就会更有方向性。比如出现404时可以确定是路径重写问题,出现403通常是header配置问题。

8. 现代前端工具的代理差异

不同构建工具的代理配置有些微差异:

Vite项目:

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        rewrite: path => path.replace(/^\/api/, '')
      }
    }
  }
}

Webpack项目:

// vue.config.js
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: process.env.VUE_APP_API_URL,
        agent: new https.Agent()
      }
    }
  }
}

主要区别在于:

  • Vite使用rewrite而非pathRewrite
  • Webpack需要自行处理HTTPS agent
  • Vite的配置更简洁,Webpack的可定制性更强

9. 安全防护要点

使用代理时要注意这些安全实践:

  1. 敏感信息过滤:配置onProxyRes钩子移除敏感响应头
onProxyRes: (proxyRes) => {
  delete proxyRes.headers['x-server-version']
}
  1. 访问控制:限制代理路径的白名单
bypass: (req) => {
  if(!req.url.includes('/valid-path')) {
    return '/404'
  }
}
  1. 日志审计:记录关键操作的代理日志
onProxyReq: (proxyReq, req) => {
  auditLog(`Proxied: ${req.method} ${req.url}`)
}

10. 终极调试方案

当所有配置都检查过还是不行时,我的终极排错流程:

  1. 在终端运行curl -v http://localhost:端口/api/test查看原始响应
  2. 使用whistle或Fiddler抓包分析网络流量
  3. 在Node层直接写最简单的HTTP代理服务器测试:
const http = require('http')
http.createServer((clientReq, clientRes) => {
  const proxy = http.request({
    host: 'target.com',
    path: clientReq.url
  }, (serverRes) => {
    serverRes.pipe(clientRes)
  })
  clientReq.pipe(proxy)
}).listen(3000)

这种从最底层开始排查的方法,曾帮我解决过一个困扰团队三天的诡异跨域问题——原来是某个中间件偷偷修改了请求头。

更多推荐