前言
基于前三篇关于HTTP原理(URL与资源,HTTP报文,连接管理)的介绍,我们已经大体清楚了浏览器发起一次HTTP事务的具体过程及涉及的其中细节:浏览器从URL中解析出服务器IP地址和端口号(如果有的话)——》浏览器建立一条与Web服务器的TCP连接——》浏览器向服务器发送HTTP请求报文——》服务器向浏览器发送HTTP响应报文——》关闭连接。本节我们就继续探究HTTP事务的目的端——Web服务器。
Web服务器有着各种不同的风格,形状和尺寸,比如可以在PC机上安装并运行的通用软件Web服务器Apache,Nginx等,已经预装并配置好相关程式的Web服务器设备以及某些便携式的嵌入式Web服务器。但不论功能有何差异,所有的Web 服务器都要能接受请求资源的HTTP请求,将内容回送给客户端。
实际的Web服务器在做什么
Web服务器逻辑实现了HTTP协议,管理着Web资源,并负责提供Web服务器的管理功能。Web服务器逻辑和操作系统共同负责管理TCP连接,底层操作系统负责管理底层计算机系统的硬件细节,并提供了TCP/IP网络支持,负责装载Web资源的文件系统以及控制当前计算活动的进程管理功能。
第一步——接受客户端连接
如果客户端已经打开了一条到服务器到持久连接,可以使用那条连接来发送请求,否则客户端需要打开一条新的服务器连接。当客户端请求一条到Web服务器的TCP连接时,Web服务器会建立连接,判断连接是哪个客户端,从TCP连接中将IP地址解析出来。一旦新连接建立并被接受,服务器就会将新连接添加到其现存Web服务器连接列表中,做好监视连接上数据传输的准备。
当然,Web服务器可以随意拒绝或立即关闭任意一条连接,比如IP或主机名未认证或已知恶意客户端。
关于请求连接的客户端主机名识别(比如将其用于详细的访问控制和日志记录),可以用’反向DNS’对大部分Web服务器进行配置,(主机名查找可能会比较费时,降低Web事务处理的速度),比如配置指令HostnameLookups启用Apache的主机查找功能。
1 | HostnameLookups off |
如果客户端支持ident协议,就在TCP端口113监听ident请求。服务器打开自己到客户端ident端口的连接,发送询问用户名的请求。但出于多种原因,在公共网络上这并不能很好的工作。
第二步——接受请求报文
连接上有数据到达时,Web服务器会从网络连接中读取数据,并将请求报文中的内容解析出来。解析请求报文时,Web服务器会:
- 解析请求行,查找请求方法,指定的资源标识符(URI)以及版本号;
- 读取以CRLF结尾的报文首部;
- 检测到以CRLF结尾的、标识首部结束的空行(如果有的话);
- 如果有的话(长度由Content-Length首部指定),读取请求主体;
解析请求报文时,Web服务器会不定期地从网络上接收输入数据。网络连接可能随时会出现延迟。Web服务器需要从网络中读取数据,将部分报文数据临时存储在内存中,直到收到足以进行解析的数据并理解其意义为止。
有些Web服务器还会用便于进行报文操作的内部数据结构来存储请求报文——快速查询表。
Web服务作为IO密集型应用,高性能的Web服务器能够同时支持数十万条连接,某些连接可能快速地向Web服务器发送请求,一些可能在慢慢发送,或者不经常发送请求,还有一些可能空闲,等待任意时刻可能出现的动作。
不同的Web服务器结构会以不同的方式为请求服务:
- 单线程Web服务器:单线程的Web服务器一次只处理一个请求,直到其完成为止,存在严重的性能问题,只适用于低负荷服务器;
- 多进程及多线程Web服务器:根据需要创建,或预先创建一些线程/进程,有些服务器为每条连接分配一个线程/进程,但当同时
处理成百,甚至数以万计的连接时,需要的进程/线程数量可能会消耗太多的内存或系统资源; - 复用I/O服务器:为支持大量连接,在复用结构中,要同时监视所有连接上的活动。当连接的状态发生变化时(比如数据可用,
或出现错误)就对那条连接进行少量的处理。处理结束后,将连接返回到开放连接列表中,等待下一次的状态变化。只有在有事情
可做时才会对连接进行处理,在空闲连接上等待时不会绑定线程和进程。 - 复用的多线程Web服务器:有些系统会将多线程与复用功能结合在一起。多个线程(通常是一个物理处理器)中每一个都在观察
打开的连接(或打开的连接中的一个子集)并对每条连接执行少量的任务,比如后续文章提到的Nginx。
第三步——处理请求
一旦Web服务器收到了请求,就可以根据方法,资源,首部和可选的主体部分对请求进行处理了。
第四步——对资源的映射及访问
Web服务器是资源服务器。在Web服务器将内容传送给客户端之前,要将请求中的URI映射为Web服务器上适当的内容或内容生成器,以识别处内容的源头;
docroot
Web服务器支持各种不同类型的资源映射,但最简单的资源映射形式就用请求的URI作为名字来访问 Web 服务器文件系统中的文件。通常Web服务器的文件系统中会有一个特殊的文件夹专门用于存放Web内容,这个文件夹被称为文档的根目录(document root 或docroot)。Web服务器从请求报文中获取URI,并将其附加在文档根目录的后面。
1 | #配置文件httpd.conf添加一个DocumentRoot就可以为Web服务器设置文档根目录 |
虚拟托管的Web服务器会在同一台Web服务器上提供多个Web站点,每个站点在服务器上都有自己独有的文档根目录。虚拟托管的Web服务器会根据URI或Host首部的IP地址或主机名来识别要使用的正确文档根目录。
1 | #大多数服务器配置虚拟托管文档根目录很简单,比如Apache |
Docroot的另一种常见应用是在Web服务器上为人们提供私有的Web站点。通常以/~开始,后面跟用户名的URI映射为此用户的私有文档根目录,比如/~blob/index.html。私有docroot通常是用户主目录下名为public_html的目录,也可以配置为其他值。
目录列表
Web服务器可以接受对目录URL的请求,其路径可以解析为一个目录而不是文件。我们可以对大多数Web服务器进行配置,使其在客户端请求目录URL时采取不同的动作:
- 返回一个错误;
- 不返回目录,返回一个特殊的默认’索引文件’,大多数服务器做法,比如Apache
DirectoryIndex index.php;
- 扫描目录,返回一个包含目录内容的HTML页面,通常禁止自动生成目录索引文件,比如Apache
Options -Indexes
;
动态内容资源映射
Web服务器可将URI映射为动态资源,也就是说映射到按需生成内容的程序上去——应用程序服务器。Web 服务器能够分辨资源何时动态,动态内容生成程序位于何处,以及如何运行那个程序。
比如Apache允许用户将URI路径名组件映射为可执行文件目录:ScriptAlias /cgi-bin/ /usr/local/ect/httpd/cgi-programs/
;Apache还允许用户用一个特殊的文件扩展名来标识可执行文件:AddHandler cig-script .cgi
。
服务器端包含项
如果某个资源被标识为存在服务器端包含项(SSI),服务器就会将其发送给客户端之前对资源内容进行处理。要对内容进行扫描,以查找(通常包含在特定HTML注释中)特定的模板,这些模板可以是变量名,也可以是嵌入式脚本。可以用变量的值或可执行脚本的输出取代特定的模板。这是创建动态内容的一种简便方式。
访问控制
Web服务器还可以为特定资源进行访问控制,比如根据IP地址进行访问控制。
第五步——构建响应
一旦Web服务器识别出了资源,就执行请求方法中描述的动作,并返回响应报文。响应报文中包含有响应状态码,响应首部,如果生成了响应主体的话,还包括响应主体(Content-Type,Content-Length,MIME类型)。
Web服务器负责确定响应主体的MIME类型,可以根据文件扩展名映射;根据已知模式表(被称为魔法文件)对扫描的资源内容进行匹配;显式分类,配置Web 服务器使其不考虑文件扩展名或内容强制特定文件或目录内容拥有的某个MIME类型;类型协商;
重定向
Web服务器有时会返回重定向响应而不是成功的报文:返回码3XX,Location响应首部包含了内容的新地址或优选地址的URI。
重定向可用于下列情况:
- 访问永久删除的资源,返回码301,并更新书签;
- 访问临时删除的资源,返回码303或307,不更新书签;
- URL增强,服务器通常用重定向来重写URL,往往用于嵌入上下文。当请求到达时,服务器会生成一个新的包含了嵌入式状态信息的URL,
并将用户重定向到这个新的URL上去。客户端会跟随这个重定向信息重新发起请求,此请求包含完整的、经过状态增强的URL; 返回码303或307;
综上这些就是实际Web服务器中要做的一些事情,这些的确都是些浮于表面的繁琐理论,真正的实际使用和技术实现难点都在冰山之下呢。此文也只是让大家对实际中服务器行为有一个大体的认知,下篇文章就开始认知经典的Nginx,它是如何处理高并发的,以及了解一些具体的实用配置。