2.1 nginx服务器
Web网站的功能由编程语言实现,例如Java、Python和PHP等。编程语言专注于网站功能的实现,资源映射与连接处理则由服务器软件完成。常见的服务器软件有Apache、nginx和Tomcat等,接下来我们将通过nginx来增进对服务器的了解。
nginx是一个HTTP和反向代理服务器,同时也是邮件代理服务器和通用的TCP / UDP代理服务器。它具有模块化设计、可扩展、低内存消耗、支持热部署等优秀特性,所以非常多的Web应用将其作为服务器软件。本书也将使用它实现一些反爬虫的功能。
nginx有一个主进程和若干工作进程,其中主进程用于读取和评估配置并维护工作进程,工作进程会对请求进行实际处理。nginx采用基于事件的模型和依赖于操作系统的机制,有效地在工作进程之间分发请求。工作进程数在配置文件中进行定义,可以设定具体数值或使用默认选项。
2.1.1 nginx的信号
信号(signal)是控制nginx工作状态的模块,我们可以在终端使用信号来控制nginx的启动、停止和配置重载等。信号的语法格式如下:
nginx -s signal
其中signal是信号名称。常用的nginx信号有以下几种。
❑ stop:快速关机。
❑ quit:正常关机。
❑ reload:重新加载配置文件。
❑ reopen:重新打开日志文件。
假如我们需要停止nginx服务,但又希望它处理完当前请求后再停止工作进程,可以在终端向nginx发送正常关机的信号,命令为:
$ nginx -s quit
当nginx的配置被更改或者添加新的辅助配置文件时,它们不会立即生效。如果想让新的配置生效,就必须重新启动nginx或者进行配置重载。假如我们希望nginx在不影响当前任务处理的情况下重载配置,可以通过终端向nginx发送重新加载配置文件的信号,命令为:
$ nginx -s reload
一旦主进程收到配置重载信号,它将检查新配置文件的语法有效性,并尝试应用其中的配置。如果成功,主进程将启动新的工作进程并向旧工作进程发送关闭请求,否则主进程将回滚更改,并继续使用旧配置。旧工作进程在接收关闭请求后会停止接受新连接,并且继续为当前请求提供服务,直到当前请求处理完毕才关闭。
更多nginx信号的知识可前往nginx官方文档查看,详见http://nginx.org/en/docs/control.html。
2.1.2 nginx配置文件
nginx由模块组成,而这些模块由配置文件中特定的指令控制,也就是说nginx的配置文件决定了nginx及其模块的工作方式。nginx的配置文件分为主配置文件和辅助配置文件:主配置文件名为nginx.conf,默认存放在 /etc/nginx目录中;辅助配置文件要求以 .conf作为文件后缀,并且默认存放在/etc/nginx/conf.d目录中。要注意的是,nginx允许同时存在多个辅助配置文件。
nginx的指令分为简单指令和块指令。一个简单的指令由指令名称和参数组成,它们以空格作为分隔符,并以分号结尾,如:
error_page 404 /404.html;
其中error_page是指令名称,404和 /404.html共同组成参数,作用是指定404错误显示的HTML文件。
块指令与简单指令具有相同的结构,但它不是以分号结尾,而是以花括号包围的一组附加指令结束,如:
location /404.html { root /home/async/www/error_page; }
如果块指令内包含其他指令,则该块指令称为上下文。常见的上下文有events、http、server和location。要注意的是,这里还有一个隐藏的main上下文,它并非实际存在,类似于层级的根目录,即所有的指令的最外层都是main。main上下文作为其他上下文的参考对象,例如events和http,必须写在main上下文中,server必须写在http中,而location则必须写在server中。对于它们的关系,我们可以通过一段简单的配置来理解:
http { server { location / { root /www/index index.html; } location /images/ { # ... } } }
配置文件的注释符为#。以上配置默认监听80端口,当我们在本地访问http://localhost/时,服务器将根据配置文件设定的资源路径寻找资源,并将符合条件的资源发送给客户端,如果资源不存在,则发送404错误。我们并没有在配置中添加任何有关main的文字,但http上下文确实包含在main中。
nginx提供了一个默认的辅助配置文件default.conf,存放在 /etc/nginx/conf.d目录中,里面包含了若干server块指令示例。我们可以在终端使用如下命令查看default.conf文件的内容:
$sudo cat /etc/nginx/conf.d/default.conf
命令执行后,终端会输出如下内容(以 ... 代替部分被注释的内容):
server { listen 80; server_name localhost; #charset koi8-r; #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 # ... #} }
2.1.3 简单的代理服务
提供资源是服务器的功能之一,接下来我们就来学习如何通过nginx实现简单的代理服务。本次任务要求服务器根据用户的请求URL,响应服务器本地目录中的资源(如 /data/www目录中包含的HTML文件和 /data/images目录中包含的图片文件)。我们可以通过编辑nginx的配置文件,将URL和本地目录中的资源进行映射,从而实现这个需求。
首先,我们需要准备本地目录的资源。在用户目录(如 /home/async)下创建www文件夹,将包含任意内容的index.html文件放到www目录中。在www文件夹中创建images文件夹,并在里面放置一张名为example.png的图片。
然后使用编辑器打开nginx默认的辅助配置文件default.conf,将其中的所有代码注释后,编写新的配置:
http { server { } }
在通常情况下,server需要确定监听的端口和服务器名称, nginx一旦决定处理请求,就会根据块指令中定义的指令参数测试请求头中指定的URI。我们将下方的location添加到server中:
location / { root /home/async/www; }
配置中的location指定“/”与请求中的URI进行比较。对于匹配的请求,URI将指向root指令中指定的路径,即 /home/async/www,以便将本地资源与请求对应。如果存在多个匹配的location块指令,那么nginx会选择具有最长前缀的块指令。当无法匹配到其他前缀时,就会匹配“/”。
接下来添加第二个location块指令:
location /images/ { root /home/async/www; }
它将匹配以“/images/”开头的URI(“/”也匹配此类请求,但由于“/”的前缀长度比“/images/”短,所以优先匹配“/images/”)。完整的辅助配置文件内容如下:
server { location / { root /home/async/www; } location /images/ { root /home/async/www; } }
如果想让刚才的配置生效,我们需要给nginx发送重载配置的信号:
$ nginx -s reload
配置生效后,nginx就会监听80端口(80是默认值),我们可以在浏览器中访问http://localhost/。服务器为响应这次请求,会根据配置文件中指定的文件路径搜索HTML文件,并优先选择名为index.html的文件作为响应内容。如果在浏览器中访问http://localhost/images/example.png,那么nginx会根据配置文件,从指定的路径中搜索example.png文件,如果存在,则返回文件资源,否则触发404错误。
在真正访问http://localhost/之前,还有两件事要做。第一件事是编辑nginx主配置文件,将配置中User指定的用户从nginx改为你的操作系统的用户名(比如我的操作系统的用户名为async)。打开主配置文件的命令为:
$sudo nano /etc/nginx/nginx.conf
主配置文件的第一行即是User设置。
第二件事是关闭SELinux。SELinux是美国国家安全局(NSA)对于强制访问控制的实现,默认安装在版本较新的Linux系统中。如果当前操作系统中没有SELinux,则无须关闭。打开SELinux配置文件的命令为:
$sudo nano /etc/selinux/config
注意将配置文件中的SELINUX=enforcing改为SELINUX=disabled,保存改动后重新启动操作系统。
接着我们用浏览器访问http://localhost/,此时显示的内容如图2-2所示。
图2-2 浏览器显示的内容
再试一试访问http://localhost/images/example.png,浏览器显示的内容如图2-3所示。
图2-3 浏览器中显示的图片
当我们使用浏览器访问指定的URL后,如果浏览器中能够正确显示我们准备的HTML内容和图片,就说明nginx辅助配置已生效。
2.1.4 nginx模块与指令
nginx内置了很多模块,可以从nginx文档中的Modules reference部分查看。其中比较常用的模块是ngx_http_rewrite_module(详见http://nginx.org/en/docs/http/ngx_http_rewrite_module.html),我们可以通过学习该模块来熟悉nginx模块的语法。
ngx_http_rewrite_module模块的主要作用是重定向,它通过正则表达式或判断语句来更改请求的URI。该模块有一些主要的指令,这些指令分别是if、set、break、return和rewrite。if指令的语法和语境如表2-1所示。
表2-1 if指令的语法和语境
我们可以看到if指令的语法是:
if 条件 { ... }
这与其他编程语言的语法非常相似,很容易理解。if指令没有默认值,使用范围限制在server块和location块内。它的条件有以下几种情况。
❑ 变量名称:如果变量的值是空字符串或0,则条件的布尔值为false;在nginx 1.0.1版本之前,任何以0开头的字符串都被认为是错误的值。
❑ =或!=:比较变量和字符串。
❑ ~或~*:将变量与正则表达式匹配,区分大小写。如果正则表达式包含}或;字符,则整个表达式应该用单引号或双引号括起来。
❑ -f或!-f:检查文件是否存在。
❑ -d或!-d:检查目录是否存在。
❑ -e或!-e:检查文件、目录或符号链接是否存在。
❑ -x或!-x:检查可执行文件。
我们可以通过一些例子来理解这些条件判断:
# 当请求头中User-Agent 头域的值包含MSIE 字符串,则重定向到指定URI if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /msie/$1 break; } # 当请求头中Cookie 头域的值满足条件,则设定$id 变量值为正则部分 if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } # 如果请求方式是 POST,则返回 405 if ($request_method = POST) { return 405; } # 限制下载速度为10k,$slow 可以通过set 指令设置 if ($slow) { limit_rate 10k; } #当请求头中Referer 头域的值为空或www.example.com 时,允许访问,否则返回403 valid_referers none www.example.com; if ($invalid_referer) { return 403; }
我们可以从其中选出一个作为验证对象。打开nginx辅助配置文件default.conf,并在文件中添加对Referer的判断语句:
server { location / { # 对请求的Referer 进行验证,如果没有Referer 头域 # 或者头域值为www.example.com,则允许访问 valid_referers none www.example.com; if ($invalid_referer) { return 403; } root /home/async/www; } location /images/ { root /home/async/www; } }
为了让nginx启用新配置,我们需要给它发送reload信号:
$ nginx -s reload
为了验证请求的过滤效果,我们可以使用Postman进行测试。首先测试没有Referer头域的请求,得到的结果如图2-4所示。
图2-4 Postman请求结果1
本次请求的响应状态码为200,说明服务器可以正常响应客户端的请求。然后测试Referer头域的值不满足条件的情况,结果如图2-5所示。
图2-5 Postman请求结果2
本次请求的响应状态码为403,说明服务器对Referer头域的值进行判断后,发现它并不符合要求。除了以上给出的$invalid_referer和$request_method变量之外,还有哪些变量呢?在介绍ngx_http_core_module时,我们给出了nginx支持的嵌入式变量,其中常用的变量及含义如表2-2所示。
表2-2 nginx中常用的变量及其含义
nginx所支持的嵌入变量详见http://nginx.org/en/docs/http/ngx_http_core_module.html#variables。
nginx指令列表详见http://nginx.org/en/docs/http/ngx_http_core_module.html#Directives。以limit_rate指令为例,其语法和语境如表2-3所示。
表2-3 limit_rate指令的语法和语境
limit_rate的作用是限制客户端的传输速度,单位为字节/秒,默认值为0,即不限速。limit_rate是对单个请求设置限制的,意味着如果客户端同时打开两个连接,则总速率将是指定限制的两倍。除了全局限速以外,还可以根据条件设置限速:
server { if (condition) { set $limit_rate 100k; } }
ngx_http_rewrite_module模块的语法和语境如表2-4所示。
表2-4 ngx_http_rewrite_module模块的语法和语境
如果指定的正则表达式与请求URI匹配,则URI将根据replacement字符串的指定进行更改。该rewrite指令按照其在配置文件中出现的顺序依次执行,可以使用标志终止对指令的进一步处理。如果替换字符串以http://、https://或$scheme开头,则处理停止并将重定向返回给客户端。
flag参数可以是以下值之一。
❑ last:停止处理当前的ngx_http_rewrite_module指令集,并开始搜索与更改后的URI匹配的新位置。
❑ break:ngx_http_rewrite_module与break指令一样,可以停止处理当前的指令集。
❑ redirect:返回带有302代码的临时重定向。如果替换字符串不以http://、https://或$scheme开头,则使用它。
❑ permanent:返回301代码的永久重定向。
官方给出的重定向示例代码如下:
server { ... rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last; return 403; ... }
但是如果将这些指令放在/download/位置内,则该last标志应替换为break,否则nginx将进行10次循环并返回500错误:
location /download/ { rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break; rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break; return 403; }
2.1.5 nginx日志
日志是nginx的重要组成部分,记录着每一次请求的相关信息,是开发者了解客户端请求和服务器端响应状态的好帮手。nginx的日志分为访问日志和错误日志,存储路径可以在nginx主配置文件中查看,设置访问日志存放路径的指令名为access_log,而设置错误日志存放路径的指令名为error_log,示例命令如下:
access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log;
1. 访问日志
访问日志主要记录客户端访问nginx的请求信息,如客户端的IP地址、请求的URI、响应状态、Referer头域的值等。以下记录为本机nginx访问记录中的一条:
127.0.0.1 - - [11/Mar/2019:08:42:43 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0" "-"
nginx中与访问日志相关的指令是log_format和access_log。log_format用来设置访问日志的格式,也就是日志文件中每条日志记录的格式,它在主配置文件中的设置如图2-6所示。
图2-6 访问日志格式
访问日志默认使用main格式,并且里面记录了很多的信息,这些信息的含义如表2-5所示。
表2-5 log_format支持的变量及其释义
2. 错误日志
错误日志主要记录客户端访问nginx错误时的请求信息,不支持自定义格式。错误日志有多种等级,如debug、info、notice、warn、error和crit,从左到右日志级别逐步递增。nginx的主配置文件中将错误日志的级别设为warn。以下记录为本机nginx错误记录中的一条:
2019/03/10 20:30:44 [error] 11160#11160: *1 "/home/async/www/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: , request: "GET / HTTP/1.1", host: "localhost"
我们可以在错误记录中看到错误发生的具体时间、原因、客户端的IP地址、请求方式和协议版本等信息,这对我们排查错误和测试有很大的帮助。
2.1.6 小结
nginx是一款轻量、高性能的服务器应用,配置灵活、功能强大,深受开发者喜爱。nginx具有条件判断、连接限制和客户端信息获取等功能,这些功能为开发者限制爬虫程序提供了条件。