原文:《关于 Nginx 和 Nuxt 结合这一事》

最近终于把博客用 Nuxt 重构完了,作为一个跟 nuxt 共处了一个月的猛男,我可以说这框架可以的:SSR解决方案非常巧妙,难度也不大,熟记其生命周期后靠官方文档很快就能上手。如果你有 SEO 的需要完全可以尝试一波

但是在前后端对接和最后部署的时候 nuxt 还是比较多坑的,而且是挺大的那种,这里我列出两个我觉得比较棘手的问题

  • nuxt 默认把 URL /XX 映射到目录 /static/XX,并且无法修改
  • 部分请求是客户端和服务端共享的,如何在封装一个请求方法,使得

    • 在客户端时,请求携带请求头直接发往后端
    • 在服务端时(如首屏渲染),把来自客户端的请求头或 IP 等参数传递给后端

我称他们为资源映射请求传递问题,不知到其他人有没有因此受过困扰,反正我是被这东西卡了挺久的。在此记录这两个问题的解决方案,希望能帮到有需要的人

注:为了简化描述,下文的 Nuxt 服务端简称“服务端”,php 后端简称“后端”,分别是服务器上的两个服务

请求传递问题

要解决这个问题,最好先认真考虑一下浏览器发起的请求是如何在服务器里反复横跳的。假设 nuxt 服务端工作在8080端口,后端(这里 php 为例)工作在9000端口,理想情况下(先不考虑静态资源的请求)如下图,颜色相同的线条为一对请求和响应。

请求流

对应到代码,图中的③和⑤是可能请求到同一个接口的(如asyncData中的请求),两种请求的发起点一个是客户端(本身就带着正确的请求头和IP),另一个是 nuxt 服务端(请求头基本为空,IP为本机IP)。前者符合需求好的,后者问题就大了,后端丢失了客户端的信息。
所以这里的重点是“传递原请求”,即把③的请求头和 IP 更换为②的请求头和 IP

请求头传递

针对请求头的传递,可以利用 context.req 的特性:当在客户端运行时,requndefine;在服务端运行时 req 为nodejs 的 Request 对象,包含请求②的信息,req.headers 就是其请求头。
有了这个特性就可以根据 req 是否为 undefine 判断是否在服务端,只有在服务端时才需要用原请求头覆盖现请求头。下面是实现代码,丢到 plugins 中注册注入至双端即可

import axios from 'axios';
import qs from 'qs';
axios.defaults.timeout = 10000;
axios.defaults.withCredentials = true;

export default ({app,req},inject)=>{
  //封装get方法
  inject('fetch',(url,params={},req=null)=>{
    return new Promise((resolve,reject)=>{
      axios.get(url,{
        params:params,
        headers:req?req.headers:{}
      }).then(response=>{
        resolve(response)
      }).catch(err=>{
        reject(err)
      })
    })
  });
  //封装post方法
  inject('post',(url,data={},req=null)=>{
    return new Promise((resolve,reject)=>{
      axios.post(url,qs.stringify(data),{
        headers:{
          'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
          ...(req?req.headers:{})
        }
      }).then(response=>{
          resolve(response)
        }).catch(err=>{
        reject(err)
      })
    })
  });
}

使用的时候,只要涉及服务端的请求必须要传 req 进去,如 this.$fetch('114514.php',{},req);而只涉及客户端的请求可以省略 req 参数如 this.$post('114514.php',{})

IP 传递

请求的IP原本并不存储于请求头中,而 php 获取的 IP 只认TCP连接的 IP,因此为了传递 IP 需要开辟一个新字段去存储,让后端从这个新字段获取客户端的 IP。Nginx 有现成的 proxy_set_header 指令帮我们在反向代理的时候修改请求头。如果有更多需要,我们甚至可以 set 多几个

upstream nuxtserver{
    server 127.0.0.1:8080;
    keepalive 64;
}
server{
    #...
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        # 把客户端的IP的放在 X-Real-IP 字段中
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        proxy_pass http://nuxtserver;
    }
    #...
}

这些新增的字段最终会出现在客户端的 req.headers 中,再使用上面请求头传递的方法,IP 等信息就能传递到后端(如php就能通过$_SERVER['HTTP_X_REAL_IP']获取到设置的IP

映射问题

URL 指向通常有三类:页面、静态资源,后端接口
nuxt 最大的问题是会把除页面外的类型指向 /XX 映射到目录 /static/XX
最简单的解决方法就是把资源文件,接口文件全丢进 static 文件夹里,但这会显得非常违和,哪有接口路径是以 static 开头的。

目前能找到比较有效的方法是在 nginx 的配置文件中写规则进行拦截,一旦确定URL是请求资源的,全部由 nginx 映射到对应的目录或者禁止访问,剩下的访问页面的 URL 就可以放心交给 nuxt

server{
    #...
    # 禁止文件(夹)
    location ^~ /node_modules {
        return 404;
    }
    # 静态资源
    location ~ ^/(downloads|music|site|static|tmp|uploads)/ {
        root html;
        expires 7d;
    }
    # PHP CGI
    location ~ \.php$ {
        root           html;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
    # 最后交由代理
    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        proxy_pass http://nuxtserver;
    }
    #...
}

_
记录到此为止,如果有 dalao 有优化建议或者新的解决思路,欢迎提出讨论


忍野忍
209 声望187 粉丝

自分の世界を変えるのは自分。 —— モルガナ