(一)Nginx 基本:静态资源代理与反向代理

798 阅读11分钟

在当今的技术领域中,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 网站有相关提问:

  1. 为什么要使用nginx部署前端项目?
  2. How does a Node.js "server" compare with Nginx or Apache servers?
  3. 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 选择默认。

vue-front.png

进入项目并安装项目依赖:

cd vue-front
npm install

(选读)项目开发过程描述

前端开发工程师需求开发中...

开发环境调试,nodejs 为开发环境提供了很好的支持

npm run dev

浏览首页:http://localhost:5173/ vue3-main.png


项目构建,将在项目根目录下生成 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.htmlindex.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:80http://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 后端。

  1. 直连访问 Golang 后端
  2. 访问 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.1nginx-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 上运行显示:

nginx-example.png


下面进行效果演示与分析:

在浏览器中输入 http://localhost/ ,并打开 F12 开启调试功能——网络

nginx-vue.png

  1. 访问 http://localhost/

通过浏览器(如 Chrome )请求到 Nginx,其作为 HTTP Web 服务器直接返回静态资源 dist 包内容。

  1. Vue 项目浏览器 Fetct/XHR 访问同源 URL /api/hello,Nginx 作为代理访问 http://localhost:9000/

Nginx 和 Golang 均为服务器,可以正常访问,返回「Hello,我是Golang服务消息」,访问地址:http://localhost:9000/hello

  1. 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 项目部署最佳实践。希望得到大家的关注和点赞。


关注微信公众号,获取运维资讯

如果此篇文章对你有所帮助,感谢你的点赞收藏,也欢迎在评论区友好交流。

微信搜索关注公众号:持续运维

参考

  1. Docker 启动的 nginx 如何解决浏览器跨域问题,segmentfault.com/q/101000004…
  2. Nginx 官方网站,nginx.org/en/
  3. 为什么选择 nginx 部署前端,segmentfault.com/q/101000004…
  4. Vite 静态资源部署,cn.vitejs.dev/guide/stati…
  5. Nginx Reverse Proxy,docs.nginx.com/nginx/admin…