在当今的技术领域中,Nginx 已然成为前端开发、后端开发以及运维工作不可或缺的关键技能。然而,当我们在技术海洋中寻觅有关 Nginx 的学习资源时,却发现诸多不尽人意之处。现有的 Nginx 技术博客,要么只是机械地罗列功能,繁杂琐碎且逻辑架构混乱不清,让读者深陷于功能的泥沼中难以把握重点;要么仅仅偏向于某一项技术实现,犹如管中窥豹,无法深入理解 Nginx 核心功能与能力。
为了突破这些局限,为大家带来不一样的 Nginx 学习体验,将从 Nginx 最基础、最核心的功能开始,分三篇文章专题,摒弃扩展功能的冗长罗列,循序渐进以最清晰、最直接的方式深入理解其精髓。在这里,您将发现 Nginx 如何一站式地解决前端服务部署的难题,巧妙化解浏览器跨域的困扰,轻松实现负载均衡的高效分发,以及精准达成内外网隔离的安全保障。
为了顺应主流技术的演进潮流,我们采用 Docker 容器化的方式来启动 Nginx,使您能够在熟悉现代技术实践的同时,更好地理解 Nginx 在容器化环境中的应用优势。不仅如此,通过实践示例项目,全方位地体会 Nginx 的强大功能。
本文是第一章,Nginx 基本:静态资源代理与反向代理
项目地址:
Nginx简介
Nginx ("engine x")是 HTTP Web 服务器、反向代理、内容缓存、负载均衡器、TCP/UDP 代理服务器和邮件代理服务器。最初由 Igor Sysoev 编写,并在 2 条款 BSD 许可证下发布。2019年3月11日,Nginx 公司被 F5网络公司以 6.7 亿美元收购。
基于笔者的认识与实践经验,Nginx提供的核心功能包括:HTTP Web 服务器、反向代理服务器、邮件代理服务器,其中 HTTP Web 服务器功能又可称为静态资源代理。内容缓存、负载均衡、SSL支持、限流等都可以作为基本能力或扩展能力包含于上述三个核心功能中。
在国内,与 Nginx 功能相似且总是拿来对比的软件项目有 Apache Http Server(httpd) 和 Tomcat。本文中不作讨论和对比,只给出结论: 基于 Java 的 Spring 项目内置 Tomcat,Nginx 作为 HTTP Web 服务器部署前端项目以及反向代理。虽然 Apache 自称是全球 Web 服务器市场占有率最高,但 Nginx 的连年增长大有赶超的势头。
技术选型:为什么是 Nginx?
前端 Web 项目普遍使用 React、Vue 框架开发,在开发完成后打包成 dist 部署。dist 为静态资源包,依赖 Web 服务器 Apache、Nginx 等对外提供服务。
开发前端项目时,Node.js 可以提供运行时环境,也支持部署前端服务,为什么要使用 Nginx 或 Apache 等传统的 Web 服务器来托管前端项目?
这个问题在 SegmentFault、Reddit、StackOverFlow 网站有相关提问:
- 为什么要使用nginx部署前端项目?
- How does a Node.js "server" compare with Nginx or Apache servers?
- Nginx/Apache with Nodejs? Why?
Nginx 使用异步非阻塞的方式处理请求,这意味着它可以同时处理成千上万的连接,而不需要为每个连接创建一个线程或进程。Nginx 的这种事件驱动架构使其在处理高并发连接时更加高效。总的来说,Nginx 或 Apache 提供了更强大的性能支持和更丰富的功能。
Nginx 使用 epoll 网络模型,epoll 是同步非阻塞的,那为什么又说 Nginx 是异步非阻塞的?
Nginx 基本能力
本章介绍 Nginx 的两大基本功能和应用场景。
开发环境要求:
若您只是为了在本地项目运行体验,Docker 是你唯一所需要的
- Git
- Docker (Docker Desktop 或 Docker Engine)
- nodejs
- Golang(可选的)
静态资源代理
Tomcat 是 Java 项目内置服务器,最流行的 SpringBoot 项目打包为 jar,可直接独立运行。
国内互联网公司及项目普遍采用 Nginx 部署基于 React、Vue 的前端项目
前端 Web 开发完成后,打包项目为 dist 包,是为静态资源。静态资源对外提供 Web 服务,需使用 Nginx 代理。下面以部署 Vue 项目来展示 Nginx 静态资源代理的能力。
本文采用国内最流行的 Vue 项目作为示例,利用脚手架创建空项目,此处项目命名为 vue-front
npm create vue@latest
项目创建选项,可以一路 Enter
选择默认。
进入项目并安装项目依赖:
cd vue-front
npm install
(选读)项目开发过程描述
前端开发工程师需求开发中...
开发环境调试,nodejs 为开发环境提供了很好的支持
npm run dev
项目构建,将在项目根目录下生成 dist 文件夹,该文件夹为构建后的静态资源包,添加在 .gitignore 中
npm run build
dist 文件夹是静态资源,无法独立对外提供 HTTP 服务,需要 Nginx 作为 Web 服务器提供运行时支持。
本文使用容器 Docker 提供构建服务。
步骤1:自定义配置文件 nginx.conf,代理静态资源 dist
自定义配置文件 nginx.conf 应放置在项目根目录下
此处静态资源代理无需自定义 nginx.conf,使用默认文件 default.conf。为了保持项目的可维护性,推荐在项目中自定义 nginx.conf
配置文件目录:/etc/nginx/
静态资源文件目录:/usr/share/nginx/html
在 /etc/nginx
目录下的主要配置文件为:
/etc/nignx/nginx.conf
:全局配置文件,定义了 Nginx 的基本配置(默认不需要改)/etc/nginx/conf.d/default.conf
:默认配置文件 (用户关注)
查看 nginx.conf
配置文件:
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;
}
此处不做详细介绍,仅对第 30 行代码 include /etc/nginx/conf.d/*.conf;
作解释,在 conf.d
目录
中自定义配置文件,需要以 .conf
为结尾。
查看default.conf
文件:
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 {
listen 80;
listen [::]:80;
server_name localhost;
# 静态资源代理,前端Web服务器
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
其中 6-9 行代码块定义了静态资源代理,对网站根路径 "/" 的请求处理规则。它指定了当用户访问网站首页时,Nginx 将在 /usr/share/nginx/html
目录下寻找默认的索引文件,优先顺序为 index.html
和 index.htm
。
步骤2:创建 Dockerfile,构建容器
依据 Nginx 配置文件,应将静态资源 dist 内容放置到 nginx 容器的 /usr/share/nginx/html
中,见 22 行代码。
使用 node 构建项目,使用 nginx 代理静态资源,作 HTTP Web 服务器。
FROM node:22-alpine3.20 as build-stage
# 作者信息
LABEL authors="xing.xiaolin@foxmail.com"
# 设置工作目录
WORKDIR /app
# 复制所有文件到工作目录
COPY . .
# 安装依赖
RUN npm install
# 构建生产环境下到Vue项目
RUN npm run build
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
# 启动Nginx服务
CMD ["nginx", "-g", "daemon off;"]
构建容器:
# 前端项目容器构建
# 1. 使用 buildx 构建
docker buildx build -t nginx-example/vue-front:0.0.1 .
# 2. 使用 build 构建
docker build -t nginx-example/vue-front:0.0.1 .
# 后端项目容器构建
docker buildx build -t nginx-example/go-backend:0.0.1 .
docker build -t nginx-example/go-backend:0.0.1 .
查看:
docker images | grep vue-front
运行 vue-front 容器:
方式一:Docker Cli
docker run --name vue-front-demo -d -p 80:80 nginx-example/vue-front:0.0.1
方式二:Docker Compose
vue-front 项目根目录下创建 docker-compose.yaml
networks:
nginx-front:
driver: bridge
external: false
services:
vue-front-demo:
image: nginx-example/vue-front:0.0.1
container_name: vue-front-demo
ports:
- "80:80"
networks:
- nginx-front
Docker Compose 启动容器:
# 在项目根目录下执行
docker compose up -d
访问服务地址:http://localhost:80 或 http://localhost/
至此前端项目已经可以作为 HTTP Web 服务器,对外提供服务。
反向代理服务器
思考:什么是正向代理?
流程图展示浏览器获取网络资源的过程,包含 Nginx 反向代理:
sequenceDiagram
User(Web浏览器)->> HTTP Web 服务器(Nginx): 访问 www.example.com,查看首页
HTTP Web 服务器(Nginx)->> User(Web浏览器): HTML 「页面 A」,其中包含外链接获取「资源 B 」
User(Web浏览器)->> HTTP Web 服务器(Nginx): 网页 JavaScript的 XMLHttpRequests 发送 HTTP 请求「资源 B 」
HTTP Web 服务器(Nginx) -->> Spring微服务(Tomcat): 访问后端服务获取「资源 B」
Spring微服务(Tomcat)-->> HTTP Web 服务器(Nginx): 返回「资源 B」
HTTP Web 服务器(Nginx)->> User(Web浏览器): 返回「资源 B」
User(Web浏览器)->> User(Web浏览器): 基于HTML 「页面A」和「资源B」进行页面渲染和展示
在上述流程图中,User 通过 Web 浏览器(如 Chrome )访问 example 首页,Nginx 上代理了静态资源,可直接返回「页面 A」,但页面上的「资源 B」需要浏览器发送 HTTP 请求(XHR/Fetch)向后端服务获取,Nginx 充当了代理的角色,用户只能感受到与 Nginx 进行数据通信。对用户视角来说,「页面A」和「资源B」都是从 Nginx 服务器获取的。反向代理,是代理服务器。
在现代软件架构体系中,用户无法直接向后端服务获取资源,这是内外网络隔离的。Nginx 在互联网暴露面,既对外提供服务,又能访问内网的后端服务。
在 Vue 项目中 /src/App.vue
增加如下代码,使用 axios
访问后端服务。
// 不跨域
let response = axios.get("/api/hello")
.then(function (response) {
console.log(response)
})
// 跨域,通过宿主机访问后端项目。如果不在同一个主机上,可通过域名访问
axios.get("http://localhost:9000/hello")
.then(function (response) {
console.log(response)
})
使用 Golang 简单实现后端示例项目,代码如下:
package main
import (
"fmt"
"net/http"
)
func httpHandler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "Hello,我是Golang服务消息")
if err != nil {
return
}
}
func defaultHandler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintf(w, "Golang后台服务已启动")
if err != nil {
return
}
}
func main() {
var exposePort = ":9000"
http.HandleFunc("/", defaultHandler)
http.HandleFunc("/hello", httpHandler)
err := http.ListenAndServe(exposePort, nil)
if err != nil {
return
}
}
Vue 前端项目有 2 种方法访问到 Golang 后端。
- 直连访问 Golang 后端
- 访问 Nginx,Nginx反向代理访问 Golang 后端并响应
建议在项目根目录中,自定义 nginx.conf,Nginx配置方式有 2 种:
- 【容器构建时】在 Dockerfile 容器构建时确定
- 【容器启动时】在容器启动时使用 volume 挂载
在 vue-front 项目中,修改配置文件 nginx.conf
server {
listen 80;
listen [::]:80;
server_name localhost;
# 静态资源代理,前端Web服务器
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
# 反向代理
location /api {
# 容器内访问
proxy_pass http://go-backend-demo:9000;
# 桥接到宿主机访问
# proxy_pass http://host.docker.internal:9000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
构建容器,命名为 nginx-example/vue-front:0.0.1 和 nginx-example/go-backend:0.0.1
将 Vue 前端项目和 Golang 后端项目编排启动:
networks:
nginx-example:
driver: bridge
external: false
services:
vue-front-demo:
image: nginx-example/vue-front:0.0.1
container_name: vue-front-demo
ports:
- "80:80"
networks:
- nginx-example
go-backend-demo:
image: nginx-example/go-backend:0.0.1
container_name: go-backend-demo
ports:
- "9000:9000"
networks:
- nginx-example
在项目根目录下启动
docker compose up -d
在 Docker Desktop 上运行显示:
下面进行效果演示与分析:
在浏览器中输入 http://localhost/ ,并打开 F12 开启调试功能——网络
通过浏览器(如 Chrome )请求到 Nginx,其作为 HTTP Web 服务器直接返回静态资源 dist 包内容。
- Vue 项目浏览器 Fetct/XHR 访问同源 URL
/api/hello
,Nginx 作为代理访问 http://localhost:9000/
Nginx 和 Golang 均为服务器,可以正常访问,返回「Hello,我是Golang服务消息」,访问地址:http://localhost:9000/hello
- Vue 项目浏览器 Fetct/XHR 访问 URL http://localhost:9000/hello
浏览器跨域错误 CORS
sequenceDiagram
User(Web浏览器)->> Nginx-Vue: 访问 http://localhost/
Nginx-Vue ->> User(Web浏览器): HTML等静态资源
par
User(Web浏览器) ->> Golang: 浏览器依赖 XMLHttpRequests 发送 HTTP 请求: http://localhost:9000/hello
Golang ->> User(Web浏览器): CORS 错误
and
User(Web浏览器)->> Nginx-Vue: 浏览器依赖 XMLHttpRequests 发送 HTTP 请求: /api/hello,同源访问 http://localhost/api/hello
Nginx-Vue ->> Golang: 对 URL /api/hello 匹配并请求转发
Golang ->> Nginx-Vue: 「Hello,我是Golang服务消息」
Nginx-Vue ->> User(Web浏览器): 「Hello,我是Golang服务消息」
end
Q & A
1. HTTP 服务器、Web 服务器、HTTP Web服务器,这些概念有什么区别?
上述概念有些混淆,HTTP 是 TCP/IP 协议栈中最常见的应用层协议,客户端服务器之间通过 HTTP 协议进行数据通信。简单理解:C/S、B/S 架构中的 Server 即为服务器,对外提供服务。可以区分:
- 静态 Web 服务器:对HTML、JavaScript、CSS作静态资源代理,如 Nginx、Apache Http Server
- 动态 Web 服务器:代理编程语言如Python、Java、Php等后端服务,如 Tomcat。
2. 为什么会出现跨域 CORS 错误,这是什么?
跨域 CORS 是浏览器的特性,当浏览器以 Fetch/XHR 发送 HTTP 网络请求到非同源服务器时,报错。同源指的是:协议、主机和端口号 均相同。该问题会专门开专题讲解。
3. 在 Nginx 配置文件中,URL 是如何匹配的,例如 /api/hello
是如何指向 http://go-backend-demo:9000/hello
的?
Nginx 配置文件 URL 路径匹配是重点内容,并支持正则表达式,可先自行搜索,在 Nginx 进阶文章中会讨论。
总结
本文讨论 Nginx 最基本的两大功能:静态资源代理和反向代理,这两项功能分别用于前端项目部署和前后端连接。本文以 Docker 容器化项目实践,体验 Nginx 的能力。下一步会继续写 2 篇文章:1. Nginx进阶:浏览器 CORS 问题、URL 匹配、负载均衡等,2. 微服务架构下 Nginx 项目部署最佳实践。希望得到大家的关注和点赞。
关注微信公众号,获取运维资讯
如果此篇文章对你有所帮助,感谢你的点赞与收藏,也欢迎在评论区友好交流。
微信搜索关注公众号:持续运维
参考
- Docker 启动的 nginx 如何解决浏览器跨域问题,segmentfault.com/q/101000004…
- Nginx 官方网站,nginx.org/en/
- 为什么选择 nginx 部署前端,segmentfault.com/q/101000004…
- Vite 静态资源部署,cn.vitejs.dev/guide/stati…
- Nginx Reverse Proxy,docs.nginx.com/nginx/admin…