Nginx
帖子数 53
关注者 46

Nginx 发表了文章

NGINX第四季资料下载

感谢您参加NGINX开源社区基础培训系列课程(第四季),以下为本系列培训的课件和录像,希望您能通过此培训学有成果,祝学习进步!> 课程演讲稿下载: -9月03日:NGINX性能为什么远高于Apache?-9月10日:Tengine与官方NGINX的差别在哪里?-9月17日:OpenResty与官方NGINX的差别在哪里?-9月24日:从Lua语言看NGINX生态的扩展> 视频回顾:-9月03日:NGINX性能为什么远高于Apache? -9月10日:Tengine与官方NGINX的差别在哪里? -9月17日:OpenResty与官方NGINX的差别在哪里? -9月24日:从Lua语言看NGINX生态的扩展 > 访问NGINX开源社区:https://www.nginx.org.cn/> NGINX 官方微信群(扫码入群) > 后续活动推荐 NGINX 公开课:9月23日: 使用NGINX/NGINX Plus构建CDN如果您通过互联网向受众交付内容并关心性能,那么您很可能使用CDN。CDN是广告、媒体、在线游戏、商业、移动、医疗、甚至政府和教育等领域的流行使用场景,因为用户可以获得更快地应用和网页访问能力。主要的CDN提供商都使用NGINX,因为商业CDN服务的扩展成本很高,在细粒度控制和定制方面受到极大限制,并且提供的性能优化更少。如果您的企业CDN费用高得惊人,并且您需要更多的控制和定制,那么构建自己的CDN将更具成本效益。主题:使用NGINX/NGINX Plus构建CDN时间:9月23日下午2-3点讲师:NGINX 解决方案架构师邹俊通过本次公开课,您可以了解: - CDN的部署架构- CDN用例- NGINX配置和调优- CDN遥测 邹俊  NGINX 大中华区解决方案架构师长期从事软件开发和系统架构设计工作,在企业级软件领域拥有超过10年的工作经验。先后供职于CA,EMC,Pivotal等公司。在十多年的软件行业从业经历中,积累了丰富的容器云平台架构设计,自动化平台运维和系统稳定性调优等相关经验,主要关注于微服务,APIM,k8s,service mesh等行业技术的发展。点击链接立即报名NGINX公开课:http://www.f5chinanetworks.com/partner/wechat/datacenter/campaign/meetingreg.asp?meetingid=72&trackingcode=NGINXCommunity  F5作为应用交付领域的领导者,在过去的几年中每年都会举办一次针对金融科技领域的线下研讨会,反响热烈。由于疫情原因,今年的研讨会将以线上的形式开展,我们将继续保持高品质的内容输出,为广大金融从业者奉献一场专为金融行业烹制的饕餮盛宴。会议主题:2020 F5金融科技趋势研讨会会议时间:10月16 - 10月17日(周五-周六),下午1:30 - 6:00会议形式:线上研讨会适合参加者:所有金融IT从业人员、架构师、开发者、安全岗、数据库管理人员、运维人员等报名形式:在线注册后, 需审批,通过后可获得直播链接 点击链接立即报名在线研讨会:http://www.f5chinanetworks.com/partner/wechat/datacenter/campaign/meetingreg.asp?meetingid=76&trackingcode=nginxcommunity> 往期课程资料下载点击获取 NGINX开源社区(第一季)课程补充及课程ppt资料点击获取 NGINX开源社区(第二季)课程补充及课程ppt资料点击获取NGINX开源社区(第三季)课程补充及课程ppt资料点击获取NGINX系列公开课:使用NGINX/NGINX Plus构建API网关课程视频和ppt资料> 【邀请您加入NGINX开源社区】 NGINX 开源社区在5月20日在中国正式落地并开门迎客了。Nginx的的开源社区的英文F5 / NGINX面向所有NGINX用户的官方社区。我们秉持“开放,包容,沟通,贡献“(开,包括,连接,贡献)之力量,与发展中国家共建开放,包容,活跃的“ NGINX用户之家”;秉承开源的精神,在社区治理上高度开放,为所有NGINX的用户,者开发技术状语从句:爱好者,提供一个方便学习,讨论的场所。也期待您成为此社区中活跃的一员,贡献您的文章,博客,代码,踊跃讨论与回答问题,打造您个人品牌和影响力。  

点赞 1
318 次浏览
0 条评论

洪志道 发表了文章

编程是门手艺,NGINX社区的经验分享

前言关于我:NJS核心开发者,Unit贡献者,NGINX专家编程是门手艺是一个系列,分享我在编程上的思考,尤其是在NGINX社区上学到的。编程是逻辑的创造,力求清晰这是我对编程的理解,代码体现的是业务的本质,也可以说成是一种抽象。既然是逻辑,清晰是最最重要的。以Linus在Ted的例子为例:删除列表里的元素。不好的逻辑:遍历列表,找到元素entry。如果entry有没前结点prev,将头部指向entry的next;如果有则将prev的next指向entry的next。好的逻辑:遍历列表,找到元素entry的引用,将引用指向entry的next。虽然两者都能达到目的,但是明显后者更清晰,代码更简洁。不清晰的逻辑往往有更多的特殊处理,反映为代码有更多的if分支。如果你想锻炼更好的逻辑,试试将代码里的if分支优化掉。前几年NGINX作者还在NJS项目时,跟他交集比现在更多。NJS是他亲自设计和实现的纯C JS引擎,它的代码跟NGINX没任何关系。两个软件都有红黑树的实现,NJS的版本是我见过最好红黑树实现,没有之一。我还给他提过一点小改进,我个人很喜欢红黑树这个数据结构。NJS的逻辑更清晰,如果看它实现的话。这种带来的好处是它的API更易用。比较下两者的使用:ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel, ngx_http_file_cache_rbtree_insert_value);ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);njs_rbtree_init(&tree, rbtree_unit_test_comparison);njs_rbtree_insert(&tree, &items[i].node);唯一的区别是rbtree_init时,njs版本更简单,nginx版本需要一个叫sentinel的哨子节点。njs的实现逻辑更清晰,将那个特殊处理优化掉了。简而言之,代码应该做到逻辑清晰。也可以理解为更进一步的抽象。良好代码的判断标准@drdrxp提过一种方式,增加行数和删除行数比例为4:1。这个非常直观有效,尤其适用于稳定的项目。对于新项目,这个比例可以提高为3:1,因为新项目往往设计考虑的不够,需要更多的重构动作。以NJS为例,目前算稳定的项目了。xeioex#1xeioex805 commits  忽略数据,有大量的文件重命名重构igorsysoev#2igorsysoev313 commits 58,714 ++ 15,181 --lexborisov#3lexborisov124 commits 29,307 ++ 11,163 --       hongzhidao#4hongzhidao108 commits 12,864 ++ 10,028 --@Igor贡献了整个软件的基础库,实现了早期的JS语法。他的代码行数大约4:1。@xeioex绝对核心,实现了大量的JS功能。@lexborisov语言解析方面的专家,作了很多的改进。@hongzhidao我是第三个参与的,实现部分JS功能,并重构一些基础库。可以看出我跟@lexborisov因为做了更多的重构,增和删的比较很高。这方式是我印象比较深的一种,特别指出来,仅供参考。重构,提升能力最实用的习惯以下是Igor在NGINX上的一个重构例子,就像做了卫生打扫一样,让房间变的更干净了。重构就是这么一种技能,在不改变原来行为的前提,通过内部的优化让代码变的更具扩展性。一定要记住,不能改变原来的行为。重构的目的是让代码更具扩展性,就是为了添加新功能更简单。重构所做的事情可以很多,从小到大有这么几种常见的:1. 细小:改错字;重命名变量、函数和文件等,让代码更具可读性。2. 逻辑优化:比如Linus前面的那个例子,让逻辑更清晰。3. 设计改进:比如NGINX到NJS的红黑树实现。重构会有整一篇幅深入研究,尤其我在社区实践来的。测试用例,不可或缺这里只讨论针对代码的测试用例。以下是我实现Lua版本radix tree写的测试用例。function test_get() local tree = radix_tree.new(); tree:insert(‘foo‘, 1); tree:insert(‘bar‘, 2); tree:insert(‘zoo‘, 3); assert(tree:get(‘bar‘) == 2, ‘get bar‘); assert(tree:get(‘noop‘) == nil, ‘get non‘);endNJS比NGINX好一点的是,基础库比如红黑树之类的都有对应的测试用例,我认为这点非常重要,也是很多项目没做好的,一旦这些实现有问题,就是大规模的雪崩。当然也有很多将私下自己写测试用例的,没有开源出来。前面提的重构,如果没有测试用例就不可能进行。任何改的代码都有可能出问题,怎么保证没问题呢,跑一遍测试用例是最好的方式。在NJS项目里,我跟另外两个经常在修bug和增加功能时,会做一些重构。有时当天发现的bug,如果紧急当天修复,然后发布。敢做这样的事,得益于我们有大量的测试用例。测试用例也会有专门的篇幅继续深入介绍。命名,很大一门学问编程有两大难点,命名和缓存。命名这看起来简单的事情,要做好非常不容易。这课题放在此系列的另一篇中。设计,真正的功夫好的设计能减少代码的实现成本。NGINX代码最复杂的部分应该是buf那块。比如响应是一个流式内容,想对这内容做过滤处理。ssi那个模块就是干这事的。C跟其它语言不一样,几乎都在跟内存打交道。NGINX有内存池,但它只能统一分配和释放。上面举例的内容你需要保存,在NGINX里存放在buf这样的地方,这个buf本身又需要占内存。因此为了复用buf,NGINX做了非常复杂的实现。反观在NJS里,它实现了动态分配的内存池,既然内存可以动态分配和回收,就不需要buf复用了,我估计如果NGINX有这样的实现,代码至少可以简化30%。我以前说过,因为大量的第三方模块,导致NGINX没法引入这样的改进,有点可惜。我在自己的nginx-lua-module里,参考了njs不少好的设计。因此,好的设计可以节省很多的实现成本,也让软件更具健壮性和扩展性。这往往取决于实施者本身的综合能力,所谓天赋。如何真正提升编程能力我认为专业的程序员体现在两方面:整体设计能力和细节处理能力。我最早给社区提交代码时,大约60%多都会被改动调整,整体设计和细节处理的不好。现在90%多都会直接合并进去,甚至不用任何改动的。所以我想实践是最好的方式,有专家帮你review那再好不过了,他会重审你的设计,改进你的细节。实践是最好的方式,接着你要找到好代码或者好项目。自己实现,然后比对,或者找高手帮你看。好的代码或项目不用多,一个就足够了。找你兴趣的,或者用我推荐的。找基础库练手,这是最简单有效的。现在很多公司面试都要先做题,还有很多人在leetcode上刷题的。其实本质都是考核你的代码能力。举个例子,以下是我实现js_import "../.././aa/bb/cc/../d.js" 这样的语法时需要的函数。voidnjs_file_dirname(const njs_str_t *path, njs_str_t *name){ const u_char *p, *end; if (path->length == 0) { goto current_dir; } p = path->start + path->length - 1; /* Stripping basename. */ while (p >= path->start && *p != ‘/‘) { p--; } end = p + 1; if (end == path->start) { goto current_dir; } /* Stripping trailing slashes. */ while (p >= path->start && *p == ‘/‘) { p--; } p++; if (p == path->start) { p = end; } name->start = path->start; name->length = p - path->start; return;current_dir: *name = njs_str_value(".");}这个例子非常好,从命名,注释粒度,API设计都有涉及。这种就非常适合练手。另一种是自己学习和写项目。推荐utopia,1千行的API网关。即将开源。我会经常分享适合练手的好代码,欢迎关注公众号。编程是门手艺,一个分享编程的系列本文是个开篇。命名、重构、测试等更多编程技能在各自的篇幅里。[nginx-lua-module] https://github.com/hongzhidao/nginx-lua-module[utopia] https://github.com/hongzhidao/utopia 

点赞 2
20 次浏览
0 条评论

守望 发表了文章

Nginx访问日志分析

1.首先说明Nginx日志存放在系统的哪个位置,可以使用下列命令:找到*/nginx/logs/access.log这个关键路径,就是Nginx的访问日志的位置。(其中*代表你电脑nginx文件夹前面的路径)2.Nginx默认的日志格式如下:log_format main ‘$remote_addr - - $remote_user [$time_local] "$request" ‘                           ‘$status $body_bytes_sent "$http_referer" ‘                           ‘"$http_user_agent"    "$http_x_forwarded_for" ‘;3.字段说明(下面是自己主机查看访问日志的一条信息,再下面的文字是解释):192.168.43.189 - - [22/Feb/2019:22:26:07 +0800] "GET /index.php HTTP/1.1" 200 56787 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"远程主机IP地址                  访问时间           时区      方法       资源        协议    状态码  发送字节                        referer                                                 浏览器信息4.日志分析:(1)统计访问IP前十:由于在虚拟机中的Linux服务器只有宿主机登录访问过,因此只有宿主机IP192.168.43.189在Nginx访问日志记录中。5.统计制定某一天的访问IP上述两条命命令都可以实现统计制定某一天的访问IP6.过滤URL7.统计指定资源上面的命令的作用是:处理第7个字段以‘.html’结尾的行8.过滤制定时间后的日志并打印IP9.统计流量10.统计状态码

点赞 0
17 次浏览
0 条评论

守望 发表了文章

