HTTP(五)web服务器
各种形状和尺寸的Web服务器
Web服务器的实现
Web 服务器有各种不同的形式。
- 可以在标准的计算机系统上安装并运行通用的软件 Web 服务器。
-
可以买一台 Web 服务器设备,通常会是一台安装在时髦机架上的计算机,里面的软件会预装并配置好。
-
甚至可以在少量计算机芯片上实现嵌入式Web 服务器,使其成为完美的(便携式)消费类设备管理控制台。
通用软件Web服务器
通用软件Web 服务器都运行在标准的、有网络功能的计算机系统上。可以选择开源软件(比如Apache 或W3C 的Jigsaw)或者商业软件(比如微软和iPlanet 的Web服务器)。基本上所有的计算机和操作系统中都有可用的Web 服务器软件。
2002 年2 月,Netcraft 调查(http://www.netcraft.com/survey/)显示有三家厂商主宰了公共因特网Web 服务器市场。
- 免费的Apache 软件占据了所• 有因特网 Web 服务器中大约 60% 的市场。
- 微软的 Web 服务器占据了另外 30%。
- Sun 的 iPlanet 占据了另外 3%。
Web服务器设备
Web 服务器设备(Web server appliance)是预先打包好的软硬件解决方案。厂商会在他们选择的计算机平台上预先安装好软件服务器,并将软件配置好。
- Sun/Cobalt RaQ Web 设备(http://www.cobalt.com);
- 东芝的 Magnia SG10(http://www.toshiba.com);
- IBM 的Whistle Web 服务器设备(http://• www.whistle.com)。
嵌入式Web服务器
- IPic 火柴头大小的 Web 服务器(http://www-ccs.cs.umass.edu/~shri/iPic.html);
- NetMedia SitePlayer SP1 以太网 Web 服务器(http://www.siteplayer.com)
最小的Perl Web服务器
可以用少于30 行的Perl代码来创建一个最小的可用HTTP 服务器。
例子显示了一个名为type-o-serve 的小型Perl 程序。这个程序是个很有用的诊断工具,可以用来测试与客户端和代理的交互情况。与所有Web 服务器一样,type-oserve会等待HTTP 连接。只要type-o-serve 收到了请求报文,就会将报文打印在屏幕上,然后等待用户输入(或粘贴)一条响应报文,并将其回送给客户端。通过这种方式,type-o-serve 假扮成一台Web 服务器,记录下确切的HTTP 请求报文,并允许用户回送任意的HTTP 响应报文。
这个简单的type-o-serve 实用程序并没有实现大部分的HTTP 功能,但它是一种很有用的工具,产生服务器响应报文的方式与Telnet 产生客户端请求报文的方式相同。可以从http://www.http-guide.com/tools/type-o-serve.pl 上下载type-oserve程序。
#!/usr/bin/perl use Socket; use Carp; use FileHandle; # (1) use port 8080 by default, unless overridden on command line $port = (@ARGV ? $ARGV[0] : 8080); # (2) create local TCP socket and set it to listen for connections $proto = getprotobyname('tcp'); socket(S, PF_INET, SOCK_STREAM, $proto) || die; setsockopt(S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die; bind(S, sockaddr_in($port, INADDR_ANY)) || die; listen(S, SOMAXCONN) || die; # (3) print a startup message printf(" <<<Type-O-Serve Accepting on Port %d>>>\n\n",$port); while (1) { # (4) wait for a connection C $cport_caddr = accept(C, S); ($cport,$caddr) = sockaddr_in($cport_caddr); C->autoflush(1); # (5) print who the connection is from $cname = gethostbyaddr($caddr,AF_INET); printf(" <<<Request From '%s'>>>\n",$cname); # (6) read request msg until blank line, and print on screen while ($line = <C>) { print $line; if ($line =~ /^\r/) { last; } } # (7) prompt for response message, and input response lines, # sending response lines to client, until solitary "." printf(" <<<Type Response Followed by '.'>>>\n"); while ($line = <STDIN>) { $line =~ s/\r//; $line =~ s/\n//; if ($line =~ /^\./) { last; } print C $line . "\r\n"; } close(C); }
-
首先,管理员启动了type-o-serve • 诊断服务器,在一个特定的端口上监听。由于Joe 的五金商店已经有一个产品化的Web 服务器在监听80 端口了,所以管理员用下面这条命令在端口8080(可以选择任意未用端口)上启动了type-o-serve 服务:
% type-o-serve.pl 8080
-
只要type-o-serve开始运行了,就可以将浏览器指向这个Web服务器。浏览器指向了http://www.joes-hardware.com:8080/foo/bar/blah.txt。
-
type-o-serve 程序收到来自浏览器的 HTTP 请求报文,并将 HTTP 请求报文的内容打印在屏幕上。然后type-o-serve 诊断工具会等待用户输入一条简单的响应报文,后面跟着只有一个句号的空行。
- type-o-serve 将 HTTP 响应报文回送给浏览器,浏览器会显示响应报文的主体。
实际的Web服务器会做些什么
显示的Perl 服务器是一个Web 服务器的小例子。最先进的商用Web 服务器要比它复杂得多,但它们确实执行了几项同样的任务。
-
建立连接——接受一个客户端连接,或者如果不希望与这个客户端建立连接,就将其关闭。
- 接收请求——从网络中读取一条HTTP 请求报文。
- 处理请求——对请求报文进行解释,并采取行动。
- 访问资源——访问报文中指定的资源。
- 构建响应——创建带有正确首部的HTTP 响应报文。
- 发送响应——将响应回送给客户端。
- 记录事务处理过程——将与已完成事务有关的内容记录在一个日志文件中。
第一步——接受客户端连接
如果客户端已经打开了一条到服务器的持久连接,可以使用那条连接来发送它的请求。否则,客户端需要打开一条新的到服务器的连接
处理新连接
客户端请求一条到Web 服务器的TCP 连接时,Web 服务器会建立连接,判断连接的另一端是哪个客户端,从TCP 连接中将IP 地址解析出来。一旦新连接建立起来并被接受,服务器就会将新连接添加到其现存Web 服务器连接列表中,做好监视连接上数据传输的准备。
Web 服务器可以随意拒绝或立即关闭任意一条连接。有些Web 服务器会因为客户端IP 地址或主机名是未认证的,或者因为它是已知的恶意客户端而关闭连接。Web服务器也可以使用其他识别技术。
客户端主机名识别
可以用“反向DNS”对大部分Web 服务器进行配置,以便将客户端IP 地址转换成客户端主机名。Web 服务器可以将客户端主机名用于详细的访问控制和日志记录。但要注意的是,主机名查找可能会花费很长时间,这样会降低Web 事务处理的速度。很多大容量Web 服务器要么会禁止主机名解析,要么只允许对特定内容进行解析。
可以用配置指令HostnameLookups 启用Apache 的主机查找功能。
HostnameLookups off <Files ~ "\.(html|htm|cgi)$"> HostnameLookups on </Files>
通过ident确定客户端用户
有些Web 服务器还支持IETF 的ident 协议。服务器可以通过ident 协议找到发起HTTP 连接的用户名。这些信息对Web 服务器的日志记录特别有用——流行的通用日志格式(Common Log Format)的第二个字段中就包含了每条HTTP 请求的ident用户名。
如果客户端支持ident 协议,就在TCP 端口113 上监听ident 请求。客户端打开了一条HTTP 连接。然后,服务器打开自己到客户端ident 服务器端口(113)的连接,发送一条简单的请求,询问与(由客户端和服务器端口号指定的)新连接相对应的用户名,并从客户端解析出包含用户名的响应。
由于没有ident 信息可用,在使用通用日志格式的日志文件中,第二个字段通常都是连字符。
第二步——接收请求报文
连接上有数据到达时,Web 服务器会从网络连接中读取数据,并将请求报文中的内容解析出来,解析请求报文时,Web 服务器会:
-
解析请求行,查找请求方法、指定的资源标识符(URI)以及版本号,各项之间由一个空格分隔,并以一个回车换行(CRLF)序列作为行的结束;
- 读取以 CRLF 结尾的报文首部;
- 检测到以 CRLF 结尾的、标识首部结束的空行(如果有的话);
- 如果有的话(长度由 Content-Length 首部指定),读取请求主体。
报文的内部表示法
有些Web 服务器还会用便于进行报文操作的内部数据结构来存储请求报文。比如,数据结构中可能包含有指向请求报文中各个片段的指针及其长度,这样就可以将这些首部存放在一个快速查询表中,以便快速访问特定首部的具体值了
连接的输入/输出处理结构
因为请求可能会在任意时刻到达,所以Web 服务器会不停地观察有无新的Web 请求。不同的Web 服务器结构会以不同的方式为请求服务。
- 单线程 Web 服务器
单线程的Web 服务器一次只处理一个请求,直到其完成为止。一个事务处理结束之后,才去处理下一条连接。这种结构易于实现,但在处理过程中,所有其他连接都会被忽略。这样会造成严重的性能问题,只适用于低负荷的服务器,以及type-o-serve 这样的诊断工具。
- 多进程及多线程 Web 服务器
多进程和多线程Web 服务器用多个进程,或更高效的线程同时对请求进行处理。可以根据需要创建,或者预先创建一些线程/ 进程。6 有些服务器会为每条连接分配一个线程/ 进程,但当服务器同时要处理成百、上千,甚至数以万计的连接时,需要的进程或线程数量可能会消耗太多的内存或系统资源。因此,很多多线程Web 服务器都会对线程/ 进程的最大数量进行限制。
- 复用 I/O 的服务器
为了支持大量的连接,很多Web 服务器都采用了复用结构。在复用结构中,要同时监视所有连接上的活动。当连接的状态发生变化时(比如,有数据可用,或出现错误时),就对那条连接进行少量的处理;处理结束之后,将连接返回到开放连接列表中,等待下一次状态变化。只有在有事情可做时才会对连接进行处理;在空闲连接上等待的时候并不会绑定线程和进程。
- 复用的多线程 Web 服务器
有些系统会将多线程和复用功能结合在一起,以利用计算机平台上的多个CPU。多个线程(通常是一个物理处理器)中的每一个都在观察打开的连接(或打开的连接中的一个子集),并对每条连接执行少量的任务。
注:进程是一个独立的程序控制流,有自己的变量集。线程是一种更快、更高效的进程版本。单个程序可以通过线程和进程同时处理多件事情。为了便于解释,我们将线程和进程当作是可以互换的概念。但由于性能的不同,很多高性能服务器既是多进程的,又是多线程的。会预先创建一些线程的系统被称为“工作池”系统,因为池中会有一组线程在等待工作。
第三步——处理请求
一旦Web 服务器收到了请求,就可以根据方法、资源、首部和可选的主体部分来对请求进行处理了。
第四步——对资源的映射及访问
docroot
Web 服务器支持各种不同类型的资源映射,但最简单的资源映射形式就是用请求URI 作为名字来访问Web 服务器文件系统中的文件。通常,Web 服务器的文件系统中会有一个特殊的文件夹专门用于存放Web 内容。这个文件夹被称为文档的根目录(document root,或docroot)。Web 服务器从请求报文中获取URI,并将其附加在文档根目录的后面。
在配置文件httpd.conf 中添加一个DocumentRoot 行就可以为Apache Web 服务器设置文档的根目录了:
DocumentRoot /usr/local/httpd/files
1. 虚拟托管的docroot
虚拟托管的Web 服务器会在同一台Web 服务器上提供多个Web 站点,每个站点在服务器上都有自己独有的文档根目录。虚拟托管Web 服务器会根据URI 或Host 首部的IP 地址或主机名来识别要使用的正确文档根目录。通过这种方式,即使请求URI 完全相同,托管在同一Web 服务器上的两个Web 站点也可以拥有完全不同的内容了。
对大多数Web 服务器来说,配置虚拟托管的文档根目录是很简单的。对常见的Apache Web 服务器来说,需要为每个虚拟Web 站点配置一个VirtualHost 块,而且每个虚拟服务器都要包含DocumentRoot。
<VirtualHost www.joes-hardware.com> ServerName www.joes-hardware.com DocumentRoot /docs/joe TransferLog /logs/joe.access_log ErrorLog /logs/joe.error_log </VirtualHost> <VirtualHost www.marys-antiques.com> ServerName www.marys-antiques.com DocumentRoot /docs/mary TransferLog /logs/mary.access_log ErrorLog /logs/mary.error_log </VirtualHost> ...
2. 用户的主目录docroot
Docroot 的另一种常见应用是在Web 服务器上为人们提供私有的Web 站点。通常会把那些以斜杠和波浪号(/~)开始,后面跟着用户名的URI 映射为此用户的私有文档根目录。私有docroot 通常都是用户主目录下那个名为public_html 的目录,但也可将其配置为其他值。
目录列表
Web 服务器可以接收对目录URL 的请求,其路径可以解析为一个目录,而不是文件。我们可以对大多数Web 服务器进行配置,使其在客户端请求目录URL 时采取不同的动作。
- 返回一个错误。
- 不返回目录,返回一个特殊的默认“索引文件”。
- 扫描目录,返回一个包含目录内容的 HTML 页面。
大多数Web 服务器都会去查找目录中一个名为index.html 或index.htm 的文件来代表此目录。如果用户请求的是一个目录的URL,而且这个目录中有一个名为index.html(或index.htm)的文件,服务器就会返回那个文件的内容。
在Apache Web 服务器上,可以用配置指令DirectoryIndex 来配置要作为默认目录文件使用的文件名集合。指令DirectoryIndex 会按照优先顺序列出所有可以作为目录索引文件使用的文件名。
如果用户请求目录URI 时,没有提供默认的索引文件,而且没有禁止使用目录索引,很多Web 服务器都会自动返回一个HTML 文件,此文件中会列出那个目录里的文件名,以及每个文件的大小和修改日期,还包括到每个文件的URI 链接。使用这个文件列表可能会很方便,但有些好事者也可以通过它在Web 服务器上找到一些通常找不到的东西。
可以通过以下Apache 指令禁止自动生成目录索引文件:
Options -Indexes
动态内容资源的映射
Web 服务器还可以将URI 映射为动态资源——也就是说,映射到按需动态生成内容的程序上去。
服务器端包含项
很多Web 服务器还提供了对服务器端包含项(SSI)的支持。如果某个资源被标识为存在服务器端包含项,服务器就会在将其发送给客户端之前对资源内容进行处理。
访问控制
Web 服务器还可以为特定资源进行访问控制。有请求到达,要访问受控资源时,Web 服务器可以根据客户端的IP 地址进行访问控制,也可以要求输入密码来访问资源。
第五步——构建响应
响应实体
如果事务处理产生了响应主体,就将内容放在响应报文中回送过去。如果有响应主体的话,响应报文中通常包括:
- 描述了响应主体 MIME 类型的 Content-Type 首部;
- 描述了响应主体长度的 Content-Length 首部;
- 实际报文的主体内容。
MIME类型
- MIME 类型(mime.types)
Web 服务器可以用文件的扩展名来说明MIME 类型。Web 服务器会为每个资源扫描一个包含了所有扩展名的MIME 类型的文件,以确定其MIME 类型。这种基于扩展名的类型相关是最常见的
- 魔法分类(Magic typing)
Apache Web 服务器可以扫描每个资源的内容,并将其与一个已知模式表(被称为魔法文件)进行匹配,以决定每个文件的MIME 类型。这样做可能比较慢,但很方便,尤其是文件没有标准扩展名的时候。
- 显式分类(Explicit typing)
可以对Web 服务器进行配置,使其不考虑文件的扩展名或内容,强制特定文件或目录内容拥有某个MIME 类型。
- 类型协商
有些Web 服务器经过配置,可以以多种文档格式来存储资源。在这种情况下,可以配置Web 服务器,使其可以通过与用户的协商来决定使用哪种格式(及相关的MIME 类型)“最好”
重定向
Web 服务器有时会返回重定向响应而不是成功的报文。 Web 服务器可以将浏览器重定向到其他地方来执行请求。重定向响应由返回码3XX 说明。Location 响应首部包含了内容的新地址或优选地址的URI。重定向可用于下列情况。
- 永久搬离的资源
资源可能已经被移动到了新的位置,或者被重新命名,有了一个新的URL。Web 服务器可以告诉客户端资源已经被重命名了,这样客户端就可以在从新地址获取资源之前,更新书签之类的信息了。状态码301 Moved Permanently 就用于此类重定向。
- 临时搬离的资源
如果资源被临时移走或重命名了,服务器可能希望将客户端重定向到新的位置上去。但由于重命名是临时的,所以服务器希望客户端将来还可以回头去使用老的URL,不要对书签进行更新。状态码303 See Other 以及状态码307 TemporaryRedirect 就用于此类重定向。
- URL 增强
服务器通常用重定向来重写URL,往往用于嵌入上下文。当请求到达时,服务器会生成一个新的包含了嵌入式状态信息的URL,并将用户重定向到这个新的URL 上去。客户端会跟随这个重定向信息,重新发起请求,但这次的请求会包含完整的、经过状态增强的URL。这是在事务间维护状态的一种有效方式。状态码303 See Other 和307 Temporary Redirect 用于此类重定向。
- 负载均衡
如果一个超载的服务器收到一条请求,服务器可以将客户端重定向到一个负载不太重的服务器上去。状态码303 See Other 和307 Temporary Redirect 可用于此类重定向。
- 服务器关联
Web 服务器上可能会有某些用户的本地信息;服务器可以将客户端重定向到包含了那个客户端信息的服务器上去。状态码303 See Other 和307 Temporary Redirect可用于此类重定向。
- 规范目录名称
客户端请求的URI 是一个不带尾部斜线的目录名时,大多数Web 服务器都会将客户端重定向到一个加了斜线的URI 上,这样相对链接就可以正常工作了。
第六步——发送响应
服务器要记录连接的状态,还要特别注意对持久连接的处理。对非持久连接而言,服务器应该在发送了整条报文之后,关闭自己这一端的连接。
对持久连接来说,连接可能仍保持打开状态,在这种情况下,服务器要特别小心,要正确地计算Content-Length 首部,不然客户端就无法知道响应什么时候结束了
第七步——记录日志
当事务结束时,Web 服务器会在日志文件中添加一个条目,来描述已执行的事务。大多数Web 服务器都提供了几种日志配置格式。