Nginx从入门到解决实际问题
本文为nginx的入门教程,适合零基础同学,以及了解较浅显的同学。
1. 序言
Nginx是异步框架的网页服务器,也可以用作反向代理、负载平衡器和Http缓存。该软件由俄罗斯程序员伊戈尔 <math xmlns="http://www.w3.org/1998/Math/MathML"> ⋅ \cdot </math>⋅塞索耶夫开发并于2004年首次公开发布。2011年成立同名公司以提供支持服务。2019年3月11日,Nginx公司被F5网络公司以6.7亿美元收购。
Nginx是免费的开源软件,根据类BSD许可证的条款发布。大部分Web服务器使用Nginx,通常作为负载均衡器。
2. Nginx介绍
对于大多数个人开发者,Nginx的主要作用有两个:
- (前端开发)静态资源代理(Web服务器):前端项目打包后的dist、视频、图像等
- (后端开发)反向代理:将流量转发到后端服务器
我们默认使用Docker启动Nginx服务器,这是目前最方便、最普遍、最纯粹的启用方式,如果还不了解,可以先行快速了解一下。(目前,我在后端开发中,Nginx MySQL Redis RabbimtMQ Nacos都使用Docker容器)
快速介绍
启动一个Nginx镜像,宿主机端口80,容器名称为the-nginx
,在后台运行:
shell
docker run -d --name the-nginx -p 80:80 nginx
在自己的浏览器中,输入http://localhost:80/ 观察到以下界面:
Nginx的功能主要由两个关键的配置文件决定。
- 系统配置文件目录
/etc/nginx/
- 媒体资源文件目录
/usr/share/nginx/html
在/etc/nginx
目录下的主要配置文件为:
/etc/nignx/nginx.conf
:全局配置文件,定义了nginx的基本配置(默认不需要改)/etc/nginx/conf.d/default.conf
:默认配置文件(用户关注)
查看一下nginx.conf
配置文件:
dart
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
这里不做详细介绍,只提醒一点include /etc/nginx/conf.d/*.conf;
,我们可以自定义配置文件(创建),但是需要以.conf
为结尾。
查看default.conf
文件:
ini
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#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 /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
这里关于server
的配置文件,我也不做过多介绍,网上一抓一大把,我也不希望制造更多的文字垃圾。
下面我直接使用例子出发,介绍如何使用。
3. Nginx使用场景
3.1 静态资源代理(Web服务器)
这里我们使用Vue前端项目来作示例。需要保证你的PC上已经安装了npm
和git
,核心代码内容
ts
// 不跨域
axios.get("/api/hello")
.then(function (response) {
console.log(response)
})
// 跨域访问后端项目
axios.get("http://localhost:9000/hello")
.then(function (response) {
console.log(response)
})
- 拉取仓库
shell
git clone https://github.com/xlxingRun/nginx-demo-front.git
- 安装依赖
shell
npm install
- 开发环境热启动(仅在开发环境使用)
shell
npm run dev
- 编译打包
shell
npm run build
本项目只需要执行步骤1 2 4,编译打包后会生成一个dist
文件夹,这个是生产环境要部署的静态资源。
将静态资源放置到nginx的资源目录中,并重新启动该容器
shell
docker cp dist/. the-nginx:/usr/share/nginx/html && docker restart the-nginx
访问http://localhost:80 可以看到如下界面: 可以看到此时nginx是一个web服务器的角色,生产环境基本都是这么弄的。 当然如果你使用
nohup npm run dev &
也可以,默认5173端口,但是这种方式仅在开发环境使用。
上述关键内容在于文件default.conf
,根目录是/usr/share/nginx/html
,默认使用index.html
或者index.htm
作为默认首页。此处我们是将dist/.
复制到html
中。(注意不是dist
,这会将整个文件夹复制到容器中)
ini
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
其实不只是前端项目,包括服务器上的视频、音频、图像都可以,如果感兴趣可以自行尝试(我都试过,有问题可以联系我交流)。
3.2 反向代理服务器
完成上述步骤后,我们启动go语言实现的简单后端服务,监听9000端口
go
func handler1(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "Hello, 第一条消息")
if err != nil {
return
}
}
func handler2(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "Hello, 第二条消息")
if err != nil {
return
}
}
func main() {
http.HandleFunc("/hello", handler1)
http.HandleFunc("/api/hello", handler2)
err1 := http.ListenAndServe(":9000", nil)
if err1 != nil {
return
}
}
拉取仓库
shell
git clone https://github.com/xlxingRun/nginx-demo-backend.git
热启动
shell
go run main.go
启动Go服务,并监听9000端口,可以直接访问http://localhost:9000/hello/ 查看
此时我们再次查看前端项目http://localhost:80/ F12查看网络
这两个后端请求都失败,一个是跨域错误,一个是404。下面我来简单分析和总结
- 跨域错误:浏览器80端口服务通过axios访问9000端口
- 404:资源不存在,访问http://localhost:80/api/hello 没有这个后端服务
下面我们来解决这两个问题,解决的原则是,尽可能前后端服务解耦合。 解决跨域的方式有三种:
前端解决:配置虚拟代理
例如在vue项目中配置:
js
module.exports = {
// 设置端口号和自动打开
devServer: {
// 自动打开浏览器
open: true,
// 配置端口号
port: 8888,
// 配置代理
proxy: {
// 请求路径中携带 /api 的话就会,向 http://localhost:3000 发送请求
'/api': {
// 目标服务器地址
target: 'http://localhost:3000',
// pathRewrite: { '^/api': '' }
}
}
},
// 关闭ESLint
lintOnSave: false
}
该方法不是很推荐,更多信息自行搜索网络(前端不是很懂,就不搬运太多内容了)
后端解决:配置允许跨域
例如SpringBoot项目创建一个配置Bean
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class MyCorsFilter {
@Bean
public CorsFilter corsFilter() {
// 1.创建 CORS 配置对象
CorsConfiguration config = new CorsConfiguration();
// 支持域
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 支持请求方式
config.addAllowedMethod("*");
// 允许的原始请求头部信息
config.addAllowedHeader("*");
// 暴露的头部信息
config.addExposedHeader("*");
// 2.添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3.返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}
各个开发框架都有不止一种配置后端跨域的方式,这里也不班门弄斧,只做基本介绍。
Nginx解决跨域(重点)
上述的前端项目中,我们访问/api/hello
的期望是访问的是后端服务,而不是前端项目自己的路由,解决方案为配置反向代理
在default.conf的server块中增加
bash
location /api {
# proxy_pass https://localhost:9000/;
proxy_pass http://host.docker.internal:9000;
}
其中host.docker.internal
为docker内流量转发到localhost
的地址(本项目使用docker启动nginx。若直接在操作系统中安装nginx,则使用localhost
)
将该配置文件覆盖docker容器中的default.conf
并重新启动
shell
docker cp default.conf the-nginx:/etc/nginx/conf.d/default.conf && docker restart the-nginx
再次访问http://localhost:80 可以看到访问成功
这里我做一个简单的解释:
nginx作为Web服务器,当访问以/api
为前缀的路径时,将流量转发到另一台服务器go,这是一个反向代理,我们也可以基于这一原理实现负载均衡,将流量转发到服务
而不是具体的实例
,例如
ini
upstream goServer {
server http://localhost:9000 weight=10;
server http://localhost:9001 weight=2;
server http://localhost:9002;
}
location /api {
# proxy_pass https://localhost:9000/;
proxy_pass goServer;
}
更多详细内容请自行搜索。
http://localhost:80
通过axois访问http://localhost:9000/hello
需要解决跨域问题,因为这个是直接调用后端服务的url,允许全部域名。(直接在前端项目中将后端服务写死的这种情况非常不推荐)
我们把后端服务部署在另一台服务器上(api.simple.com)。 详细地说:
- 前端服务打包为dist,使用nginx部署,服务器域名为www.simple.com
- 后端服务启动在本机的8080端口上,服务器名为www.api.simple.com。
在server块中的server_name的意义是,你通过此域名访问该服务,可以匹配的特定的块,一个服务器上是可以定义多个访问域名的。
perl
server {
listen 80;
server_name www.simple.com; # 用一个域名部署前端项目
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
server {
listen 80;
server_name www.api.simple.com; #单独用一个api域名代理到后端,并设置允许跨域
location / {
proxy_pass http://127.0.0.1:8080;
add_header 'Access-Control-Allow-Origin' 'http://www.simple.com'; # 允许www.simple.com跨域
}
}
4. 最佳实践
- 前端路由,直接填写如/user /order
- 前端服务访问后端,前缀添加api,如/api/user /api/order
- nginx部署反向代理到后端,匹配api前缀,并把前缀去掉访问到后端。