通过nginx的upstream配置域名进行http/htts的访问最佳实践方案(406/404问题解决)

这篇具有很好参考价值的文章主要介绍了通过nginx的upstream配置域名进行http/htts的访问最佳实践方案(406/404问题解决)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 背景

​ 最近,开发部门有一个访问需求,被访问方给了我们两个https的域名访问接口,这里假设为:

https://aaa.target.com/my_target/login/
https://bbb.target.com/my_target/login/

​ 这两个域名解析出来的地址和接口信息都是一样的,但是根据要求,需要将两个域名访问接口作为主备的方式进行配置,在https://aaa.target.com/mytarget/login/出现异常不能使用的时候,能够动态切换到https://bbb.target.com/mytarget/login/访问域名接口。

​ 那么通过nginx来进行代理配置,首先想到的就是使用其负载均衡均衡的功能(upstream)对两个域名进行主备配置:

upstream mytarget
{
    server aaa.target.com:443 max_fails=30 fail_timeout=300s;
    server bbb.target.com:443 backup;
}

​ 以上配置,通过upstream创建了一个mytarget的访问池,访问该池的时候,首先会访问aaa.target.com:443,当访问30次失败后,该服务会停用300s,在300s之后重新尝试访问该地址;而当aaa.target.com:443访问失败到达30次后,服务停用,则会启用bbb.target.com地址用于访问。

​ 在server中,最初的配置如下:

server {
        listen       8901;
        server_name  target.server;
        
        location /login/ {
            proxy_pass https://mytarget/my_target/login/;
        }
   }

​ proxy_pass中只需要访问upstream访问池即可。