Nginx + Lua 搭建网站WAF防火墙

 前言对于项目里面只是使用代理等常用功能,在线安装即可,如需制定化模块,则推荐编译安装PS:本文不仅仅包含Nginx相关的知识点,还包含了逆天学习方法(对待新事物的处理)官方网站:https://nginx.org/Github:https://github.com/nginx/nginxNginx书籍:Nginx Cookbook 中文版 https://huliuqing.gitbooks.io/complete-nginx-cookbook-zh/content/Nginx官方中文文档 https://docshome.gitbooks.io/nginx-docs/content/Nginx入门教程 https://xuexb.github.io/learn-nginx/淘宝Nginx文档 http://tengine.taobao.org/book/1.在线安装1.1.修改yum源地址清华源:https://mirrors.tuna.tsinghua.edu.cn/help/centos/更新软件包缓存:yum makecache1.2.在线安装Nginx在线安装比较简单,参考官方文档即可:https://nginx.org/en/linux_packages.htmlPS:线上选stable的就行了,记得把$releasever改成你的版本号,eg:7安装图示:# 创建nginx的yumvi /etc/yum.repos.d/nginx.repo# 内容如下:[nginx-stable]name=nginx stable repobaseurl=http://nginx.org/packages/centos/7/$basearch/gpgcheck=1enabled=1gpgkey=https://nginx.org/keys/nginx_signing.key# 在线安装yum install nginx -y1.3.端口放行放行80端口:firewall-cmd --zone=public --add-port=80/tcp --permanentPS:规则生效:firewall-cmd --reload1.4.验证安装2.知识拓展2.1.编译参数离线安装可以参考在线安装的配置:nginx -V:编译参数(nginx -v:查看版本)编译参数详解(点我展开)2.2.安装目录在线安装的包都可以通过:rpm -ql xxx查看安装到哪些目录安装目录详解(点我展开)2.3.默认配置配置语法检查:nginx -t -c /etc/nginx/nginx.confPS:不重启的方式加载配置:Nginx -s reload -c /etc/nginx/nginx.conf全局以及服务级别的配置:参数说明user使用用户来运行nginxworker_processes工作进程数error_lognginx的错误日记pidnginx启动时的pidevents相关配置:参数说明worker_connections每个进程的最大连接数use工作进程数常用中间件配置:http { ...... server { listen 80; # 端口号 server_name localhost; # 域名 # 路径访问控制(默认访问路径,eg:/ ==> 根目录) location / { root /usr/share/nginx/html; # 网站根目录 index index.html index.htm index.py; # 首页配置 } error_page 500 502 503 504 /50x.html; # 错误页面(可以自定义添404页面,error_page 404 /404.html;...) # 访问xxx/50x.html的时候去指定目录找 location = /50x.html { root /usr/share/nginx/html; # 错误页面所在路径 } } # 一个server配置一个虚拟 or 独立的站点(通过listen和server_name来区别多个server) server { ...... }}2.4.systemctl配置nginx:(等会编译安装的时候可以参考)[root@localhost dnt]# cat /usr/lib/systemd/system/nginx.service[Unit]Description=nginx - high performance web serverDocumentation=http://nginx.org/en/docs/After=network-online.target remote-fs.target nss-lookup.targetWants=network-online.target[Service]Type=forkingPIDFile=/var/run/nginx.pidExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.confExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s TERM $MAINPID[Install]WantedBy=multi-user.targetnginx-debug:[root@localhost dnt]# cat /usr/lib/systemd/system/nginx-debug.service[Unit]Description=nginx - high performance web serverDocumentation=http://nginx.org/en/docs/After=network-online.target remote-fs.target nss-lookup.targetWants=network-online.target[Service]Type=forkingPIDFile=/var/run/nginx.pidExecStart=/usr/sbin/nginx-debug -c /etc/nginx/nginx.confExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s TERM $MAINPID[Install]WantedBy=multi-user.target3.编译安装3.1.安装编译环境一步到位:yum install gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel -y简单拆分解析一下:Nginx使用C/C++编写的,安装一下依赖:yum install gcc-c++ -yNginx需要使用PCRE来进行正则解析:yum install pcre pcre-devel -y现在服务器和浏览器一般都是使用gzip:yum install -y zlib zlib-devel -y让Nginx支持https:yum install openssl openssl-devel -y3.2.Nginx编译安装3.2.1.下载解压先编译安装一下,后面说lua模块的时候再重新编译下就行了下载:curl -o nginx.tar.gz http://nginx.org/download/nginx-1.16.0.tar.gz解压:tar -zxvf nginx.tar.gz3.2.2.配置编译参数参考前面说的在线版Nginx来设置编译参数的配置:PS:nginx -V切换到nginx的解压目录:cd nginx-1.16.0 然后执行下面命令PS:root权限编译哦~./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt=‘-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC‘ --with-ld-opt=‘-Wl,-z,relro -Wl,-z,now -pie‘3.2.3.进行编译安装接着编译安装:make && make installPS:提速:make -j 4 && make install3.2.4.配置systemctl利用systemctl添加自定义系统服务# vi /usr/lib/systemd/system/nginx.service[Unit]Description=nginx - high performance web serverDocumentation=http://nginx.org/en/docs/After=network-online.target remote-fs.target nss-lookup.targetWants=network-online.target[Service]Type=forkingPIDFile=/var/run/nginx.pidExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.confExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s TERM $MAINPID[Install]WantedBy=multi-user.targetPS:如果不生效可以重载下systemctl:systemctl daemon-reload3.2.5.端口放行放行80端口:firewall-cmd --zone=public --add-port=80/tcp --permanentPS:规则生效:firewall-cmd --reload3.2.6.验证运行的时候如果出现nginx: [emerg] getpwnam("nginx") failed的错误可以参考我写这篇文章:https://www.cnblogs.com/dotnetcrazy/p/11304783.htmlPS:核心:useradd -s /sbin/nologin -M nginx3.3.编译安装Lua模块大体思路默认是不支持Lua的,所以需要自己编译安装下PS:记得安装下Lua库:yum install lua lua-devel -y主要就3步走:安装Lua即时编译器:LuaJIT目前最新:http://luajit.org/download/LuaJIT-2.0.5.tar.gz安装Nginx模块:ngx_devel_kit and lua-nginx-modulengx_devel_kit:https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1.tar.gzlua-nginx-module:https://github.com/openresty/lua-nginx-module/archive/v0.10.15.tar.gz重新编译Nginx:复制在线安装的编译参数(nginx -V)然后添加两个参数--add-module=../ngx_devel_kit-0.3.1--add-module=../lua-nginx-module-0.10.153.3.1.编译安装luajit并导入环境变量解压缩# 编译安装make install PREFIX=/usr/local/LuaJIT# 导入环境变量export LUAJIT_LIB=/usr/local/LuaJIT/libexport LUAJIT_INC=/usr/local/LuaJIT/include/luajit-2.03.3.2.共享lua动态库加载lua库到ld.so.conf文件echo "/usr/local/LuaJIT/lib" >> /etc/ld.so.conf执行ldconfig让动态函式库加载到缓存中3.3.3.配置nginx的编译参数完整参数附录:./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt=‘-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC‘ --with-ld-opt=‘-Wl,-z,relro -Wl,-z,now -pie‘ --add-module=../ngx_devel_kit-0.3.1 --add-module=../lua-nginx-module-0.10.153.3.4.重新编译安装nginx编译安装:make && make install3.3.5.验证Lua模块验证下Lua是否已经可用:在nginx.config的server节点下添加:PS:vi /etc/nginx/nginx.confserver { listen 80; server_name localhost; charset utf-8; # 默认编码为utf-8 location / { root html; index index.html index.htm; } ... # 测试Nginx的Lua(添加这一段) location /hello { default_type ‘text/plain‘; content_by_lua ‘ngx.say("欢迎访问逸鹏说道公众号~")‘; } ...}检查配置:nginx -t -c /etc/nginx/nginx.confPS:配置生效:nginx -s reload -c /etc/nginx/nginx.conf看看效果:扩展:你可以试试获取ip哦~# 获取客户端iplocation /myip { default_type ‘text/plain‘; content_by_lua ‘ clientIP = ngx.req.get_headers()["x_forwarded_for"] ngx.say("IP:",clientIP) ‘; }4.Nginx+Lua搭建WAF防火墙市面上比较常用一块开源项目:ngx_lua_wafhttps://github.com/loveshell/ngx_lua_waf拦截Cookie类型工具拦截异常post请求拦截CC洪水攻击拦截URL拦截arg(提交的参数)4.1.环境clone代码并移动到nginx的waf目录下简单说下里面的规则分别有啥用:args里面的规则get参数进行过滤的url是只在get请求url过滤的规则post是只在post请求过滤的规则whitelist是白名单,里面的url匹配到不做过滤user-agent是对user-agent的过滤规则4.2.配置修改必要配置详细说明我引用一下我的上篇文章:参数简单说明下:红色字体部分需要修改 nginx.config的http下添加如下内容:lua_package_path "/etc/nginx/waf/?.lua";lua_shared_dict limit 10m;init_by_lua_file /etc/nginx/waf/init.lua;access_by_lua_file /etc/nginx/waf/waf.lua;4.3.生效配置语法检查:nginx -t -c /etc/nginx/nginx.confPS:不重启的方式加载配置:Nginx -s reload -c /etc/nginx/nginx.conf4.4.简单验证PS:其实绕过很简单,看看他默认规则即可,这款WAF的强大之处在于轻量级,而且规则可以自定化过滤规则在wafconf下,可根据需求自行调整,每条规则需换行,或者用|分割举个例子:http://192.168.0.10/hello?id=1 or 1=1PS:默认规则没有这点的防护那么我们可以在args规则中添加比如\sor\s+,然后nginx -s reload一下就行了PS:如果是从post进行注入,或者cookie中转注入,那么在对应规则里面添加就行,我这边只是演示下防火墙被绕过该怎么解决~(多看看日志)4.5.CC验证留个课后作业:使用ab来测试下nginx+lua的waf对cc的防御效果提示:可以使用ab -n 2000 -c 200 http://192.168.0.10来简单测试PS:测试前curl http://192.168.0.10/hello 看看返回内容,测试后再curl看看返回内容扩展:隐藏Nginx版本信息防止被黑客进行针对性渗透,隐藏下版本信息PS:其他配置今天就不详细讲解了,下次讲Nginx的时候会说的原来:配置下:vi /etc/nginx/nginx.confhttp下添加:server_tokens off;检查下语法:nginx -t不重启的方式加载配置文件:nginx -s reload现在效果:作者:毒逆天出处:https://www.cnblogs.com/dotnetcrazy本文版权归作者和博客园共有。欢迎转载,但必须保留此段声明,且在文章页面明显位置给出原文连接! 

点赞 0
37 次浏览
0 条评论

叫我阿喵 提出了问题

nginx配置异常,不知道哪里出问题

站点是前后端分离的,nginx已配置ssl证书,上传文件还是报sslHandshakeException错误并且有时候提交options请求不成功,这个就比较奇怪,不定时会报options请求失败,但是刷新一下就好了

149 次浏览
2 个回答

守望 发表了文章

upstream模块

