Nginx从入门到解决实际问题

Nginx从入门到解决实际问题

本文为nginx的入门教程,适合零基础同学,以及了解较浅显的同学。

Docker启动的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的功能主要由两个关键的配置文件决定。

  1. 系统配置文件目录/etc/nginx/
  2. 媒体资源文件目录/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上已经安装了npmgit,核心代码内容

ts 复制代码
// 不跨域
axios.get("/api/hello")
    .then(function (response) {
      console.log(response)
    })


// 跨域访问后端项目
axios.get("http://localhost:9000/hello")
    .then(function (response) {
      console.log(response)
    })
  1. 拉取仓库
shell 复制代码
git clone https://github.com/xlxingRun/nginx-demo-front.git
  1. 安装依赖
shell 复制代码
npm install
  1. 开发环境热启动(仅在开发环境使用)
shell 复制代码
npm run dev
  1. 编译打包
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)。 详细地说:

在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前缀,并把前缀去掉访问到后端。
相关推荐
fajianchen19 小时前
什么是HTTP/2协议?NGINX如何支持HTTP/2并提升网站性能?
nginx·http
m0_7482487721 小时前
在 Ubuntu 上安装 Nginx 的详细指南
nginx·ubuntu·postgresql
若云止水1 天前
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_os_init 函数
运维·nginx
m0_512744641 天前
Nginx(详解以及如何使用)
运维·服务器·nginx
铁锅与大鹅2 天前
http+nginx
网络协议·nginx·http
s_fox_2 天前
Nginx Embedded Variables 嵌入式变量解析(4)
java·网络·nginx
致奋斗的我们2 天前
Nginx反向代理及负载均衡
linux·运维·mysql·nginx·负载均衡·shell·openeluer
招风的黑耳2 天前
使用Nginx本地部署Axure生成的HTML文件,局域网内浏览器通过IP和地址访问
nginx·html·axure·本地部署
s_fox_2 天前
nginx ngx_http_module(7) 指令详解
运维·nginx·http
若云止水2 天前
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_process_options
运维·nginx