​ 但是通过实际情况对该网址进行访问(curl http://localhost:8901/login/),却返回了406 Not Acceptable错误。

​ 而当我们不使用upstream的方式进行请求的时候:

server {
        listen       8901;
        server_name  target.server;
        
        location /login/ {
            proxy_pass https://aaa.target.com/my_target/login/;
            #proxy_pass https://bbb.target.com/my_target/login/;
        }
   }

​ 请求(curl http://localhost:8901/login/))则可以顺利完成,HTTP1.1返回200代码。

​ 经过很长时间的分析和测试,最终还是无法解决该问题,故想到了通过“二级跳”的方式变向实现(经过测试,server里面如果是ip,也可以访问的通):

    upstream target {
        server 127.0.0.1:18901;
        server 127.0.0.1:18902 backup;
    }
    server {
        listen       8900;
        server_name  mytarget.server;

        location /login/ {
            proxy_pass   http://target/;
            proxy_next_upstream error timeout http_404 http_403;
        }

    }
    server {
        listen       18901;
        location / {
            proxy_pass   https://aaa.target.com/my_target/login/;
        }

    }
    server {
        listen       18902;
        location / {
            proxy_pass   https://bbb.target.com/my_target/login/;
        }

    }

​ 以上过程也很好理解,即分别对我们需要的https://aaa.target.com/my_target/login/和https://bbb.target.com/my_target/login/地址进行代理,通过本机的18901和18902端口提供服务;而upstream再对本机的18901端口和18902端口进行负载均衡(主备配置),然后再通过本机的8900端口代理访问127.0.0.1的18901和18902端口,最终实现访问https://aaa.target.com/my_target/login/或https://bbb.target.com/my_target/login/

​ 但二级跳的访问方式也具有一些缺陷,这个缺陷主要反映在我的日志可读性上,我们的当前http访问的日志格式如下:

log_format  main  '$remote_addr - $http_referer - $upstream_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$http_cookie" "$upstream_connect_time" "$upstream_response_time" "$request_time"';
    access_log    logs/access.log    main;

​ 也就是说我们在日志中会显示出 u p s t r e a m a d d r ,即被访问端解析出的地址,从而方便我们做一些问题排查依据和手段,但采取“二级跳”的方式,则导致我的 upstream_addr,即被访问端解析出的地址,从而方便我们做一些问题排查依据和手段,但采取“二级跳”的方式,则导致我的 upstreamaddr,即被访问端解析出的地址,从而方便我们做一些问题排查依据和手段,但采取二级跳的方式,则导致我的upstrema_addr显示的均为127.0.0.1:18901/18902,反而给我们的日志观察造成不便。

因此,最好的方式还是解决直接通过域名做负载均衡的访问问题,才能最好的达到我们的要求。但是看似合理的操作,为什么会产生406的问题?406的问题又该如何解决?

二 分析思路

​ 对于406这个问题的分析,我们首先要知道HTTP1.1返回406 Not Acceptable代码代表什么意思?根据资料解释:

http返回406错误的时候,往往说明是客户端错误 , 即客户端无法解析服务端返回的内容,一般是说在客户端发送的accept头里 , 设置了允许接受的类型 , 但是服务端没有按该格式返回,如果accept指定的类型和response返回的content-type类型不一致,会出现406 not acceptable错误。

​ 而针对该问题的解决方式有两种:

1.修改服务端按指定格式返回
2.修改客户端接受服务端的格式

​ 此时,我们可以通过curl -vvv的方式详细显示访问请求及返回代码:

​ 返回成功时侯的content-type:

nginx upstream到域名,中间件,nginx,nginx,http,运维

​ 返回失败时候的content-type:
nginx upstream到域名,中间件,nginx,nginx,http,运维

​ 此时可以看出,当返回406的时候,content-type返回的是text/html格式,而不是正确的application/json格式(其实,该格式是指返回后的内容的格式)。

​ 那么此时其实可以理解为当访问返回406的时候,我们在客户端想向对方的服务器端请求包头中的content-type为json格式,但是最终服务器端并没有找到我们想要的请求内容所以反馈了一个406 Not Acceptable的html。

起初,认为是由于客户端向服务器发送的包头请求类型不对,所以导致服务器返回的content-type也不对,最终产生406错误。因此,着重研究了如何让nginx强制向服务端请求我们想要的content-type,详细配置如下:

upstream mytarget
{
    server aaa.target.com:443 max_fails=30 fail_timeout=300s;
    server bbb.target.com:443 backup;
}


server {
  listen       8901;
  server_name  mytarget.server;
  location /login/ {
      type {} default_type application/json;
      add_header Content-Type 'application/json; charset=utf-8';
      proxy_pass https://mytarget/my_target/login/;
   }

​ 该配置中,实现强制指定nginx请求content-type的部分主要为:

type {} default_type application/json;
add_header Content-Type 'application/json; charset=utf-8';

​ 在nginx中,http层面的配置默认的content-type是application/octet-stream,charset=utf-8,所以在location中type{ }表示会先将默认的content-type置空,然后通过defalut_type将content-type改为application/json,而add_header Content-Type则表示直接在请求头中直接指定Content-type为 ‘application/json; charset=utf-8’;(这里还需要注意,想要强制生效,我们必须还要修改http引入的mime.types文件,需要在mime.type文件中加入application/json json;的配置,否则可能不生效,经过检查在我们配置中,之前就已经加入了)

​ 在这样设置后,再次进行模拟访问尝试(curl -vvv http://localhost:8901/login/), 发现http返回依然是406,而客户端返回的content-type还是text/html。最开始我一直认为是配置未生效,直到我在请求中加入了一段配置:

server {
  listen       8901;
  server_name  mytarget.server;
  location /login/ {
      type {} default_type application/json;
      add_header Content-Type 'application/json; charset=utf-8';
      return 200 '{"status":"success","result":"nginx json"}'
      proxy_pass https://mytarget/my_target/login/;
   }

​ 再次模拟访问尝试(curl -vvv http://localhost:8901/login/),发现nginx返回了200,且返回的内容也是我们定义的’{“status”:“success”,“result”:“nginx json”}',而这也说明我们的配置生效了。但是为什么直接访问地址还是不行呢?
nginx upstream到域名,中间件,nginx,nginx,http,运维

​ 通过资料查询,我知道了,nginx在做代理转发的时候,会自行将前段请求请求头进行处理,并根据我们处理结果,向服务端发送请求。那么当我们请求头处理异常时,则会导致nginx转发到后段的请求由于服务器无法正确相应,返回406、400、404等错误。

​ 所以,我认为处理该问题最好的办法就是让nginx按照我们想要的方式处理前段发来的请求头,那么处理请求头该如何设置呢?在nginx中有一个参数,即proxy_set_header。

​ 该参数可以根据我们的需求设置请求头,而这里最终要的一个即为proxy_set_header HOST。在nginx官方指导文档中,proxy_set_header HOST有几种写法:

proxy_set_header HOST $host
proxy_set_header HOST $proxy_host
proxy_set_header HOST $host:$proxy_port
proxy_set_header HOST $http_host

这里,对几种方法的解释如下:

1.不设置proxy_set_header Host时,浏览器直接访问nginx,获取到的Host是proxy_pass后面的值,即 $proxy_host的值;
2.设置proxy_set_header Host $host时,浏览器直接访问nginx,获取到的Host是$host的值,没有端口信息,此时代码中如果有重定向路由,那么重定向时就会丢失端口信息,导致 404;
3.设置proxy_set_header Host $host:$proxy_port时,浏览器直接访问nginx,获取到的Host是 $host:$proxy_port的值;
4.proxy_set_header Host $http_host时,浏览器直接访问nginx,获取到的Host包含浏览器请求的IP和端口;

​ 此时,则可以知道,我们在对HOST设置不同变量的时候,则会封装不同的请求头,当请求头在远端服务器找不到的时候,则无法访问。

​ 由于在不设置proxy_set_header HOST的时候,默认时取proxy_pass后面的值,那此时其实向服务器端请求的时候,我们认为是向mystarget的upstream池中的地址发出请求,实际上则是向服务器真实请求了mytarget,而在服务器端根本不存在mytarget这个请求内容,所以会返回406这种无法处理客户端请求的消息。

​ 搞清楚原理后,我们则只需设置对应的HOST到请求头中就可以解决该问题了,但是发现我们设置了官方给的方式,都不能达到效果。

那么,这里就需要介绍nginx中proxy_set_header的隐藏用法(这个经过网上鲜有的资料和多次尝试以后,得出的结论):

​ 1.在proxy_set_header HOST后,我们可以直接加upstream中明确的域名地址:aaa.target.com,即

upstream mytarget
{
    server aaa.target.com:443 max_fails=30 fail_timeout=300s;
    server bbb.target.com:443 backup;
}
server {
        listen       8901;
        server_name  mytarget.server;
location /login/ {
            proxy_set_header Host aaa.target.com;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass https://mytarget/my_target/login/;

​ 该方式找了很久,找到两篇帖子这样设置,跟着设置后,发现返回成功。

​ 继续查阅资料发现,另外一篇帖子上虽然也是用上述方法,但是进行了更加详细的解释。大概意思是,proxy_set_header Host就是向服务器请求vhost的server_name,我们不能将该参数写成$http_host,否则请求的则是我们代理的server_name,即mytarget.server。同样,在服务器端并没有mytarget.server的vhost,对方服务器的vhost是aaa.target.com/bbb.target.com。

根据上述资料理解,其实可以理解:

1.当我们不配置HOST,则客户端会向服务端请求mytarget;

2.当我们配置HOST为$host的时候,则会向对方请求本机的ip或者域名,且不带端口;

3.当我们配置为 h o s t : host: host:proxy_prot的时候,则会向对方请求本机的ip或者域名,且带端口;

4.当我们配置为$http_host的时候,则会向对方请求我们设置的server_name,即target.server;

5.当我们配置为指定的aaa.target.com/bbb.target.com时,则会向对方请求响应的vhost;

​ 综上,只有第五种的配置可以实现我们向服务器发出正确的请求,而其他四种配置都无法在服务端找到正确的vhost,从而导致返回出现406或者404错误。

​ 可是,这种方式也存在问题。在我们的场景下,需要有两个域名,而这里我们只能使用一个域名,那么当aaa.target.com不可用的时候,需要请求bbb.target.com,但是proxy_set_header HOST依然回去请求aaa.target.com,两者不一样,则无法返回正确的值,则依然返回406,此时我们则需要手动进行调整,非常麻烦。

​ 于是我想通过第四种方式,将我们的server_name设置为我需要的aaa.target.com/bbb.target.com,然后使用$http_host,但发现还是不行(这里与查询到的帖子写的有所不符,不知道为什么)。

​ 最后,经过多次尝试,发现了proxy_set_header的隐藏用法:

​ 2.在proxy_set_header HOST后,我们可以直接加upstream中将server_name以变量的形式带入,而在server_name中,我们则可以写入我们需要请求的vhost(而且可以写多个),即server_name aaa.target.com bbb.target.com。具体写法如下:

upstream mytarget
{
    server aaa.target.com:443 max_fails=30 fail_timeout=300s;
    server bbb.target.com:443 backup;
}
server {
        listen       8901;
        server_name  aaa.target.com bbb.target.com;
location /login/ {
            proxy_set_header Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_pass https://mytarget/my_target/login/;

​ 经过模拟访问尝试,发现这种写法可以成功通过upstream的方式访问http/https。

三 问题解决方案

​ 在经过长时间的查阅资料、学习、理解和测试中,最终我们找到了nginx主备方式访问域名解决方案,为了更好的配合nginx代理作用,避免域名对应的ip改变,由于nginx解析缓存,导致nginx无法访问,我又加入了动态解析dns的相关配置,最终形成完整的最佳解决方案,具体完整访问配置最佳解决方案如下:

    upstream mytarget {
        server aaa.target.com:443 max_fails=10 fail_timeout=300s;
        server bbb.target.com:443 backup;
    }


    server {
        listen       8901;
        server_name  aaa.target.com bbb.target.com;
        resolver 61.139.2.69 valid=10s ipv6=off;

        location /login/ {
            #default_type application/json;
            #add_header Content-Type 'application/json; charset=utf-8';
            proxy_set_header Host $server_name;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
            set $cmpassport_addr https://mytarget/my_target/login/;
            proxy_pass $cmpassport_addr;
        }
   }

​ 想要动态解析,我们必须要将proxy_pass访问地址设置为变量,在变量中指定我们的具体访问地址,然后再加上resolver配置即可。这里的resolver表示通过61.139.2.69(四川电信)的dns,每10s中动态解析一次server中的域名,且关闭ipv6的解析。文章来源地址https://www.toymoban.com/news/detail-806023.html

到了这里,关于通过nginx的upstream配置域名进行http/htts的访问最佳实践方案(406/404问题解决)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • nginx 配置代理ip访问https的域名配置

    目录 问题背景 解决方式 正向代理:  反向代理:  通俗点儿一句话,正向与反向的区别: 在某些单位或机构内部,访问互联网接口需要通过指定的服务器去访问,那我们就需要通过代理 ip 和 端口去访问外网域名。 示例:如何通过指定 ip 和 端口 访问 https://api.elecredit.co

    2024年02月14日
    浏览(49)
  • 【linux】Nginx企业级优化:恶意域名解析优化、禁止IP访问网站、HTTP请求方法优化

    鱼弦:公众号:红尘灯塔,CSDN内容合伙人、CSDN新星导师、51CTO(Top红人+专家博主) 、github开源爱好者(go-zero源码二次开发、游戏后端架构 https://github.com/Peakchen) 恶意域名解析优化: 恶意域名解析优化是指通过配置Nginx,阻止恶意域名对服务器的访问,以提高服务器的安全性

    2024年04月26日
    浏览(38)
  • nginx网站服务(下载,配置,命令,实现访问状态统计,访问控制,域名 IP端口访问,身份验证)

    目录 概念 Nginx的优势和特点: 下载配置nginx(两种方式编译和yum) 编译安装,安装依赖  创建运行用户、组 解压nginx压缩包 配置Nginx  编译安装 修改权限 让系统识别nginx的操作命令 配置 nginx命令 nginx的配置文件 全局配置 模块 location的配置 1. root 指令: 2. alias 指令: 实现

    2024年02月19日
    浏览(53)
  • Nginx学习1:通过访问路径代理不同二级http服务

    通过nginx实现代理,前端HTML5只需要对接一个http端口,即可与后台多个二级http服务进行对接,方法是在nginx中通过不同的路径代理不同的二级http服务。 静态页面依然使用nginx一级服务器返回。 配置nginx,访问路径为button1时转发到3002端口,访问路径为button2时转发到3003端口。

    2024年02月12日
    浏览(43)
  • web开发-springboot配置https与域名进行访问

    目录 步骤1:域名解析ip 步骤2:下载ssl证书 步骤3:把证书放入springboot项目里,并配置 步骤4:打包放上去服务器运行 现在云服务器添加自己的域名与子域名进行解析ip(没有域名自己去买哈) springboot的服务器是tomcat,所以要下载对应的文件    下载下来 放入文件,写入配

    2024年02月16日
    浏览(37)
  • nginx下添加http_ssl_module并且配置域名,指定端口

    1.切换到源码包: 2.进行编译: 3.配置完成后,运行命令: make命令执行后,不要进行make install,否则会覆盖安装。 4.备份原有已安装好的nginx: 5.停止nginx状态: 6.将编译好的nginx覆盖掉原有的nginx: 7.提示是否覆盖,输入yes即可。 8.然后启动nginx: 9.进入nginx/sbin目录下,通过

    2024年02月11日
    浏览(51)
  • 【碎片知识点】springboot配置https与域名进行访问

    目录 步骤1:域名解析ip 步骤2:下载ssl证书 步骤3:把证书放入springboot项目里,并配置 步骤4:打包放上去服务器运行 现在云服务器添加自己的域名与子域名进行解析ip(没有域名自己去买哈) springboot的服务器是tomcat,所以要下载对应的文件    下载下来 放入文件,写入配

    2024年02月03日
    浏览(40)
  • k8s 通过配置 hostAliases 来进行域名解析

    在 Kubernetes (K8s) 中,hostAliases 是一种用于在 Pod 中配置主机名与 IP 地址映射的机制。通过使用 hostAliases,你可以将指定的主机名映射到 Pod 所在节点的 IP 地址,从而实现对主机名的自定义解析。这对于一些特定的用例,比如与主机上的外部资源进行交互,非常有用。 以下是一

    2024年02月08日
    浏览(49)
  • http的ssl证书保姆级配置安装-多域名 免费ssl证书 解析 nginx配置

    摘要:多个域名(mysite.com,*.mysite.com),免费证书,添加解析记录,申请证书的shell脚本,nginx配置 登录免费证书网站:https://freessl.cn/ 输入:mysite.com,*.mysite.com 选择亚洲诚信trustasia 点击“创建免费的ssl证书” ACME域名配置 域名:确认刚才输入的mysite.com,*.mysite.com无误,点击下

    2024年02月09日
    浏览(100)
  • tengine/nginx https请求 转发 http upstream

    当前的互联网应用基本都要支持https协议,而当浏览器头通过https协议将请求发到到负责负载的nginx后,会由当前nginx再以http协议向后端upstream进行请求,之所以这么做是因为https协议的安全性也带来的额外的性能消耗。而源端基本都是在一个内网里面的,对于通讯协议的安全性

    2024年01月23日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包