upstream模块 (100%)nginx模块一般被分成三大类:handler、filter和upstream。前面的章节中,读者已经了解了handler、filter。利用这两类模块,可以使nginx轻松完成任何单机工作。而本章介绍的upstream,将使nginx将跨越单机的限制,完成网络数据的接收、处理和转发。数据转发功能,为nginx提供了跨越单机的横向处理能力,使nginx摆脱只能为终端节点提供单一功能的限制,而使它具备了网路应用级别的拆分、封装和整合的战略功能。在云模型大行其道的今天,数据转发使nginx有能力构建一个网络应用的关键组件。当然,一个网络应用的关键组件往往一开始都会考虑通过高级开发语言编写,因为开发比较方便,但系统到达一定规模,需要更重视性能的时候,这些高级语言为了达成目标所做的结构化修改所付出的代价会使nginx的upstream模块就呈现出极大的吸引力,因为他天生就快。作为附带,nginx的配置提供的层次化和松耦合使得系统的扩展性也可能达到比较高的程度。言归正传,下面介绍upstream的写法。upstream模块接口从本质上说,upstream属于handler,只是他不产生自己的内容,而是通过请求后端服务器得到内容,所以才称为upstream(上游)。请求并取得响应内容的整个过程已经被封装到nginx内部,所以upstream模块只需要开发若干回调函数,完成构造请求和解析响应等具体的工作。这些回调函数如下表所示:create_request生成发送到后端服务器的请求缓冲(缓冲链)。reinit_request在某台后端服务器出错的情况,nginx会尝试另一台后端服务器。 nginx选定新的服务器以后,会先调用此函数,然后再次调用 create_request,以重新初始化upstream模块的工作状态。process_header处理后端服务器返回的信息头部。所谓头部是与upstream server 通信的协议规定的,比如HTTP协议的header部分,或者memcached 协议的响应状态部分。abort_request在客户端放弃请求时被调用。不需要在函数中实现关闭后端服务 器连接的功能,系统会自动完成关闭连接的步骤,所以一般此函 数不会进行任何具体工作。finalize_request正常完成与后端服务器的请求后调用该函数,与abort_request 相同,一般也不会进行任何具体工作。input_filter处理后端服务器返回的响应正文。nginx默认的input_filter会 将收到的内容封装成为缓冲区链ngx_chain。该链由upstream的 out_bufs指针域定位,所以开发人员可以在模块以外通过该指针 得到后端服务器返回的正文数据。memcached模块实现了自己的 input_filter,在后面会具体分析这个模块。input_filter_init初始化input filter的上下文。nginx默认的input_filter_init 直接返回。memcached模块分析memcache是一款高性能的分布式cache系统,得到了非常广泛的应用。memcache定义了一套私有通信协议,使得不能通过HTTP请求来访问memcache。但协议本身简单高效,而且memcache使用广泛,所以大部分现代开发语言和平台都提供了memcache支持,方便开发者使用memcache。nginx提供了ngx_http_memcached模块,提供从memcache读取数据的功能,而不提供向memcache写数据的功能。作为web服务器,这种设计是可以接受的。下面,我们开始分析ngx_http_memcached模块,一窥upstream的奥秘。Handler模块?初看memcached模块,大家可能觉得并无特别之处。如果稍微细看,甚至觉得有点像handler模块,当大家看到这段代码以后,必定疑惑为什么会跟handler模块一模一样。clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);clcf->handler = ngx_http_memcached_handler;因为upstream模块使用的就是handler模块的接入方式。同时,upstream模块的指令系统的设计也是遵循handler模块的基本规则:配置该模块才会执行该模块。{ ngx_string("memcached_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_memcached_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }所以大家觉得眼熟是好事,说明大家对Handler的写法已经很熟悉了。Upstream模块!那么,upstream模块的特别之处究竟在哪里呢?答案是就在模块处理函数的实现中。upstream模块的处理函数进行的操作都包含一个固定的流程。在memcached的例子中,可以观察ngx_http_memcached_handler的代码,可以发现,这个固定的操作流程是:1. 创建upstream数据结构。if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR;}2. 设置模块的tag和schema。schema现在只会用于日志,tag会用于buf_chain管理。u = r->upstream;ngx_str_set(&u->schema, "memcached://");u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;3. 设置upstream的后端服务器列表数据结构。mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memcached_module);u->conf = &mlcf->upstream;4. 设置upstream回调函数。在这里列出的代码稍稍调整了代码顺序。u->create_request = ngx_http_memcached_create_request;u->reinit_request = ngx_http_memcached_reinit_request;u->process_header = ngx_http_memcached_process_header;u->abort_request = ngx_http_memcached_abort_request;u->finalize_request = ngx_http_memcached_finalize_request;u->input_filter_init = ngx_http_memcached_filter_init;u->input_filter = ngx_http_memcached_filter;5. 创建并设置upstream环境数据结构。ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));if (ctx == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR;}ctx->rest = NGX_HTTP_MEMCACHED_END;ctx->request = r;ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);u->input_filter_ctx = ctx;6. 完成upstream初始化并进行收尾工作。r->main->count++;ngx_http_upstream_init(r);return NGX_DONE;任何upstream模块,简单如memcached,复杂如proxy、fastcgi都是如此。不同的upstream模块在这6步中的最大差别会出现在第2、3、4、5上。其中第2、4两步很容易理解,不同的模块设置的标志和使用的回调函数肯定不同。第5步也不难理解,只有第3步是最为晦涩的,不同的模块在取得后端服务器列表时,策略的差异非常大,有如memcached这样简单明了的,也有如proxy那样逻辑复杂的。这个问题先记下来,等把memcached剖析清楚了,再单独讨论。第6步是一个常态。将count加1,然后返回NGX_DONE。nginx遇到这种情况,虽然会认为当前请求的处理已经结束,但是不会释放请求使用的内存资源,也不会关闭与客户端的连接。之所以需要这样,是因为nginx建立了upstream请求和客户端请求之间一对一的关系,在后续使用ngx_event_pipe将upstream响应发送回客户端时,还要使用到这些保存着客户端信息的数据结构。这部分会在后面的原理篇做具体介绍,这里不再展开。将upstream请求和客户端请求进行一对一绑定,这个设计有优势也有缺陷。优势就是简化模块开发,可以将精力集中在模块逻辑上,而缺陷同样明显,一对一的设计很多时候都不能满足复杂逻辑的需要。对于这一点,将会在后面的原理篇来阐述。回调函数前面剖析了memcached模块的骨架,现在开始逐个解决每个回调函数。1. ngx_http_memcached_create_request:很简单的按照设置的内容生成一个key,接着生成一个“get $key”的请求,放在r->upstream->request_bufs里面。2. ngx_http_memcached_reinit_request:无需初始化。3. ngx_http_memcached_abort_request:无需额外操作。4. ngx_http_memcached_finalize_request:无需额外操作。5. ngx_http_memcached_process_header:模块的业务重点函数。memcache协议将头部信息被定义为第一行文本,可以找到这段代码证明:for (p = u->buffer.pos; p < u->buffer.last; p++) { if ( * p == LF) { goto found;}如果在已读入缓冲的数据中没有发现LF(‘n’)字符,函数返回NGX_AGAIN,表示头部未完全读入,需要继续读取数据。nginx在收到新的数据以后会再次调用该函数。nginx处理后端服务器的响应头时只会使用一块缓存,所有数据都在这块缓存中,所以解析头部信息时不需要考虑头部信息跨越多块缓存的情况。而如果头部过大,不能保存在这块缓存中,nginx会返回错误信息给客户端,并记录error log,提示缓存不够大。process_header的重要职责是将后端服务器返回的状态翻译成返回给客户端的状态。例如,在ngx_http_memcached_process_header中,有这样几段代码:r->headers_out.content_length_n = ngx_atoof(len, p - len - 1);u->headers_in.status_n = 200;u->state->status = 200;u->headers_in.status_n = 404;u->state->status = 404;u->state用于计算upstream相关的变量。比如u->status->status将被用于计算变量“upstream_status”的值。u->headers_in将被作为返回给客户端的响应返回状态码。而第一行则是设置返回给客户端的响应的长度。在这个函数中不能忘记的一件事情是处理完头部信息以后需要将读指针pos后移,否则这段数据也将被复制到返回给客户端的响应的正文中,进而导致正文内容不正确。u->buffer.pos = p + 1;process_header函数完成响应头的正确处理,应该返回NGX_OK。如果返回NGX_AGAIN,表示未读取完整数据,需要从后端服务器继续读取数据。返回NGX_DECLINED无意义,其他任何返回值都被认为是出错状态,nginx将结束upstream请求并返回错误信息。6. ngx_http_memcached_filter_init:修正从后端服务器收到的内容长度。因为在处理header时没有加上这部分长度。7. ngx_http_memcached_filter:memcached模块是少有的带有处理正文的回调函数的模块。因为memcached模块需要过滤正文末尾CRLF “END” CRLF,所以实现了自己的filter回调函数。处理正文的实际意义是将从后端服务器收到的正文有效内容封装成ngx_chain_t,并加在u->out_bufs末尾。nginx并不进行数据拷贝,而是建立ngx_buf_t数据结构指向这些数据内存区,然后由ngx_chain_t组织这些buf。这种实现避免了内存大量搬迁,也是nginx高效的奥秘之一。本节回顾这一节介绍了upstream模块的基本组成。upstream模块是从handler模块发展而来,指令系统和模块生效方式与handler模块无异。不同之处在于,upstream模块在handler函数中设置众多回调函数。实际工作都是由这些回调函数完成的。每个回调函数都是在upstream的某个固定阶段执行,各司其职,大部分回调函数一般不会真正用到。upstream最重要的回调函数是create_request、process_header和input_filter,他们共同实现了与后端服务器的协议的解析部分。负载均衡模块 (100%)负载均衡模块用于从”upstream”指令定义的后端主机列表中选取一台主机。nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。为了方便介绍负载均衡模块,做到言之有物,以下选取nginx内置的ip hash模块作为实际例子进行分析。配置要了解负载均衡模块的开发方法,首先需要了解负载均衡模块的使用方法。因为负载均衡模块与之前书中提到的模块差别比较大,所以我们从配置入手比较容易理解。在配置文件中,我们如果需要使用ip hash的负载均衡算法。我们需要写一个类似下面的配置:upstream test { ip_hash; server 192.168.0.1; server 192.168.0.2;}从配置我们可以看出负载均衡模块的使用场景: 1. 核心指令”ip_hash”只能在upstream {}中使用。这条指令用于通知nginx使用ip hash负载均衡算法。如果没加这条指令,nginx会使用默认的round robin负载均衡模块。请各位读者对比handler模块的配置,是不是有共同点? 2. upstream {}中的指令可能出现在”server”指令前,可能出现在”server”指令后,也可能出现在两条”server”指令之间。各位读者可能会有疑问,有什么差别么?那么请各位读者尝试下面这个配置:upstream test { server 192.168.0.1 weight=5; ip_hash; server 192.168.0.2 weight=7;}神奇的事情出现了:nginx: [emerg] invalid parameter "weight=7" in nginx.conf:103configuration file nginx.conf test failed可见ip_hash指令的确能影响到配置的解析。指令配置决定指令系统,现在就来看ip_hash的指令定义:static ngx_command_t ngx_http_upstream_ip_hash_commands[] = { { ngx_string("ip_hash"), NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS, ngx_http_upstream_ip_hash, 0, 0, NULL }, ngx_null_command};没有特别的东西,除了指令属性是NGX_HTTP_UPS_CONF。这个属性表示该指令的适用范围是upstream{}。钩子以从前面的章节得到的经验,大家应该知道这里就是模块的切入点了。负载均衡模块的钩子代码都是有规律的,这里通过ip_hash模块来分析这个规律。static char *ngx_http_upstream_ip_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ ngx_http_upstream_srv_conf_t *uscf; uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash; uscf->flags = NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN; return NGX_CONF_OK;}这段代码中有两点值得我们注意。一个是uscf->flags的设置,另一个是设置init_upstream回调。设置uscf->flagsNGX_HTTP_UPSTREAM_CREATE:创建标志,如果含有创建标志的话,nginx会检查重复创建,以及必要参数是否填写;NGX_HTTP_UPSTREAM_MAX_FAILS:可以在server中使用max_fails属性;NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:可以在server中使用fail_timeout属性;NGX_HTTP_UPSTREAM_DOWN:可以在server中使用down属性;此外还有下面属性:NGX_HTTP_UPSTREAM_WEIGHT:可以在server中使用weight属性;NGX_HTTP_UPSTREAM_BACKUP:可以在server中使用backup属性。聪明的读者如果联想到刚刚遇到的那个神奇的配置错误,可以得出一个结论:在负载均衡模块的指令处理函数中可以设置并修改upstream{}中”server”指令支持的属性。这是一个很重要的性质,因为不同的负载均衡模块对各种属性的支持情况都是不一样的,那么就需要在解析配置文件的时候检测出是否使用了不支持的负载均衡属性并给出错误提示,这对于提升系统维护性是很有意义的。但是,这种机制也存在缺陷,正如前面的例子所示,没有机制能够追加检查在更新支持属性之前已经配置了不支持属性的”server”指令。设置init_upstream回调nginx初始化upstream时,会在ngx_http_upstream_init_main_conf函数中调用设置的回调函数初始化负载均衡模块。这里不太好理解的是uscf的具体位置。通过下面的示意图,说明upstream负载均衡模块的配置的内存布局。从图上可以看出,MAIN_CONF中ngx_upstream_module模块的配置项中有一个指针数组upstreams,数组中的每个元素对应就是配置文件中每一个upstream{}的信息。更具体的将会在后面的原理篇讨论。初始化配置init_upstream回调函数执行时需要初始化负载均衡模块的配置,还要设置一个新钩子,这个钩子函数会在nginx处理每个请求时作为初始化函数调用,关于这个新钩子函数的功能,后面会有详细的描述。这里,我们先分析IP hash模块初始化配置的代码:ngx_http_upstream_init_round_robin(cf, us);us->peer.init = ngx_http_upstream_init_ip_hash_peer;这段代码非常简单:IP hash模块首先调用另一个负载均衡模块Round Robin的初始化函数,然后再设置自己的处理请求阶段初始化钩子。实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。这里,IP hash模块指定Round Robin模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对Round Robin模块进行初始化。初始化请求nginx收到一个请求以后,如果发现需要访问upstream,就会执行对应的peer.init函数。这是在初始化配置时设置的回调函数。这个函数最重要的作用是构造一张表,当前请求可以使用的upstream服务器被依次添加到这张表中。之所以需要这张表,最重要的原因是如果upstream服务器出现异常,不能提供服务时,可以从这张表中取得其他服务器进行重试操作。此外,这张表也可以用于负载均衡的计算。之所以构造这张表的行为放在这里而不是在前面初始化配置的阶段,是因为upstream需要为每一个请求提供独立隔离的环境。为了讨论peer.init的核心,我们还是看IP hash模块的实现:r->upstream->peer.data = &iphp->rrp;ngx_http_upstream_init_round_robin_peer(r, us);r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;第一行是设置数据指针,这个指针就是指向前面提到的那张表;第二行是调用Round Robin模块的回调函数对该模块进行请求初始化。面前已经提到,一个负载均衡模块可以调用其他负载均衡模块以提供功能的补充。第三行是设置一个新的回调函数get。该函数负责从表中取出某个服务器。除了get回调函数,还有另一个r->upstream->peer.free的回调函数。该函数在upstream请求完成后调用,负责做一些善后工作。比如我们需要维护一个upstream服务器访问计数器,那么可以在get函数中对其加1,在free中对其减1。如果是SSL的话,nginx还提供两个回调函数peer.set_session和peer.save_session。一般来说,有两个切入点实现负载均衡算法,其一是在这里,其二是在get回调函数中。peer.get和peer.free回调函数这两个函数是负载均衡模块最底层的函数,负责实际获取一个连接和回收一个连接的预备操作。之所以说是预备操作,是因为在这两个函数中,并不实际进行建立连接或者释放连接的动作,而只是执行获取连接的地址或维护连接状态的操作。需要理解的清楚一点,在peer.get函数中获取连接的地址信息,并不代表这时连接一定没有被建立,相反的,通过get函数的返回值,nginx可以了解是否存在可用连接,连接是否已经建立。这些返回值总结如下:返回值说明nginx后续动作NGX_DONE得到了连接地址信息,并且连接已经建立。直接使用连接,发送数据。NGX_OK得到了连接地址信息,但连接并未建立。建立连接,如连接不能立即建立,设置事件, 暂停执行本请求,执行别的请求。NGX_BUSY所有连接均不可用。返回502错误至客户端。各位读者看到上面这张表,可能会有几个问题浮现出来:Q:什么时候连接是已经建立的?A:使用后端keepalive连接的时候,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,这些连接都是已经准备好的。Q:什么叫所有连接均不可用?A:初始化请求的过程中,建立了一张表,get函数负责每次从这张表中不重复的取出一个连接,当无法从表中取得一个新的连接时,即所有连接均不可用。Q:对于一个请求,peer.get函数可能被调用多次么?A:正式如此。当某次peer.get函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,nginx会执行ngx_http_upstream_next,然后可能再次调用peer.get函数尝试别的连接。upstream整体流程如下:本节回顾这一节介绍了负载均衡模块的基本组成。负载均衡模块的配置区集中在upstream{}块中。负载均衡模块的回调函数体系是以init_upstream为起点,经历init_peer,最终到达peer.get和peer.free。其中init_peer负责建立每个请求使用的server列表,peer.get负责从server列表中选择某个server(一般是不重复选择),而peer.free负责server释放前的资源释放工作。最后,这一节通过一张图将upstream模块和负载均衡模块在请求处理过程中的相互关系展现出来。

点赞 0
23 次浏览
0 条评论

犹达斯 提出了问题

nginx ngx_http_mirror_module 流量复制阻塞问题

 使用 ngx_http_mirror_module 进行流量复制的时候遇到一个问题,请教各位大佬问题背景准备在不影响生产环境的情况下将生产入口流量复制一份到测试环境,做测试验证工作;问题描述当测试环境的服务器响应缓慢或者停掉的时候,会阻塞生产的请求响应,这个是绝对不能接受的。nginx 配置如下图所示,安装nginx后只添加了 mirror 复制配置,其他均未作调整192.168.132.8 部署nginx,master(模拟生产服务器)服务节点192.168.132.1 部署mirror(模拟测试服务器)服务节点 listen 80;  server_name 192.168.132.8;   #charset koi8-r;   #access_log logs/host.access.log main;   location / {  mirror /mirror;  proxy_pass http://192.168.132.8:8080;  }     location = /mirror {  internal;  proxy_pass http://192.168.132.1:8090/$request_uri;  proxy_set_header X-Original-URI $request_uri;  }测试结果 场景TPS  直连 master 4532 通过 nginx 访问 master 2567 通过 nginx 访问 master 并复制流量到 mirror  675 通过 nginx 访问 master 并复制流量到 mirror (mirror throw exception) 883 通过 nginx 访问 master 并复制流量到 mirror (mirror sleep 10s 后返回) 9.7 通过 nginx 访问 master 并复制流量到 mirror (mirror 停机) 阻塞 期望效果复制流量到测试环境完全不影响生产环境的使用,(无论测试环境是否停机,抛出异常,或是阻塞,都不影响生产环境的使用)添加nginx代理进行流量复制后,性能下降控制在可接收范围内(TPS 从 2567 直接降到 675 确实降低太多)

81 次浏览
1 个回答

lbuu_58 发表了文章

HTTP HSTS协议和 nginx

Netcraft 公司最近公布了他们检测SSL/TLS网站的研究,并指出只有仅仅5%的用户正确执行了HTTP严格传输安全HSTS。本文介绍nginx如何配置HSTS。什么是HSTSHTTPS(SSL和TLS)确保用户和网站通讯过程中安全,使攻击者难于拦截、修改和假冒。当用户手动输入域名或http://链接,该网站的第一个请求是未加密的,使用普通的http。最安全的网站立即发送回一个重定向使用户引向到https连接,然而,中间人攻击者可能会攻击拦截初始的http请求,从而控制用户后续的回话。自然而然HSTS应运而生为了解决这一潜在的安全问题。即时用户输入域名或http连接,浏览器将严格的升级到https连接。HSTS如何工作的HSTS策略是从安全的HTTPS站点发送的HTTP响应头部发布的。1Strict-Transport-Security:max-age=31536000当浏览器从HTTPS站点看到这个头部,就知道该域名只能通过HTTPS(SSL 或者 TLS)访问了。并将此信息缓存到31536000,也就是1年。可选的参数includeSubDomains告诉浏览器该策略适用于当前域下的所有子域。1Strict-Transport-Security:max-age=31536000;includeSubDomainsnginx配置HSTS在nginx配置文件上设置HSTS响应头部。1add_header Strict-Transport-Security"max-age=31536000; includeSubDomains"always;always 参数确保所有的响应设置该头部,包括内部产生的错误响应。nginx版本早于1.7.5不支持该always参数和内部产生的错误响应不设置该头部信息。add_header指令继承规则:nginx配置块继承add_header指令所在的封装块,因此只需将add_header指令放在顶级的server块。此外还有个重要的例外,如果一个块包含了add_header指令本身,它不会从封装块继承该头部,你需要重新定义所有的add_header指令。123456789101112131415161718server{listen443ssl;add_header Strict-Transport-Security"max-age=31536000; includeSubDomains"always;# This ‘location‘ block inherits the STS headerlocation/{root/usr/share/nginx/html;}# Because this ‘location‘ block contains another ‘add_header‘ directive,# we must redeclare the STS headerlocation/servlet{add_headerX-Served-By"My Servlet Handler";add_header Strict-Transport-Security"max-age=31536000; includeSubDomains"always;proxy_pass http://localhost:8080;}}测试HTTP严格传输安全:一旦用户提出HSTS策略,它的缓存信息期由max-age指定。在此期间,浏览器将会拒绝通过未加密的HTTP访问web服务,并拒绝给予例外证书错误(如果该网站以前提交了一个有效可信的证书)。如果指定了一个includeSubDomanis参数,这些限制也同样适用于当前域下的所有子域。当你测试HSTS时,max-age时间设置短点。是否每个HTTPS响应需要有一个STS头部:我们的目标是当用户开始HTTPS回话时,尽可能快的呈现HSTS策略。如果他们在回话期间接收到HSTS策略,他们仍然容易受到HTTP劫持攻击的。浏览器只需查看一次STS头部,因此它不是严格必要将它添加到每个位置块和每个响应。然而,只在主页或者登陆页面添加它可能是不够的,如果你只添加到缓存的响应,客户端可能无法看到它。确保尽可能多的合理的覆盖到你的URL,特别注意动态的内容。HTTP和HTTPS并行有时网站需要同时运行在HTTP和HTTPS下12345server{listen80;listen443ssl;...}有时,需要将http请求重定向到https123456789101112131415161718server{listen80default_server;listen[::]:80default_server;server_name_;# Discourage deep links by using a permanent redirect to home page of HTTPS sitereturn301https://$host;# Alternatively, redirect all HTTP links to the matching HTTPS page# return 301 https://$host$request_uri;}server{listen443ssl;server_name www.ttlsa.com;add_header Strict-Transport-Security"max-age=31536000; includeSubDomains"always;}加强HSTS保护客户端从HTTP拦截,从它看到STS头部到声明的max-age的期间内。然而,HSTS并不是HTTP回话劫持的完美解决方案。用户仍然容易受到攻击,如果他们通过HTTP访问HSTS保护的网站时:以前从未访问过该网站最近重新安装了其操作系统最近重新安装了其浏览器切换到新的浏览器切换到一个新的设备如移动电话删除浏览器的缓存最近没访问过该站并且max-age过期了为了解决这个问题,google坚持维护了一个“HSTS preload list”的站点域名和子域名,并通过https://hstspreload.appspot.com/提交其域名。该域名列表被分发和硬编码到主流的web浏览器。客户端访问此列表中的域名将主动的使用HTTPS,并拒绝使用HTTP访问该站点。一旦设置了STS头部或者提交了你的域名到HSTS预加载列表,这是不可能将其删除的。这是一个单向的决定使你的域名通过HTTPS可用的。 

点赞 0
31 次浏览
0 条评论

守望 发表了文章

Nginx如何自定义变量

之前的两篇文章 Nginx 变量介绍以及利用 Nginx 变量做防盗链 讲的是 Nginx 有哪些变量以及一个常见的应用。那么如此灵活的 Nginx 怎么能不支持自定义变量呢,今天的文章就来说一下自定义变量的几个模块以及 Nginx 的 keepalive 特性。通过映射新变量提供更多的可能性:map 模块功能:基于已有变量,使用类似 switch {case: … default: …} 的语法创建新变量,为其他基于变量值实现功能的模块提供更多的可能性模块:ngx_http_map_module 默认编译进 Nginx,通过 --without-http_map_module 禁用指令Syntax: map string $variable { ... }Default: —Context: httpSyntax: map_hash_bucket_size size;Default: map_hash_bucket_size 32|64|128; Context: httpSyntax: map_hash_max_size size;Default: map_hash_max_size 2048; Context: http我们主要看一下 map string $variable { ... } 这个指令。所谓类似 switch case 的语法是指,string 的值可以有多个,可以根据 string 值的不同,来给 $variable 赋不同的值。规则已有变量:string 需要是已有的变量,可以分为下面这三种情况字符串一个或者多个变量变量与字符串的组合case 规则:{...} 内的匹配规则需要遵循以下规则,尤其是要注意当使用 hostnames 指令时,与 server name 的匹配规则是一致的,可以看之前的文章 Nginx 的配置指令字符串严格匹配使用 hostnames 指令,可以对域名使用前缀 * 泛域名匹配~ 和 ~* 正则表达式匹配,后者忽略大小写default 规则没有匹配到任何规则时,使用 default确实 default 时,返回空字符串给新变量其他使用 include 语法提升可读性使用 volatile 禁止变量值缓存大家看到上面这些规则可能都有些晕,废话不多说,直接来看一个实战配置文件就懂了。实战这里我们有一个配置文件,在这个文件里面我们定义了两个 map 块,分别配置了两个变量,$name 和 $mobile,$name 中包含 hostnames 指令。map $http_host $name { hostnames; default 0; ~map\.ziyang\w+\.org.cn 1; *.ziyang.org.cn 2; map.ziyang.com 3; map.ziyang.* 4;}map $http_user_agent $mobile { default 0; "~Opera Mini" 1;}server {listen 10001;default_type text/plain;location /{return 200 ‘$name:$mobile\n‘;}}下面看一下实际的请求:➜ test_nginx curl -H "Host: map.ziyang.org.cn" 127.0.0.1:100012:0为什么会返回 2:0 呢?我们来看一下匹配顺序。map.ziyang.org.cn 有三个规则可以生效,分别是:~map.ziyang\w+.org.cn 1;*.ziyang.org.cn 2;map.ziyang.* 4;而泛域名是优先于正则表达式的,* 在前的泛域名优先于在后面的泛域名,因此最终匹配到的就是:*.ziyang.org.cn 2;而第二个变量 $mobile 自然走的是 default 规则,不用多说。这就是 map 模块的作用,大家可以多尝试一下。下面再来看一个与 map 模块有点类似的 split_clients 模块,这个模块也是通过生成新的变量来完成 AB 测试功能的,它可以按照变量的值,按照百分比的方式,生成新的变量。实现 AB 测试:split_clients 模块功能:基于已有变量创建新变量,为其他 AB 测试提供更多的可能性对已有变量的值执行 MurmurHash2 算法,得到 32 位整形哈希数字,记为 hash32 位无符号整形的最大数字 2^32-1,记为 max哈希数字与最大数字相除,hash/max,可以得到百分比 percent配置指令中指示了各个百分比构成的范围,如 0-1%,1%-5% 等,及范围对应的值当 percent 落在哪个范围里,新变量的值就对应着其后的参数模块:ngx_http_split_clients_module,默认编译进 Nginx,通过 --without-http_split_clients_module 禁用规则已有变量字符串一个或者多个变量变量与字符串的组合case 规则:xx.xx%,支持小数点后 2 位,所有项的百分比相加不能超过 100%*,由它匹配剩余的百分比(100% 减去以上所有项相加的百分比)指令Syntax: split_clients string $variable { ... }Default: —Context: httpsplit_clients 的指令与 map 是非常相似的,可以看一下前面的介绍,这里不再赘述了。下面这个配置,来看下有没有啥问题:split_clients "${http_testcli}" $variant { 0.51% .one; 20.0% .two; 50.5% .three; 40% .four; * "";}细心的同学可能已经发现了,所有的百分比相加已经超过了 100%,所以 Nginx 直接会抛出一个错误,禁止执行。➜ test_nginx ./sbin/nginx -s reloadnginx: [emerg] percent total is greater than 100% in /Users/mtdp/myproject/nginx/test_nginx/conf/example/17.map.conf:31然后将 40% .four; 这一行给屏蔽掉再试试看:➜ test_nginx curl -H "testcli: split_clients.ziyang.com" --resolve "split_clients.ziyang.com:80:127.0.0.1" http://split_clients.ziyang.comABtestfile.three正常执行。geo 模块geo 模块与前面两个模块也很相似,不同之处在于,这个模块是基于 IP 地址或者子网掩码这样的变量值来生成新的变量的。功能:根据 IP 地址创建新变量模块:ngx_http_geo_module,默认编译进 Nginx,通过 --without-http_geo_module 禁用指令Syntax: geo [$address] $variable { ... }Default: —Context: http规则如果 geo 指令后不输入 $address,那么默认使用 $remote_addr 变量作为 IP 地址{} 内的指令匹配:优先最长匹配通过 IP 地址及子网掩码的方式,定义 IP 范围,当 IP 地址在范围内时新变量使用其后的参数值default 指定了当以上范围都未匹配上时,新变量的默认值通过 proxy 指令指定可信地址(参考 realip 模块),此时 remote_addr 的值为 X-Forwarded-For 头部值中最后一个 IP 地址proxy_recursive 允许循环地址搜索include,优化可读性delete 删除指定网络geo $country { default ZZ; #include conf/geo.conf; #proxy 172.18.144.211; 127.0.0.0/24 US; 127.0.0.1/32 RU; 10.1.0.0/16 RU; 192.168.1.0/24 UK; }问题:以下命令执行时,变量 country 的值各为多少?(proxy 实际上为客户端地址,这里设置为本机的局域网地址即可,我这里是 172.18.144.211)curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.2‘ geo.ziyang.com curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1‘ geo.ziyang.com curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4‘ geo.ziyang.com结果如下:```shell➜ test_nginx curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.2‘ geo.ziyang.comUS➜ test_nginx curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1‘ geo.ziyang.comRU➜ test_nginx curl -H ‘X-Forwarded-For: 10.1.0.0,127.0.0.1,1.2.3.4‘ geo.ziyang.comZZ这里可以看出来,匹配规则实际上是遵循最长匹配的规则的。geoip 模块geoip 模块可以根据 IP 地址生成对应的地址变量,用法与前面的也都类似,Nginx 是基于 MaxMind 数据库来生成对应的地址的。功能:根据 IP 地址创建新变量模块:ngx_http_geoip_module,默认未编译进 Nginx,通过 --with-http_geoip_module 禁用使用这个模块是需要安装 MaxMind 库的,安装步骤如下:安装 MaxMind 里 geoip 的 C 开发库(https://dev.maxmind.com/geoip/legacy/downloadable/ )编译 Nginx 时带上 --with-http_geoip_module 参数下载 MaxMind 中的二进制地址库,这个地址库是需要在指令中指定对应的地址的使用 geoip_country 或者 geoip_city 指令配置好 nginx.conf运行或者升级 Nginxgeoip_country 指令提供的变量指令Syntax: geoip_country file; # 指定国家类的地址文件Default: —Context: httpSyntax: geoip_proxy address | CIDR;Default: —Context: http变量$geoip_country_code:两个字母的国家代码,比如 CN 或者 US$geoip_country_code3:三个字母的国家代码,比如 CHN 或者 USA$geoip_country_name:国家名称,例如 “China”, “United States”geoip_city 指令提供的变量指令Syntax: geoip_city file;Default: —Context: http变量$geoip_latitude:纬度$geoip_longitude:经度$geoip_city_continent_code:位于全球哪个洲,例如 EU 或 AS与 $geoip_country 指令生成的变量重叠$geoip_country_code:两个字母的国家代码,比如 CN 或者 US$geoip_country_code3:三个字母的国家代码,比如 CHN 或者 USA$geoip_country_name:国家名称,例如 “China”, “United States”$geoip_region:洲或者省的编码,例如 02$geoip_region_name:洲或者省的名称,例如 Zhejiang 或者 Saint Petersburg$geoip_city:城市名$geoip_postal_code:邮编号$geoip_area_code:仅美国使用的邮编号,例如 408$geoip_dma_code:仅美国使用的 DMA 编号,例如 807keepalive 模块前面说的都是 Nginx 的变量相关的内容,其实 Nginx 还有一个很具有特色的模块,那就是 keepalive 模块,由于内容不是很多,所以我就直接写到这篇文章里面了,单写一篇显得内容不够哈。这里指的是 HTTP 的 keepalive,TCP 也有 keepalive,后面会说。而且是对客户端的 keepalive,不是对上游服务器的。功能:多个 HTTP 请求通过复用 TCP 连接,可以实现以下功能:减少握手次数通过减少并发连接数减少了服务器资源消耗降低 TCP 拥塞控制的影响,保证滑动窗口维持在一个最优的大小Connection 头部close:表示请求处理完就关闭连接keepalive:表示复用连接处理下一条请求Keepalive 头部:timeout=n,单位是秒,表示连接至少保持 n 秒指令对客户端行为控制的指令:Syntax: keepalive_disable none | browser ...;Default: keepalive_disable msie6; Context: http, server, locationSyntax: keepalive_requests number;Default: keepalive_requests 100; Context: http, server, locationSyntax: keepalive_timeout timeout [header_timeout];Default: keepalive_timeout 75s; Context: http, server, locationkeepalive_disable 设置为 none 表示对所有浏览器启用 keepalive,msie6 表示在老版本 MSIE 上禁用 keepalivekeepalive_requests 设置允许保持 keepalive 的请求的数量keepalive_timeout 表示超时时间好了,关于 Nginx 的模块介绍就已经全部介绍完了,有兴趣的同学可以去翻我前面的系列文章。当然还有一部分重要的内容还没有介绍,那就是关于 Nginx 的反向代理和负载均衡部分,这块咱们单独抽出来说,别着急,马上干货就出来。本文首发于我的个人博客:iziyang.github.io,所有配置文件我已经放在了 Nginx 配置文件,大家可以自取。

点赞 0
40 次浏览
0 条评论

守望 发表了文章

Nginx 处理一个 HTTP 请求的全过程

转载:https://www.cnblogs.com/iziyang/p/12933565.html前面给大家讲了 Nginx 是如何处理 HTTP请求头部的,接下来就到了真正处理 HTTP 请求的阶段了。先看下面这张图,这张图是 Nginx 处理 HTTP 请求的示意图,虽然简单,但是却很好的说明了整个过程。Read Request Headers:解析请求头。Identify Configuration Block:识别由哪一个 location 进行处理,匹配 URL。Apply Rate Limits:判断是否限速。例如可能这个请求并发的连接数太多超过了限制,或者 QPS 太高。Perform Authentication:连接控制,验证请求。例如可能根据 Referrer 头部做一些防盗链的设置,或者验证用户的权限。Generate Content:生成返回给用户的响应。为了生成这个响应,做反向代理的时候可能会和上游服务(Upstream Services)进行通信,然后这个过程中还可能会有些子请求或者重定向,那么还会走一下这个过程(Internal redirects and subrequests)。Response Filters:过滤返回给用户的响应。比如压缩响应,或者对图片进行处理。Log:记录日志。以上这七个步骤从整体上介绍了一下处理流程,下面还会再说一下实际的处理过程。Nginx 处理 HTTP 请求的 11 个阶段下面介绍一下详细的 11 个阶段,每个阶段都可能对应着一个甚至多个 HTTP 模块,通过这样一个模块对比,我们也能够很好的理解这些模块具体是怎么样发挥作用的。POST_READ:在 read 完请求的头部之后,在没有对头部做任何处理之前,想要获取到一些原始的值,就应该在这个阶段进行处理。这里面会涉及到一个 realip 模块。SERVER_REWRITE:和下面的 REWRITE 阶段一样,都只有一个模块叫 rewrite 模块,一般没有第三方模块会处理这个阶段。FIND_CONFIG:做 location 的匹配,暂时没有模块会用到。REWRITE:对 URL 做一些处理。POST_WRITE:处于 REWRITE 之后,也是暂时没有模块会在这个阶段出现。接下来是确认用户访问权限的三个模块:PREACCESS:是在 ACCESS 之前要做一些工作,例如并发连接和 QPS 需要进行限制,涉及到两个模块:limt_conn 和 limit_reqACCESS:核心要解决的是用户能不能访问的问题,例如 auth_basic 是用户名和密码,access 是用户访问 IP,auth_request 根据第三方服务返回是否可以去访问。POST_ACCESS:是在 ACCESS 之后会做一些事情,同样暂时没有模块会用到。最后的三个阶段处理响应和日志:PRECONTENT:在处理 CONTENT 之前会做一些事情,例如会把子请求发送给第三方的服务去处理,try_files 模块也是在这个阶段中。CONTENT:这个阶段涉及到的模块就非常多了,例如 index, autoindex, concat 等都是在这个阶段生效的。LOG:记录日志 access_log 模块。以上的这些阶段都是严格按照顺序进行处理的,当然,每个阶段中各个 HTTP 模块的处理顺序也很重要,如果某个模块不把请求向下传递,后面的模块是接收不到请求的。而且每个阶段中的模块也不一定所有都要执行一遍,下面就接着讲一下各个阶段模块之间的请求顺序。11 个阶段的顺序处理如下图所示,每一个模块处理之间是有序的,那么这个顺序怎么才能得到呢?其实非常简单,在源码 ngx_module.c 中,有一个数组 ngx_module_name,其中包含了在编译 Nginx 的时候的 with 指令所包含的所有模块,它们之间的顺序非常关键,在数组中顺序是相反的。char *ngx_module_names[] = { … … "ngx_http_static_module", "ngx_http_autoindex_module", "ngx_http_index_module", "ngx_http_random_index_module", "ngx_http_mirror_module", "ngx_http_try_files_module", "ngx_http_auth_request_module", "ngx_http_auth_basic_module", "ngx_http_access_module", "ngx_http_limit_conn_module", "ngx_http_limit_req_module", "ngx_http_realip_module", "ngx_http_referer_module", "ngx_http_rewrite_module", "ngx_http_concat_module", … …}灰色部分的模块是 Nginx 的框架部分去执行处理的,第三方模块没有机会在这里得到处理。在依次向下执行的过程中,也可能不按照这样的顺序。例如,在 access 阶段中,有一个指令叫 satisfy,它可以指示当有一个满足的时候就直接跳到下一个阶段进行处理,例如当 access 满足了,就直接跳到 try_files 模块进行处理,而不会再执行 auth_basic、auth_request 模块。在 content 阶段中,当 index 模块执行了,就不会再执行 auto_index 模块,而是直接跳到 log 模块。整个 11 个阶段所涉及到的模块和先后顺序如下图所示:下面开始详细讲解一下各个阶段。先来看下第一个阶段 postread 阶段,顾名思义,postread 阶段是在正式处理请求之前起作用的。postread 阶段postread 阶段,是 11 个阶段的第 1 个阶段,这个阶段刚刚获取到了请求的头部,还没有进行任何处理,我们可以拿到一些原始的信息。例如,拿到用户的真实 IP 地址问题:如何拿到用户的真实 IP 地址?我们知道,TCP 连接是由一个四元组构成的,在四元组中,包含了源 IP 地址。而在真实的互联网中,存在非常多的正向代理和反向代理。例如最终的用户有自己的内网 IP 地址,运营商会分配一个公网 IP,然后访问某个网站的时候,这个网站可能使用了 CDN 加速一些静态文件或图片,如果 CDN 没有命中,那么就会回源,回源的时候可能还要经过一个反向代理,例如阿里云的 SLB,然后才会到达 Nginx。我们要拿到的地址应该是运营商给用户分配的公网 IP 地址 115.204.33.1,对这个 IP 来进行并发连接的控制或者限速,而 Nginx 拿到的却是 2.2.2.2,那么怎么才能拿到真实的用户 IP 呢?HTTP 协议中,有两个头部可以用来获取用户 IP:X-Forwardex-For 是用来传递 IP 的,这个头部会把经过的节点 IP 都记录下来X-Real-IP:可以记录用户真实的 IP 地址,只能有一个拿到真实用户 IP 后如何使用?针对这个问题,Nginx 是基于变量来使用。例如 binary_remote_addr、remote_addr 这样的变量,其值就是真实的 IP,这样做连接限制也就是 limit_conn 模块才有意义,这也说明了,limit_conn 模块只能在 preaccess 阶段,而不能在 postread 阶段生效。realip 模块默认不会编译进 Nginx需要通过 --with-http_realip_module 启用功能变量:如果还想要使用原来的 TCP 连接中的地址和端口,需要通过这两个变量保存realip_remote_addrrealip_remote_port功能修改客户端地址指令set_real_ip_from指定可信的地址,只有从该地址建立的连接,获取的 realip 才是可信的real_ip_header指定从哪个头部取真实的 IP 地址,默认从 X-Real-IP 中取,如果设置从 X-Forwarded-For 中取,会先从最后一个 IP 开始取real_ip_recursive环回地址,默认关闭,打开的时候,如果 X-Forwarded-For 最后一个地址与客户端地址相同,会过滤掉该地址Syntax: set_real_ip_from address | CIDR | unix:;Default: —Context: http, server, locationSyntax: real_ip_header field | X-Real-IP | X-Forwarded-For | proxy_protocol;Default: real_ip_header X-Real-IP; Context: http, server, locationSyntax: real_ip_recursive on | off;Default: real_ip_recursive off; Context: http, server, location实战上面关于 real_ip_recursive 指令可能不太容易理解,我们来实战练习一下,先来看 real_ip_recursive 默认关闭的情况:重新编译一个带有 realip 模块的 nginx关于如何编译 Nginx,详见:https://iziyang.github.io/2020/03/10/1-nginx/# 下载 nginx 源码,在源码目录下执行./configure --prefix=自己指定的目录 --with-http_realip_modulemakemake install然后去上一步中自己指定的 Nginx 安装目录#屏蔽默认的 nginx.conf 文件的 server 块内容,并添加一行include /Users/mtdp/myproject/nginx/test_nginx/conf/example/*.conf;# 在 example 目录下建立 realip.conf,set_real_ip_from 可以设置为自己的本机 IPserver { listen 80; server_name ziyang.realip.com; error_log /Users/mtdp/myproject/nginx/nginx/logs/myerror.log debug; set_real_ip_from 192.168.0.108; #real_ip_header X-Real-IP; real_ip_recursive off; # real_ip_recursive on; real_ip_header X-Forwarded-For; location / { return 200 "Client real ip: $remote_addr\n"; }}在上面的配置文件中,我设置了可信代理地址为本机地址,real_ip_recursive 为默认的 off,real_ip_header 设为从 X-Forwarded-For 中取。重载配置文件./sbin/nginx -s reload测试响应结果➜ test_nginx curl -H ‘X-Forwarded-For: 1.1.1.1,192.168.0.108‘ ziyang.realip.comClient real ip: 192.168.0.108然后再来测试 real_ip_recursive 打开的情况:配置文件中打开 real_ip_recursiveserver { listen 80; server_name ziyang.realip.com; error_log /Users/mtdp/myproject/nginx/nginx/logs/myerror.log debug; set_real_ip_from 192.168.0.108; #real_ip_header X-Real-IP; #real_ip_recursive off; real_ip_recursive on; real_ip_header X-Forwarded-For; location / { return 200 "Client real ip: $remote_addr\n"; }}测试响应结果➜ test_nginx curl -H ‘X-Forwarded-For: 1.1.1.1,2.2.2.2,192.168.0.108‘ ziyang.realip.comClient real ip: 2.2.2.2所以这里面也可看出来,如果使用 X-Forwarded-For 获取 realip 的话,需要打开 real_ip_recursive,并且,realip 依赖于 set_real_ip_from 设置的可信地址。那么有人可能就会问了,那直接用 X-Real-IP 来选取真实的 IP 地址不就好了。这是可以的,但是 X-Real-IP 是 Nginx 独有的,不是 RFC 规范,如果客户端与服务器之间还有其他非 Nginx 软件实现的代理,就会造成取不到 X-Real-IP 头部,所以这个要根据实际情况来定。rewrite 阶段的 rewrite 模块下面来看一下 rewrite 模块。首先 rewrite 阶段分为两个,一个是 server_rewrite 阶段,一个是 rewrite,这两个阶段都涉及到一个 rewrite 模块,而在 rewrite 模块中,有一个 return 指令,遇到该指令就不会再向下执行,直接返回响应。return 指令return 指令的语法如下:返回状态码,后面跟上 body返回状态码,后面跟上 URL直接返回 URLSyntax: return code [text]; return code URL; return URL;Default: —Context: server, location, if返回状态码包括以下几种:Nginx 自定义444:立刻关闭连接,用户收不到响应HTTP 1.0 标准301:永久重定向302:临时重定向,禁止被缓存HTTP 1.1 标准303:临时重定向,允许改变方法,禁止被缓存307:临时重定向,不允许改变方法,禁止被缓存308:永久重定向,不允许改变方法return 指令与 error_pageerror_page 的作用大家肯定经常见到。当访问一个网站出现 404 的时候,一般不会直接出现一个 404 NOT FOUND,而是会有一个比较友好的页面,这就是 error_page 的功能。Syntax: error_page code ... [=[response]] uri;Default: —Context: http, server, location, if in location我们来看几个例子:1. error_page 404 /404.html; 2. error_page 500 502 503 504 /50x.html;3. error_page 404 =200 /empty.gif; 4. error_page 404 = /404.php; 5. location / { error_page 404 = @fallback; } location @fallback { proxy_pass http://backend; } 6. error_page 403 http://example.com/forbidden.html; 7. error_page 404 =301 http://example.com/notfound.html;那么现在就会有两个问题,大家看下下面这个配置文件:server { server_name ziyang.return.com; listen 80; root html/; error_page 404 /403.html; #return 405; location / { #return 404 "find nothing!"; }}当 server 下包含 error_page 且 location 下有 return 指令的时候,会执行哪一个呢?return 指令同时出现在 server 块下和同时出现在 location 块下,它们有合并关系吗?这两个问题我们通过实战验证一下。实战将上面的配置添加到配置文件 return.conf在本机的 hosts 文件中绑定 ziyang.return.com 为本地的 IP 地址访问一个不存在的页面➜ test_nginx curl ziyang.return.com/text<html><head><title>403 Forbidden</title></head><body><center><h1>403 Forbidden</center><hr><center>nginx/1.17.8</center></body></html>这个时候可以看到,是 error_page 生效了,返回的响应是 403。那么假如打开了 location 下 return 指令的注释呢?打开 return 指令注释,reload 配置文件重新访问页面➜ test_nginx curl ziyang.return.com/textfind nothing!% 这时候,return 指令得到了执行。也就是第一个问题,当 server 下包含 error_page 且 location 下有 return 指令的时候,会执行 return 指令。下面再看一下 server 下的 return 指令和 location 下的 return 指令会执行哪一个。打开 server 下 return 指令的注释,reload 配置文件重新访问页面➜ test_nginx curl ziyang.return.com/text<html><head><title>405 Not Allowed</title></head><body><center><h1>405 Not Allowed</center><hr><center>nginx/1.17.8</center></body></html>针对上面两个问题也就有了答案:当 server 下包含 error_page 且 location 下有 return 指令的时候,会执行哪一个呢?会执行 location 下的 return 指令。return 指令同时出现在 server 块下和同时出现在 location 块下,它们有合并关系吗?没有合并关系,先遇到哪个 return 指令就先执行哪一个。rewrite 指令rewrite 指令用于修改用户传入 Nginx 的 URL。来看下 rewrite 的指令规则:Syntax: rewrite regex replacement [flag];Default: —Context: server, location, if它的功能主要有下面几点:将 regex 指定的 URL 替换成 replacement 这个新的 URL可以使用正则表达式及变量提取当 replacement 以 http:// 或者 https:// 或者 $schema 开头,则直接返回 302 重定向替换后的 URL 根据 flag 指定的方式进行处理last:用 replacement 这个 URL 进行新的 location 匹配break:break 指令停止当前脚本指令的执行,等价于独立的 break 指令redirect:返回 302 重定向permanent:返回 301 重定向指令示例现在我们有这样的一个目录结构:html/first/└── 1.txthtml/second/└── 2.txthtml/third/└── 3.txt配置文件如下所示:server { listen 80;server_name rewrite.ziyang.com;rewrite_log on;error_log logs/rewrite_error.log notice;root html/;location /first { rewrite /first(.*) /second$1 last; return 200 ‘first!\n‘; }location /second { rewrite /second(.*) /third$1; return 200 ‘second!\n‘; }location /third { return 200 ‘third!\n‘; } location /redirect1 { rewrite /redirect1(.*) $1 permanent; }location /redirect2 { rewrite /redirect2(.*) $1 redirect; }location /redirect3 { rewrite /redirect3(.*) http://rewrite.ziyang.com$1; }location /redirect4 { rewrite /redirect4(.*) http://rewrite.ziyang.com$1 permanent; }} 那么我们的问题是:return 指令 与 rewrite 指令的顺序关系?访问 /first/3.txt,/second/3.txt,/third/3.txt 分别返回的是什么?如果不携带 flag 会怎么样?带着这三个问题,我们来实际演示一下。实战准备工作将上面的配置添加到配置文件 rewrite.conf在本机的 hosts 文件中绑定 rewrite.ziyang.com 为 127.0.0.1last flag首先访问 rewrite.ziyang.com/first/3.txt,结果如下:➜ ~ curl rewrite.ziyang.com/first/3.txtsecond!为什么结果是 second! 呢?应该是 third! 呀,可能有人会有这样的疑问。实际的匹配步骤如下:curl rewrite.ziyang.com/first/3.txt由于 rewrite /first(.*) /second$1 last; 这条指令的存在,last 表示使用新的 URL 进行 location 匹配,因此接下来会去匹配 second/3.txt匹配到 /second 块之后,会依次执行指令,最后返回 200注意,location 块中虽然也改写了 URL,但是并不会去继续匹配,因为后面没有指定 flag。break flag下面将 rewrite /second(.*) /third$1; 这条指令加上 break flag,rewrite /second(.*) /third$1 break;继续访问 rewrite.ziyang.com/first/3.txt,结果如下:➜ ~ curl rewrite.ziyang.com/first/3.txttest3%这时候返回的是 3.txt 文件的内容 test3。实际的匹配步骤如下:curl rewrite.ziyang.com/first/3.txt由于 rewrite /first(.*) /second$1 last; 这条指令的存在,last 表示使用新的 URL 进行 location 匹配,因此接下来会去匹配 second/3.txt匹配到 /second 块之后,由于 break flag 的存在,会继续匹配 rewrite 过后的 URL匹配 /third location因此,这个过程实际请求的 URL 是 rewrite.ziyang.com/third/3.txt,这样自然结果就是 test3 了。你还可以试试访问 rewrite.ziyang.com/third/2.txt 看看会返回什么。redirect 和 permanent flag配置文件中还有 4 个 location,你可以分别试着访问一下,结果是这样的:redirect1:返回 301redirect2:返回 302redirect3:返回 302redirect4:返回 301rewrite 行为记录日志主要是一个指令 rewrite_log:Syntax: rewrite_log on | off;Default: rewrite_log off; Context: http, server, location, if这个指令打开之后,会把 rewrite 的日志写入 logs/rewrite_error.log 日志文件中,这是请求 /first/3.txt 的日志记录:2020/05/06 06:24:05 [notice] 86959#0: *25 "/first(.*)" matches "/first/3.txt", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"2020/05/06 06:24:05 [notice] 86959#0: *25 rewritten data: "/second/3.txt", args: "", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"2020/05/06 06:24:05 [notice] 86959#0: *25 "/second(.*)" matches "/second/3.txt", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"2020/05/06 06:24:05 [notice] 86959#0: *25 rewritten data: "/third/3.txt", args: "", client: 127.0.0.1, server: rewrite.ziyang.com, request: "GET /first/3.txt HTTP/1.1", host: "rewrite.ziyang.com"if 指令if 指令也是在 rewrite 阶段生效的,它的语法如下所示:Syntax: if (condition) { ... }Default: —Context: server, location它的规则是:条件 condition 为真,则执行大括号内的指令;同时还遵循值指令的继承规则(详见我之前的文章 Nginx 的配置指令)那么 if 指令的条件表达式包含哪些内容呢?它的规则如下:检查变量为空或者值是否为 0将变量与字符串做匹配,使用 = 或 !=将变量与正则表达式做匹配大小写敏感,~ 或者 !~大小写不敏感,~* 或者 !~*检查文件是否存在,使用 -f 或者 !-f检查目录是否存在,使用 -d 或者 !-d检查文件、目录、软链接是否存在,使用 -e 或者 !-e检查是否为可执行文件,使用 -x 或者 !-x下面是一些例子:if ($http_user_agent ~ MSIE) { # 与变量 http_user_agent 匹配 rewrite ^(.*)$ /msie/$1 break; } if ($http_cookie ~* "id=([^;]+)(?:;|$)") { # 与变量 http_cookie 匹配 set $id $1; } if ($request_method = POST) { # 与变量 request_method 匹配,获取请求方法 return 405; } if ($slow) { # slow 变量在 map 模块中自定义,也可以进行匹配 limit_rate 10k; } if ($invalid_referer) { return 403; }find_config 阶段当经过 rewrite 模块,匹配到 URL 之后,就会进入 find_config 阶段,开始寻找 URL 对应的 location 配置。location 指令指令语法还是老规矩,咱们先来看一下 location 指令的语法:Syntax: location [ = | ~ | ~* | ^~ ] uri { ... } location @name { ... }Default: —Context: server, locationSyntax: merge_slashes on | off;Default: merge_slashes on; Context: http, server这里面有一个 merge_slashes 指令,这个指令的作用是,加入 URL 中有两个重复的 /,那么会合并为一个,这个指令默认是打开的,只有当对 URL 进行 base64 之类的编码时才需要关闭。匹配规则location 的匹配规则是仅匹配 URI,忽略参数,有下面三种大的情况:前缀字符串常规匹配=:精确匹配^~:匹配上后则不再进行正则表达式匹配正则表达式~:大小写敏感的正则匹配~*:大小写不敏感用户内部跳转的命名 location@对于这些规则刚看上去肯定是很懵的,完全不知道在说什么,下面来实战看几个例子。实战先看一下 Nginx 的配置文件:server { listen 80;server_name location.ziyang.com;error_log logs/error.log debug; #root html/;default_type text/plain;merge_slashes off; location ~ /Test1/$ { return 200 ‘first regular expressions match!\n‘; }location ~* /Test1/(\w+)$ { return 200 ‘longest regular expressions match!\n‘; }location ^~ /Test1/ { return 200 ‘stop regular expressions match!\n‘; } location /Test1/Test2 { return 200 ‘longest prefix string match!\n‘; } location /Test1 { return 200 ‘prefix string match!\n‘; }location = /Test1 { return 200 ‘exact match!\n‘; }}问题就来了,访问下面几个 URL 会分别返回什么内容呢?/Test1/Test1//Test1/Test2/Test1/Test2//test1/Test2例如访问 /Test1 时,会有几个部分都匹配上:常规前缀匹配:location /Test1精确匹配:location = /Test1访问 /Test1/ 时,也会有几个部分匹配上:location ~ /Test1/$location ^~ /Test1/那么究竟会匹配哪一个呢?Nginx 其实是遵循一套规则的,如下图所示:全部的前缀字符串是放置在一棵二叉树中的,Nginx 会分为两部分进行匹配:先遍历所有的前缀字符串,选取最长的一个前缀字符串,如果这个字符串是 = 的精确匹配或 ^~ 的前缀匹配,会直接使用如果第一步中没有匹配上 = 或 ^~,那么会先记住最长匹配的前缀字符串 location按照 nginx.conf 文件中的配置依次匹配正则表达式如果所有的正则表达式都没有匹配上,那么会使用最长匹配的前缀字符串下面看下实际的响应是怎么样的:➜ test_nginx curl location.ziyang.com/Test1exact match!➜ test_nginx curl location.ziyang.com/Test1/stop regular expressions match!➜ test_nginx curl location.ziyang.com/Test1/Test2longest regular expressions match!➜ test_nginx curl location.ziyang.com/Test1/Test2/longest prefix string match!➜ test_nginx curl location.ziyang.com/Test1/Test3stop regular expressions match!/Test1 匹配 location = /Test1/Test1/ 匹配 location ^~ /Test1//Test1/Test2 匹配 location ~* /Test1/(\w+)$/Test1/Test2/ 匹配 location /Test1/Test2/Test1/Test3 匹配 location ^~ /Test1/这里面重点解释一下 /Test1/Test3 的匹配过程:遍历所有可以匹配上的前缀字符串,总共有两个^~ /Test1//Test1选取最长的前缀字符串 /Test1/,由于前面有 ^~ 禁止正则表达式匹配,因此直接使用 location ^~ /Test1/ 的规则返回 stop regular expressions match!preaccess 阶段下面就来到了 preaccess 阶段。我们经常会遇到一个问题,就是如何限制每个客户端的并发连接数?如何限制访问频率?这些就是在 preaccess 阶段处理完成的,顾名思义,preaccess 就是在连接之前。先来看下 limit_conn 模块。limit_conn 模块这里面涉及到的模块是 ngx_http_limit_conn_module,它的基本特性如下:生效阶段:NGX_HTTP_PREACCESS_PHASE 阶段模块:http_limit_conn_module默认编译进 Nginx,通过 --without-http_limit_conn_module 禁用生效范围全部 worker 进程(基于共享内存)进入 preaccess 阶段前不生效限制的有效性取决于 key 的设计:依赖 postread 阶段的 realip 模块取到真实 IP这里面有一点需要注意,就是 limit_conn key 的设计,所谓的 key 指的就是对哪个变量进行限制,通常我们取的都是用户的真实 IP。说完了 limit_conn 的模块,再来说一下指令语法。指令语法定义共享内存(包括大小),以及 key 关键字Syntax: limit_conn_zone key zone=name:size;Default: —Context: http限制并发连接数Syntax: limit_conn zone number;Default: —Context: http, server, location限制发生时的日志级别Syntax: limit_conn_log_level info | notice | warn | error;Default: limit_conn_log_level error; Context: http, server, location限制发生时向客户端返回的错误码Syntax: limit_conn_status code;Default: limit_conn_status 503; Context: http, server, location实战下面又到了实战的环节了,通过一个实际的例子来看一下以上的几个指令是怎么起作用的。老规矩,先上配置文件:limit_conn_zone $binary_remote_addr zone=addr:10m;#limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m;server { listen 80;server_name limit.ziyang.com;root html/;error_log logs/myerror.log info;location /{ limit_conn_status 500; limit_conn_log_level warn; limit_rate 50; limit_conn addr 1; #limit_req zone=one burst=3 nodelay; #limit_req zone=one; }}在本地的 hosts 文件中添加 limit.ziyang.com 为本机 IP在这个配置文件中,做了两条限制,一个是 limit_rate 限制为 50 个字节,并发连接数 limit_conn 限制为 1。➜ test_nginx curl limit.ziyang.com这时候访问 limit.ziyang.com 这个站点,会发现速度非常慢,因为每秒钟只有 50 个字节。如果再同时访问这个站点的话,则会返回 500。我在另一个终端里面同时访问:➜ ~ curl limit.ziyang.com<html><head><title>500 Internal Server Error</title></head><body><center><h1>500 Internal Server Error</center><hr><center>nginx/1.17.8</center></body></html>可以看到,Nginx 直接返回了 500。limit_req 模块在本节开头我们就提出了两个问题:如何限制每个客户端的并发连接数?如何限制访问频率?第一个问题限制并发连接数的问题已经解决了,下面来看第二个问题。这里面生效的模块是 ngx_http_limit_req_module,它的基本特性如下:生效阶段:NGX_HTTP_PREACCESS_PHASE 阶段模块:http_limit_req_module默认编译进 Nginx,通过 --without-http_limit_req_module 禁用生效算法:leaky bucket 算法生效范围全部 worker 进程(基于共享内存)进入 preaccess 阶段前不生效leaky bucket 算法leaky bucket 叫漏桶算法,其他用来限制请求速率的还有令牌环算法等,这里面不展开讲。漏桶算法的原理是,先定义一个桶的大小,所有进入桶内的请求都会以恒定的速率被处理,如果请求太多超出了桶的容量,那么就会立刻返回错误。用一张图解释一下。这张图里面,水龙头在不停地滴水,就像用户发来的请求,所有的水滴都会以恒定的速率流出去,也就是被处理。漏桶算法对于突发流量有很好的限制作用,会将所有的请求平滑的处理掉。指令语法定义共享内存(包括大小),以及 key 关键字和限制速率Syntax: limit_req_zone key zone=name:size rate=rate ;Default: —Context: httprate 单位为 r/s 或者 r/m(每分钟或者每秒处理多少个请求)限制并发连接数Syntax: limit_req zone=name [burst=number] [nodelay];Default: —Context: http, server, locationburst 默认为 0nodelay,如果设置了这个参数,那么对于漏桶中的请求也会立刻返回错误限制发生时的日志级别Syntax: limit_req_log_level info | notice | warn | error;Default: limit_req_log_level error; Context: http, server, location限制发生时向客户端返回的错误码Syntax: limit_req_status code;Default: limit_req_status 503; Context: http, server, location实战在实际验证之前呢,需要注意两个问题:limit_req 与 limit_conn 配置同时生效时,哪个优先级高?nodelay 添加与否,有什么不同?添加配置文件,这个配置文件与上一节的配置文件其实是相同的只不过需要注释一下:limit_conn_zone $binary_remote_addr zone=addr:10m;limit_req_zone $binary_remote_addr zone=one:10m rate=2r/m;server { listen 80;server_name limit.ziyang.com;root html/;error_log logs/myerror.log info; location /{ limit_conn_status 500; limit_conn_log_level warn; #limit_rate 50; #limit_conn addr 1; #limit_req zone=one burst=3 nodelay; limit_req zone=one; }}结论:在 limit_req zone=one 指令下,超出每分钟处理的请求数后就会立刻返回 503。➜ test_nginx curl limit.ziyang.com<html><head><title>503 Service Temporarily Unavailable</title></head><body><center><h1>503 Service Temporarily Unavailable</center><hr><center>nginx/1.17.8</center></body></html>改变一下注释的指令:limit_req zone=one burst=3;#limit_req zone=one;在没有添加 burst 参数时,会立刻返回错误,而加上之后,不会返回错误,而是等待请求限制解除,直到可以处理请求时再返回。再来看一下 nodelay 参数:limit_req zone=one burst=3 nodelay;添加了 nodelay 之后,请求在没有达到 burst 限制之前都可以立刻被处理并返回,超出了 burst 限制之后,才会返回 503。现在可以回答一下刚开始提出的两个问题:limit_req 与 limit_conn 配置同时生效时,哪个优先级高?limit_req 在 limit_conn 处理之前,因此是 limit_req 会生效nodelay 添加与否,有什么不同?不添加 nodelay,请求会等待,直到能够处理请求;添加 nodelay,在不超出 burst 的限制的情况下会立刻处理并返回,超出限制则会返回 503。access 阶段经过 preaccess 阶段对用户的限流之后,就到了 access 阶段。access 模块这里面涉及到的模块是 ngx_http_access_module,它的基本特性如下:生效阶段:NGX_HTTP_ACCESS_PHASE 阶段模块:http_access_module默认编译进 Nginx,通过 --without-http_access_module 禁用生效范围进入 access 阶段前不生效指令语法Syntax: allow address | CIDR | unix: | all;Default: —Context: http, server, location, limit_exceptSyntax: deny address | CIDR | unix: | all;Default: —Context: http, server, location, limit_exceptaccess 模块提供了两条指令 allow 和 deny,来看几个例子:location / { deny 192.168.1.1; allow 192.168.1.0/24; allow 10.1.1.0/16; allow 2001:0db8::/32; deny all; }对于用户访问来说,这些指令是顺序执行的,当满足了一条之后,就不会再向下执行。这个模块比较简单,我们这里不做实战演练了。auth_basic 模块auth_basic 模块是用作用户认证的,当开启了这个模块之后,我们通过浏览器访问网站时,就会返回一个 401 Unauthorized,当然这个 401 用户不会看见,浏览器会弹出一个对话框要求输入用户名和密码。这个模块使用的是 RFC2617 中的定义。指令语法基于 HTTP Basic Authutication 协议进行用户密码的认证默认编译进 Nginx--without-http_auth_basic_moduledisable ngx_http_auth_basic_moduleSyntax: auth_basic string | off;Default: auth_basic off; Context: http, server, location, limit_exceptSyntax: auth_basic_user_file file;Default: —Context: http, server, location, limit_except这里面我们会用到一个工具叫 htpasswd,这个工具可以用来生成密码文件,而 auth_basic_user_file 就依赖这个密码文件。htpasswd 依赖安装包 httpd-tools生成密码的命令为:htpasswd –c file –b user pass生成的密码文件的格式为:# comment name1:password1 name2:password2:comment name3:password3实战在 example 目录下生成密码文件 auth.passhtpasswd -bc auth.pass ziyang 123456添加配置文件server {server_name access.ziyang.com; listen 80;error_log logs/error.log debug;default_type text/plain;location /auth_basic { satisfy any; auth_basic "test auth_basic"; auth_basic_user_file example/auth.pass; deny all; }}重载 Nginx 配置文件在 /etc/hosts 文件中添加 access.ziyang.com这时候访问 access.ziyang.com 就会弹出对话框,提示输入密码:auth_request 模块功能:向上游的服务转发请求,若上游服务返回的响应码是 2xx,则继续执行,若上游服务返回的响应码是 2xx,则继续执行,若上游服务返回的是 401 或者 403,则将响应返回给客户端原理:收到请求后,生成子请求,通过反向代理技术把请求传递给上游服务默认未编译进 Nginx,需要通过 --with-http_auth_request_module 编译进去指令语法Syntax: auth_request uri | off;Default: auth_request off; Context: http, server, locationSyntax: auth_request_set $variable value;Default: —Context: http, server, location实战在上一个配置文件中添加以下内容server {server_name access.ziyang.com; listen 80;error_log logs/error.log debug; #root html/;default_type text/plain;location /auth_basic { satisfy any; auth_basic "test auth_basic"; auth_basic_user_file example/auth.pass; deny all; }location / { auth_request /test_auth; }location = /test_auth { proxy_pass http://127.0.0.1:8090/auth_upstream; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; }}这个配置文件中,/ 路径下会将请求转发到另外一个服务中去,可以用 nginx 再搭建一个服务如果这个服务返回 2xx,那么鉴权成功,如果返回 401 或 403 则鉴权失败限制所有 access 阶段模块的 satisfy 指令指令语法Syntax: satisfy all | any;Default: satisfy all; Context: http, server, locationsatisfy 指令有两个值一个是 all,一个是 any,这个模块对 acces 阶段的三个模块都生效:access 模块auth_basic 模块auth_request 模块其他模块如果 satisfy 指令的值是 all 的话,就表示必须所有 access 阶段的模块都要执行,都通过了才会放行;值是 any 的话,表示有任意一个模块得到执行即可。下面有几个问题可以加深一下理解:如果有 return 指令,access 阶段会生效吗?return 指令属于 rewrite 阶段,在 access 阶段之前,因此不会生效。多个 access 模块的顺序有影响吗?ngx_http_auth_request_module,ngx_http_auth_basic_module,ngx_http_access_module,有影响输对密码,下面可以访问到文件吗?location /{satisfy any;auth_basic "test auth_basic";auth_basic_user_file examples/auth.pass;deny all;}可以访问到,因为 satisfy 的值是 any,因此只要有模块满足,即可放行。如果把 deny all 提到 auth_basic 之前呢?依然可以,因为各个模块执行顺序和指令的顺序无关。如果改为 allow all,有机会输入密码吗?没有机会,因为 allow all 是 access 模块,先于 auth_basic 模块执行。precontent 阶段讲到了这里,我们再来回顾一下 Nginx 处理 HTTP 请求的 11 个阶段:现在我们已经来到了 precontent 阶段,这个阶段只有 try_files 这一个指令。try_files 模块指令语法Syntax: try_files file ... uri; try_files file ... =code;Default: —Context: server, location模块:ngx_http_try_files_module 模块依次试图访问多个 URL 对应的文件(由 root 或者 alias 指令指定),当文件存在时,直接返回文件内容,如果所有文件都不存在,则按照最后一个 URL 结果或者 code 返回实战下面我们实际看一个例子:server {server_name tryfiles.ziyang.com;listen 80;error_log logs/myerror.log info;root html/;default_type text/plain;location /first { try_files /system/maintenance.html $uri $uri/index.html $uri.html @lasturl; }location @lasturl { return 200 ‘lasturl!\n‘; }location /second { try_files $uri $uri/index.html $uri.html =404; }}结果如下:访问 /first 实际上到了 lasturl,然后返回 200访问 /second 则返回了 404这两个结果都与配置文件是一致的。➜ test_nginx curl tryfiles.ziyang.com/second<html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</center><hr><center>nginx/1.17.8</center></body></html>➜ test_nginx curl tryfiles.ziyang.com/first lasturl!mirror 模块mirror 模块可以实时拷贝流量,这对于需要同时访问多个环境的请求是非常有用的。指令语法模块:ngx_http_mirror_module 模块,默认编译进 Nginx通过 --without-http_mirror_module 移除模块功能:处理请求时,生成子请求访问其他服务,对子请求的返回值不做处理Syntax: mirror uri | off;Default: mirror off; Context: http, server, locationSyntax: mirror_request_body on | off;Default: mirror_request_body on; Context: http, server, location实战配置文件如下所示,需要再开启另外一个 Nginx 来接收请求server { server_name mirror.ziyang.com; listen 8001; error_log logs/error_log debug; location / { mirror /mirror; mirror_request_body off; } location = /mirror { internal; proxy_pass http://127.0.0.1:10020$request_uri; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; }}在 access.log 文件中可以看到有请求记录日志content 阶段下面开始就到了 content 阶段,先来看 content 阶段的 static 模块,虽然这是位于 content 阶段的最后一个处理模块,但是这里先来介绍它。static 模块root 和 alias 指令先来一下 root 和 alias 这两个指令,这两个指令都是用来映射文件路径的。Syntax: alias path;Default: —Context: locationSyntax: root path;Default: root html; Context: http, server, location, if in location功能:将 URL 映射为文件路径,以返回静态文件内容差别:root 会将完整 URL 映射进文件路径中,alias 只会将 location 后的 URL 映射到文件路径实战下面来看一个问题:现在有一个文件路径:html/first/└── 1.txt配置文件如下所示:server {server_name static.ziyang.com; listen 80;error_log logs/myerror.log info;location /root { root html; }location /alias { alias html; }location ~ /root/(\w+\.txt) { root html/first/$1; }location ~ /alias/(\w+\.txt) { alias html/first/$1; }location /RealPath/ { alias html/realpath/; return 200 ‘$request_filename:$document_root:$realpath_root\n‘; }}那么访问以下 URL 会得到什么响应呢?/root/alias/root/1.txt/alias/1.txt➜ test_nginx curl static.ziyang.com/alias/1.txttest1%➜ test_nginx curl static.ziyang.com/alias/ <!DOCTYPE html><html><head><title>Welcome to nginx!</title>...➜ test_nginx curl static.ziyang.com/root/ <html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</center><hr><center>nginx/1.17.8</center></body></html>➜ test_nginx curl static.ziyang.com/root/1.txt<html><head><title>404 Not Found</title></head><body><center><h1>404 Not Found</center><hr><center>nginx/1.17.8</center></body></html>访问这四个路径分别得到的结果是:/root:404/alias:200/root/1.txt:404/alias/1.txt:200这是为什么呢?是因为,root 在映射 URL 时,会把 location 中的路径也加进去,也就是:static.ziyang.com/root/ 实际访问的是 html/root/static.ziyang.com/root/1.txt 实际是 html/first/1.txt/root/1.txtstatic.ziyang.com/alias/ 实际上是正确访问到了 html 文件夹,由于后面有 / 的存在,因此实际访问的是 html/index.htmlstatic.ziyang.com/alias/1.txt 实际访问的是 html/first/1.txt,文件存在三个相关变量还是上面的配置文件:location /RealPath/ {alias html/realpath/; return 200 ‘$request_filename:$document_root:$realpath_root\n‘;}这里有一个问题,在访问 /RealPath/1.txt 时,这三个变量的值各为多少?为了解答这个问题,我们先来解释三个变量:request_filename:待访问文件的完整路径document_root:由 URI 和 root/alias 指令生成的文件夹路径(可能包含软链接的路径)realpath_root:将 document_root 中的软链接替换成真实路径为了验证这三个变量,在 html 目录下建立一个软链接指向 first 文件夹:ln -s first realpath➜ html curl static.ziyang.com/realpath/1.txt/Users/mtdp/myproject/nginx/test_nginx/html/realpath/1.txt:/Users/mtdp/myproject/nginx/test_nginx/html/realpath/:/Users/mtdp/myproject/nginx/test_nginx/html/first可以看出来,三个路径分别是:/Users/mtdp/myproject/nginx/test_nginx/html/realpath/1.txt/Users/mtdp/myproject/nginx/test_nginx/html/realpath//Users/mtdp/myproject/nginx/test_nginx/html/first还有其他的一些配置指令,例如:静态文件返回时的 Content-TypeSyntax: types { ... }Default: types { text/html html; image/gif gif; image/jpeg jpg; } Context: http, server, locationSyntax: default_type mime-type;Default: default_type text/plain; Context: http, server, locationSyntax: types_hash_bucket_size size;Default: types_hash_bucket_size 64; Context: http, server, locationSyntax: types_hash_max_size size;Default: types_hash_max_size 1024; Context: http, server, location未找到文件时的错误日志Syntax: log_not_found on | off;Default: log_not_found on; Context: http, server, location在生产环境中,经常可能会有找不到文件的情况,错误日志中就会打印出来:[error] 10156#0: *10723 open() "/html/first/2.txt/root/2.txt" failed (2: No such file or directory)如果不想记录日志,可以关掉。重定向跳转的域名现在有另外一个问题,当我们访问目录时最后没有带 /,static 模块会返回 301 重定向,那么这个规则是怎么定义的呢,看下面三个指令:# 该指令决定重定向时的域名,可以决定返回哪个域名Syntax: server_name_in_redirect on | off;Default: server_name_in_redirect off; Context: http, server, location# 该指令决定重定向时的端口Syntax: port_in_redirect on | off;Default: port_in_redirect on; Context: http, server, location# 该指令决定是否填域名,默认是打开的,也就是返回绝对路径Syntax: absolute_redirect on | off;Default: absolute_redirect on; Context: http, server, location这三个指令的实际用法来实战演示一下,先来看配置文件:server {server_name return.ziyang.com dir.ziyang.com;server_name_in_redirect on;listen 8088;port_in_redirect on;absolute_redirect off;root html/;}absolute_redirect 默认是打开的,我们把它关闭了,看下是怎么返回的:➜ test_nginx curl localhost:8088/first -IHTTP/1.1 301 Moved PermanentlyServer: nginx/1.17.8Date: Tue, 12 May 2020 00:31:36 GMTContent-Type: text/htmlContent-Length: 169Connection: keep-aliveLocation: /first/这个时候看到返回的头部 Location 中没有加上域名。下面再把 absolute_redirect 打开(默认是打开的,因此注释掉就行了),看下返回什么:absolute_redirect onserver_name_in_redirect onport_in_redirect on➜ test_nginx curl localhost:8088/first -IHTTP/1.1 301 Moved PermanentlyServer: nginx/1.17.8Date: Tue, 12 May 2020 00:35:49 GMTContent-Type: text/htmlContent-Length: 169Location: http://return.ziyang.com:8088/first/Connection: keep-alive可以看到,这时候就返回了域名,而且返回的是我们配置的主域名加端口号,这是因为,server_name_in_redirect 和 port_in_redirect 这两个指令打开了,如果关闭掉这两个指令,看下返回什么:absolute_redirect onserver_name_in_redirect offport_in_redirect off➜ test_nginx curl localhost:8088/first -IHTTP/1.1 301 Moved PermanentlyServer: nginx/1.17.8Date: Tue, 12 May 2020 00:39:31 GMTContent-Type: text/htmlContent-Length: 169Location: http://localhost/first/Connection: keep-alive这两个指令都设置为 off 之后,会发现返回的不再是主域名加端口号,而是我们请求的域名和端口号,如果在请求头中加上 Host,那么就会用 Host 请求头中的域名。index 模块模块:ngx_http_index_module功能:指定 / 结尾的目录访问时,返回 index 文件内容语法:Syntax: index file ...;Default: index index.html; Context: http, server, location先于 autoindex 模块执行这个模块,当我们访问以 / 结尾的目录时,会去找 root 或 alias 指令的文件夹下的 index.html,如果有这个文件,就会把文件内容返回,也可以指定其他文件。autoindex 模块模块:ngx_http_autoindex_module,默认编译进 Nginx,使用 --without-http_autoindex_module 取消功能:当 URL 以 / 结尾时,尝试以 html/xml/json/jsonp 等格式返回 root/alias 中指向目录的目录结构语法:# 开启或关闭Syntax: autoindex on | off;Default: autoindex off; Context: http, server, location# 当以 HTML 格式输出时,控制是否转换为 KB/MB/GBSyntax: autoindex_exact_size on | off;Default: autoindex_exact_size on; Context: http, server, location# 控制以哪种格式输出Syntax: autoindex_format html | xml | json | jsonp;Default: autoindex_format html; Context: http, server, location# 控制是否以本地时间格式显示还是 UTC 格式Syntax: autoindex_localtime on | off;Default: autoindex_localtime off; Context: http, server, location实战配置文件如下:server { server_name autoindex.ziyang.com; listen 8080; location / { alias html/; autoindex on; #index b.html; autoindex_exact_size on; autoindex_format html; autoindex_localtime on; }}这里我把 index b.html 这条指令给注释掉了,而 index 模块是默认编译进 Nginx 的,且默认指令是 index index.html,因此,会去找是否有 index.html 这个文件。打开浏览器,访问 autoindex.ziyang.com:8080,html 目录下默认是有 index.html 文件的,因此显示结果为:打开 index b.html 指令注释。由于 html 文件夹下并不存在 b.html 这个文件,所以请求会走到 autoindex 模块,显示目录:后面的文件大小显示格式就是由 autoindex_exact_size on; 这条指令决定的。concat模块下面介绍一个可以提升小文件性能的模块,这个模块是由阿里巴巴开发的,在淘宝网中有广泛应用。模块:ngx_http_concat_module模块开发者:Tengine(https://github.com/alibaba/nginx-http-concat) --add-module=../nginx-http-concat/功能:合并多个小文件请求,可以明显提升 HTTP 请求的性能指令:#在 URI 后面加上 ??,通过 ”,“ 分割文件,如果还有参数,则在最后通过 ? 添加参数concat on | offdefault concat offContext http, server, locationconcat_types MIME typesDefault concat_types: text/css application/x-javascriptContext http, server, locationconcat_unique on | offDefault concat_unique onContext http, server, locationconcat_max_files numberpDefault concat_max_files 10Context http, server, locationconcat_delimiter stringDefault NONEContext http, server, locationeconcat_ignore_file_error on | offDefault offContext http, server, location打开淘宝主页,会发现小文件都是通过这个模块来提高性能的:这里就不做实战了,感兴趣的同学可以自己去编译一下这个模块,做一下实验,我把配置文件放在这里:server { server_name concat.ziyang.com; error_log logs/myerror.log debug; concat on; root html; location /concat { concat_max_files 20; concat_types text/plain; concat_unique on; concat_delimiter ‘:::‘; concat_ignore_file_error on; }}log 阶段下面终于来到了 11 个阶段的最后一个阶段,记录请求访问日志的 log 模块。功能:将 HTTP 请求相关信息记录到日志模块:ngx_http_log_module,无法禁用access 日志格式Syntax: log_format name [escape=default|json|none] string ...;Default: log_format combined "..."; Context: http默认的 combined 日志格式:log_format combined ‘$remote_addr - $remote_user [$time_local] ‘ ‘"$request" $status $body_bytes_sent ‘ ‘"$http_referer" "$http_user_agent"‘;配置日志文件路径Syntax: access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]]; access_log off;Default: access_log logs/access.log combined; Context: http, server, location, if in location, limit_exceptpath 路径可以包含变量:不打开 cache 时每记录一条日志都需要打开、关闭日志文件if 通过变量值控制请求日志是否记录日志缓存功能:批量将内存中的日志写入磁盘写入磁盘的条件:所有待写入磁盘的日志大小超出缓存大小;达到 flush 指定的过期时间;worker 进程执行 reopen 命令,或者正在关闭。日志压缩功能:批量压缩内存中的日志,再写入磁盘buffer 大小默认为 64KB压缩级别默认为 1(1最快压缩率最低,9最慢压缩率最高)打开日志压缩时,默认打开日志缓存功能对日志文件名包含变量时的优化Syntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time]; open_log_file_cache off;Default: open_log_file_cache off; Context: http, server, locationmax:缓存内的最大文件句柄数,超出后用 LRU 算法淘汰inactive:文件访问完后在这段时间内不会被关闭。默认 10 秒min_uses:在 inactive 时间内使用次数超过 min_uses 才会继续存在内存中。默认 1valid:超出 valid 时间后,将对缓存的日志文件检查是否存在。默认 60 秒off:关闭缓存功能日志模块没有实战。到了这里,我们已经将 Nginx 处理 HTTP 请求的 11 个阶段全部梳理了一遍,每个阶段基本都有对应的模块。相信对于这样一个全流程的解析,大家都能够看懂 Nginx 的配置了,在此之上,还能够按照需求灵活配置出自己想要的配置,这样就真正的掌握了 11 个阶段。最后,欢迎大家关注我的个人博客:iziyang.github.io本文首发于我的个人博客:iziyang.github.io

点赞 2
66 次浏览
1 条评论