Nginx使用笔记(一): 处理前端跨域问题

Note of Nginx : handle the issue about cross origin

Posted by 犀利一下下 on 2017-07-18

Nginx虽然不算新朋友,但也不敢妄称老熟人

从第一次接触 Nginx 算起,也是快有两年时间了,之前只是在组长写好的配置下使用它,虽然了解一些它安装和使用,但对于其极其强大且复杂的配置功能项还是有些胆怯的,没有亲自设置过,就没法细细感受它的复杂与强大。一直也想好好学习一下 Nginx 的使用,体会一下它的功能,但都没有比较合适的机会(也许因为自己太懒了,没有主动去开这个坑,看过一些介绍 Nginx 配置的文章,缺少练习也都被忘到脑后了)。这次正好帮一个同事写了个简单的静态页面,需要放到阿里云上方便他的使用,这就正好给了我一个合适的练手机会,果断把页面上线(其实就是放到服务器上,开启 Nginx 服务罢了)的差事也给接了下来。

先说说这个静态页

最初只是一个业务方提供的接口,返回 json 数据,但由于数据结构稍微复杂一些,不便于查看,我就写了个 table 页面来装这些数据。鉴于公司的项目都是在用 jQuery 这些比较陈旧的技术栈开发,苦于不能用上新的技术栈和前端工作流,我还是比较心痒的,所以在这个小小的项目里搭建了个比较时髦的开发环境,算是练习一下项目搭建吧。

首先选择了 Vue-cli 这个脚手架工具,直接 vue init 了一套 Vue2 的开发目录,工具还是比较方便的,直接内置了 Webpack2、Eslint4、babel,算是为了体验和节省一些时间吧,如果按照我自己的思路来安装的话,一定直接上最新的了,反正都是为了练习嘛,毕竟Webpack3、TypeScript 等都是值得研究和练习的趋势化工具。

初步搭建完成,因为只是个单页面,也没有其他复杂功能,vue-router, vuex, 这些就不赶鸭子硬上架的安装了。出于单页面也不能少了展示的美观性,所以又添加了 bootstrap-vue,这是一套封装了bootstrap4 样式的 vue 组件库,但是并不依赖 jQuery,还是蛮好的,就拿来当 bootstrap 用了,比较方便。再者,还想感受一下 element-ui 是否真的如他宣传的那么好,果断紧跟着上了一套。组件的动效以及展示效果还是比较不错的,但是正好被我发现,el-table 组件竟然缺少table 关键的 “rowspan、colspan”功能,看了他们的组件设计思路,想要支持基本要把 el-table 重构了。不过还是有解决思路的,最近还是太忙了,看以后有没有机会贡献一发 PR 吧。这里面又踩了一个小坑,就是想要在页面加载的时候就去请求数据,要把 getData() 函数放在 Vue 的created() 周期中(可能不太准确,回头继续研究)

关键的数据获取

