图片来源网络, 侵删
1. 需求背景
公司网站的本地开发版之前一直都是部署在本地电脑上 Tomcat 容器里的, 好处就是本地搭建服务器环境接口无需做跨域请求处理, 坏处就是后台代码的每次更新都需要拷贝一份至我的电脑覆盖, 并且本地环境与测试线环境数据仍然有所差异, 在本地环境调试不便.
前天在与新入职的 Java 工程师讨论如何分工协作的时候聊到了部署 Tomcat 容器到我本地的坏处, 然后仔细想想我最近不是在学 Node.JS 嘛, 为何不学以致用在我本地用 Node.JS 部署跨域代理服务呢? 于是花了两天零碎的时间研究了如何使用 Node.JS 来实现静态页面的接口请求代理.
在实现跨域之前先理清楚我想实现的什么样的功能.
2. 跨域的几种方式
静态网站的里的 Ajax 请求是相对路径, 同一套代码要在本地开发环境, 测试线环境, 大陆正式线, 香港正式线部署. 所以请求不适合使用绝对路径.
前端跨域
前端跨域的方式有两种
通过 script 标签跨域
通过 JSONP 跨域
两者的原理都是使用请求静态资源文件的方式, 用回调函数的包装把 JSON 数据返回前端. 从而骗过浏览器的跨域审查. 使用这种方法来跨域意味着前端和后端的代码都要根据跨域的要求来重构一番, 并且只能发送 GET 请求, 这样的方式肯定是不可行的, 因为只有在本地开发时才需要跨域, 代码部署到线上服务器后就不需要跨域了.
所以开始研究后端方案跨域. 也有两种.
后端跨域
CORS 跨域资源共享
跨域代理服务
CORS (Cross-Origin Resource Sharing)跨域资源共享, 它允许浏览器向跨源 (协议 + 域名 + 端口) 服务器, 发出 XMLHttpRequest 请求, 从而克服了 Ajax 只能同源使用的限制. CORS 需要浏览器和服务器同时支持, 实现 CORS 通信的关键是服务器. 只要服务器实现了 CORS 接口, 就可以跨源通信.
参考引用: CORS(跨域资源共享)
使用 CORS 来实现跨域仍然需要在目标服务器的接口上配置跨域请求参数, 所以实现起来要前后端配合还是比较麻烦的. 而且仅仅是测试线服务器需要跨域, 正式线服务器禁止跨域, 这样子修改后, 后台代码就会出现不一致. 所以此方法也弃用.
跨域代理服务, 原理是把前端 http 请求发送给后台的代理, 让代理代为转发请求, 从而不需要在浏览器上进行跨域请求. 代理获取到响应结果再转发到前端. 使用它的好处是目标服务器上的接口代码和前端代码都不需要做任何更改, 只需开启本地跨域代理服务即可.
每种跨域方式分析过后, 觉得最可行的操作就是使用静态资源代理服务来做前端跨域请求.
3. 使用 Node.JS 跨域代理服务
在 nginx 和 Node.JS 间选择了后者, 于是开始了在本地搭建 http 服务器和寻找合适的代理跨域中间件.
搭建了本地 http 服务后, 具体怎么操作实现代理跨域其实还是没多大思路的, 于是呢就开始百度寻找案例来参考, 了解了使用 Node.JS 中间件跨域的大概思路.
先是搭建起 http 服务器, 对访问主机地址的网络请求进行判断, 如果是 API 接口的请求时则使用中间件的代理服务进行转发. 让中间件代为向目标服务器发起请求, 响应结果再转发至前端.
4. 代码实现
文件结构目录大概如此, ROOT 文件夹为网页文件夹
项目文件目录
1. 创建项目文件夹并 NPM 初始化文件夹
- mkdir demo
- cd demo
- NPM init -y
2. 安装 node-http-proxy 中间件
NPM install http-proxy --save-dev
3. 创建启动文件 proxy.JS
- var PORT = 3000;// 定义端口号
- var tatgetPATH='http://www.a.com/'// 目标服务器地址
- var http = require('http'); // 引入 http 模块
- var url=require('url'); // 引入 url 模块
- var fs=require('fs'); // 引入文件模块
- var mine=require('./fileFormat').types; // 文件格式字典
- var path=require('path'); // 引入 path 模块
- var httpProxy = require('http-proxy'); // 跨域代理中间件
- var proxy = httpProxy.createProxyServer({
- target: tatgetPATH, // 接口地址
- // 下面的设置用于 https
- // ssl: {
- // key: fs.readFileSync('server_decrypt.key', 'utf8'),
- // cert: fs.readFileSync('server.crt', 'utf8')
- // },
- // secure: false
- });
- var server = http.createServer(function (request, response) {
- var pathname = url.parse(request.url).pathname;
- // 访问根目录时改为指向首页文件
- if(pathname=='/'){
- pathname='index.html'
- }
- // 指定根目录
- var realPath = path.join("./ROOT", pathname);
- var ext = path.extname(realPath);
- ext = ext ? ext.slice(1) : 'unknown';
- // 判断如果是 API 接口访问, 则通过 proxy 转发
- if(pathname.indexOf("./ROOT")> 0){
- // console.log('发起请求:',pathname)
- proxy.web(request, response);
- return;
- }
- fs.exists(realPath, function (exists) {
- if (!exists) {
- response.writeHead(404, {
- 'Content-Type': 'text/plain'
- });
- response.write("This request URL" + pathname + "was not found on this server.");
- response.end();
- } else {
- fs.readFile(realPath, "binary", function (err, file) {
- if (err) {
- response.writeHead(500, {
- 'Content-Type': 'text/plain'
- });
- response.end(err);
- } else {
- var contentType = mine[ext] || "text/plain";
- response.writeHead(200, {
- 'Content-Type': contentType
- });
- response.write(file, "binary");
- response.end();
- }
- });
- }
- });
- });
- server.listen(PORT);
- // 代理服务执行错误的监听
- proxy.on('error', function(err, req, res){
- res.writeHead(500, {
- 'content-type': 'text/plain'
- });
- console.log(err);
- res.end('Something went wrong. And we are reporting a custom error message.');
- });
- console.log("Server runing at port:" + PORT + "."+tatgetPATH);
引入文件格式字典 fileFormat.JS
- exports.types = {
- "CSS": "text/css",
- "gif": "image/gif",
- "html": "text/html",
- "ico": "image/x-icon",
- "jpeg": "image/jpeg",
- "jpg": "image/jpeg",
- "js": "text/javascript",
- "json": "application/json",
- "pdf": "application/pdf",
- "png": "image/png",
- "svg": "image/svg+xml",
- "swf": "application/x-shockwave-flash",
- "tiff": "image/tiff",
- "txt": "text/plain",
- "wav": "audio/x-wav",
- "wma": "audio/x-ms-wma",
- "wmv": "video/x-ms-wmv",
- "xml": "text/xml",
- "woff": "application/x-woff",
- "woff2": "application/x-woff2",
- "tff": "application/x-font-truetype",
- "otf": "application/x-font-opentype",
- "eot": "application/vnd.ms-fontobject"
- };
代码写完后运行 proxy.JS 即可通过跨域代理服务来实现本地调用不同域服务器的接口了.
源码主要参考了下面这篇文章, 十分感谢该作者提供的方案.
Node.JS 配合 node-http-proxy 解决本地开发 Ajax 跨域问题
5. 总结
跨域的解决方案有很多, 具体选择哪一种方案还是根据实际的情况来进行分析.
以前还没学习 Node.JS 的时候, 遇到跨域问题能想到的解决方法就是使用 Ajax 的 JSONP, 需要后端返回的接口加上回调函数, 前端再通过通过函数对数据进行接收. 改起来十分之麻烦.
前端若是懂得后端的一些知识, 便能够使用更多的解决方案来解决问题. Web 开发中包括了前端和后端, 能够熟练使用两端的技能, 才能够在 Web 开发中游刃有余.
来源: http://www.jianshu.com/p/45db0b56e384