最终 table 还是手写了,用上一些 bootstrap 的样式,关键的第三方数据接口,直接请求会出现跨域问题,很明显不能使用 jsonp 或者 CORS 的手段来解决跨域问题了。凭借之前对于 axios 了解的一些印象,隐约记得这货是被小右推荐过的请求数据或者资源的小神器,跨个域这种小需求应该能够轻易解决吧。然而并不是,跨域是浏览器端的问题,在 vue-cli 建立的项目可以通过 config.js proxyTable 设置 rewrite 来解决, 当然也可以通过 webpack 的 proxy 方式来解决。由于 axios 只是被我用来发了个 get 请求,跨域的责任自然要旁移了,对,其实就是为了 熟悉一下 webpack 的配置,强制自己通过 webpack 的方式来解决这个问题。然后,有了下面的配置;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
devServer: {
host: '127.0.0.1',
port: 8010,
proxy: {
'/api/': {
target: 'http://api.server.com',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},
historyApiFallback: {
index: url.parse(options.dev ? '/assets/' : publicPath).pathname
}
}

这就是在开发环境的代理转发了,webpack 的 dev server 收到 ‘/api/some/request’这样的请求时,就会拦截下来。重新修改一下(这里是通过正则的方式匹配到‘/api’,替换为空字符串),然后发送新的请求 http://api.server.com/some/request 并会在接收到返回数据时再转给浏览器页面的这次请求,从而完成整个请求和接收的流程,完美解决开发环境的浏览器跨域问题。这是前后端分离的一个重要功能啊!

扯了半天,终于要说到今天的主角——Nginx

经过上面的开发和设置,终于在本地环境完成了一个有数据的长相还不错的页面。

npm run build 走你

终于,一个崭新的dist文件夹诞生,里面饱含着我折腾一下午的辛酸。终于要发布到阿里云服务器上了,一丝丝小激动,等下,先在本地跑下试试。。。

然而,无情打脸。。。

由于npm run build之后,没有了webpack dev server 的给力支持,跨域问题又回来了。又是一个新的问题,线上环境应该给用个什么方式来解决这个跨域问题会比较好呢?虽说只是个内部使用的工具页面,访问量不会太大,硬是要用 webpack server 跑在线上也可以满足需求,但总觉的这种方式不靠谱儿,毕竟“懒惰都是要付出代价的”,想了想还是应该用 Nginx 来做 线上的代理转发比较稳妥,毕竟04年诞生的 Nginx 久经世界 IT 人民的考验,会是个不错的选择。

说干就干,还是先在本地模拟线上环境了。

brew install nginx

对,mac 安装 nginx 就是这么轻松简单。

whereis nginx

nginx -h 查看基本信息

查看一下nginx 的安装目录,默认是在 /usr/local/Cellar/nginx/1.12.1
配置文件,默认位置 /usr/local/etc/nginx/nginx.conf

提前说一下踩坑总结出来的 nginx 使用命令:

1
2
3
4
5
sudo nginx                  # 启动nginx
sudo nginx -s reload # 重启并使用新的配置文件
sudo nginx -s quit # 强制退出
sudo nginx -s stop # 强制停止
sudo nginx -s reopen # 重新打开

要么通过系统命令使用上面方式进行操作,要么就得在安装目录下 ./bin/nginx 进行命令操作,
其他操作方式会入坑(只能通过 kill pid 方式来停下 nginx),谨慎

重头戏来了, Nginx 配置

虽然敲定了使用 Nginx 来帮助解决线上跨域问题,但是为什么呢? 怎样配置才能达到我所需要的效果呢?

google 一下,大概了解一下 Nginx 的一些典型特点:

  1. 反向代理 : 顾名思义,就是反着代理,你懂了吗?
    好吧,其实,这个名字起得太恶心了,让人摸不着头脑,不了解起名背景的人很难通过名字就想象到实际所表述的功能。分析一下,与之相对应的应该就是正向代理,简称“代理”了,上文提到的 webpack dev server就是如此,一般来说,代理是架设在客户端的一种方案,比如科学上个网什么的,client 的网络不能直接访问,那就通过代理来访问 一个指定的 server,需要明确知道访问的 server 地址,这是正向的代理,被代理的对象是 client。而对于 Nginx “反向代理”功能来说,它是架设在 server 端的,被代理的对象是 server ,此时server 并不知道 具体是哪一个client 访问的,只是把资源抛给了 Nginx 。
  2. 负载均衡:
    协调处理服务器集群的任务分配,防止大量请求堆积在某个服务器上导致宕机

就会发现,其实是可以通过反向代理的方式来解决跨域,让 Nginx 检测到发送的 api 请求,然后再重新指向到 http://server.com ,像 webpack proxy 功能一样,也是涉及到 rewrite 的,重点查一下这个功能就 OK 了。

经过一番折腾,有了下面的配置:

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
server {
listen 80; #配置一台服务器,80是默认端口,好处是可以不用在访问地址上写端口号了,当然80经常被占用,也可以通过匹配访问路径,映射到其他端口号
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location /urlpath {
alias /Users/Workspace/personal/Vue/vue-ele-table/dist/; #打包文件路径,或者开发环境的源码文件地址
}

location /api/ {
rewrite ^/api/(.*)$ /$1 break; #为请求加一个api便于让Nginx 匹配到,然后替换一下,发送请求到真正的接口地址
# API Server
proxy_pass http://api.server.com; #将真正的请求代理到server ,如果 ajax发送的url为/api/some/request,实际发出的的请求是http://www.server.com/api/some/request
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

}

值得提醒的一点是:dist 文件夹在放到服务器的存储空间时,不要放在权限较高的文件夹,比如 root ,否则会造成403 forbidden报错,即 服务器无权限访问的路径,浏览器也无法看到,会报 403 错误。要么 chmod -R 777 修改文件夹的访问权限,要么就需要放在另一个权限低的目录下

最后,Nginx 配置的 server 如果访问不了,会被重新指向到 nginx 安装目录下 的 html 默认文件夹里的 index.html 页面,这是默认的,如果有需要可以替换该文件,设置默认的提示页面。

(有些部分还待研究确认,如有问题,欢迎指出,共同学习,谢谢)